js.foresight 3.5.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +14 -46
- package/dist/BaseForesightModule-CJzWVlF2.mjs +2 -0
- package/dist/BaseForesightModule-CJzWVlF2.mjs.map +1 -0
- package/dist/DesktopHandler-CR9nLuzu.mjs +2 -0
- package/dist/DesktopHandler-CR9nLuzu.mjs.map +1 -0
- package/dist/ElementObservingModule-QE8ZBcnz.mjs +2 -0
- package/dist/ElementObservingModule-QE8ZBcnz.mjs.map +1 -0
- package/dist/ScrollPredictor-ZHRqX0lh.mjs +2 -0
- package/dist/ScrollPredictor-ZHRqX0lh.mjs.map +1 -0
- package/dist/TabPredictor-C2zyaEQZ.mjs +2 -0
- package/dist/TabPredictor-C2zyaEQZ.mjs.map +1 -0
- package/dist/TouchDeviceHandler-BPzPdr-z.mjs +2 -0
- package/dist/TouchDeviceHandler-BPzPdr-z.mjs.map +1 -0
- package/dist/TouchStartPredictor-r7R_E74t.mjs +2 -0
- package/dist/TouchStartPredictor-r7R_E74t.mjs.map +1 -0
- package/dist/ViewportPredictor-aTE2EmU2.mjs +2 -0
- package/dist/ViewportPredictor-aTE2EmU2.mjs.map +1 -0
- package/dist/index.d.mts +594 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lineSegmentIntersectsRect-x9nicliA.mjs +2 -0
- package/dist/lineSegmentIntersectsRect-x9nicliA.mjs.map +1 -0
- package/dist/rectAndHitSlop-T7Z3PZlb.mjs +2 -0
- package/dist/rectAndHitSlop-T7Z3PZlb.mjs.map +1 -0
- package/package.json +27 -28
- package/dist/DesktopHandler-BOXAW4XX.js +0 -1
- package/dist/ScrollPredictor-Y7NELMBI.js +0 -1
- package/dist/TabPredictor-HA2SV3CY.js +0 -1
- package/dist/TouchDeviceHandler-JWBQ2YOV.js +0 -1
- package/dist/TouchStartPredictor-ZH3KJG2C.js +0 -1
- package/dist/ViewportPredictor-H3GLDETY.js +0 -1
- package/dist/chunk-44N4MCQB.js +0 -1
- package/dist/chunk-AODZNE3S.js +0 -1
- package/dist/chunk-PAYO6NXN.js +0 -1
- package/dist/index.d.ts +0 -531
- package/dist/index.js +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Bart Spaans
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -19,53 +19,29 @@ ForesightJS is a lightweight JavaScript library that predicts user intent to pre
|
|
|
19
19
|

|
|
20
20
|
_In the GIF above, the [ForesightJS DevTools](https://foresightjs.com/docs/debugging/devtools) are enabled. Normally, users won't see anything that ForesightJS does except the increased perceived speed from early prefetching._
|
|
21
21
|
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
pnpm add js.foresight
|
|
26
|
-
# or
|
|
27
|
-
npm install js.foresight
|
|
28
|
-
# or
|
|
29
|
-
yarn add js.foresight
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Basic Usage Example
|
|
22
|
+
## Integrations
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. There are official packages for React and Vue:
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
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.
|
|
28
|
+
- **Vue** → [`@foresightjs/vue`](https://foresightjs.com/docs/vue/installation): the `v-foresight` directive and the `useForesight` / `useForesights` / `useForesightEvent` composables.
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
ForesightManager.initialize({
|
|
41
|
-
// Optional props (see configuration)
|
|
42
|
-
})
|
|
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.
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
const myButton = document.getElementById("my-button")
|
|
32
|
+
Using another framework (Angular, Svelte, Solid, …)? See [Other Frameworks](https://foresightjs.com/docs/other-frameworks) for how to build your own thin binding on top of the core. Sharing integrations for other frameworks/packages is highly appreciated!
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
element: myButton,
|
|
49
|
-
callback: () => {
|
|
50
|
-
// This is where your prefetching logic goes
|
|
51
|
-
},
|
|
52
|
-
hitSlop: 20, // Optional: "hit slop" in pixels.
|
|
53
|
-
// other optional props (see configuration)
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Integrations
|
|
34
|
+
## Configuration
|
|
58
35
|
|
|
59
|
-
|
|
36
|
+
ForesightJS works out of the box with no setup required, but it can be configured both [globally](https://foresightjs.com/docs/configuration/global-settings) and per [element](https://foresightjs.com/docs/configuration/registration-options) if needed.
|
|
60
37
|
|
|
61
|
-
|
|
62
|
-
- **Vue**: [Directive](https://foresightjs.com/docs/vue/directive) and [Composable](https://foresightjs.com/docs/vue/composable)
|
|
38
|
+
## Prediction Strategies
|
|
63
39
|
|
|
64
|
-
|
|
40
|
+
ForesightJS uses different prediction strategies depending on the device type:
|
|
65
41
|
|
|
66
|
-
|
|
42
|
+
**Desktop/Keyboard Users**: Mouse trajectory prediction, keyboard navigation tracking, and scroll-based prefetching. [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#keyboardmouse-users)
|
|
67
43
|
|
|
68
|
-
|
|
44
|
+
**Mobile Devices**: Viewport enter detection and touch start events (configurable via [`touchDeviceStrategy`]). [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#touch-devices-v330)
|
|
69
45
|
|
|
70
46
|
## Development Tools
|
|
71
47
|
|
|
@@ -77,21 +53,13 @@ pnpm add js.foresight-devtools
|
|
|
77
53
|
|
|
78
54
|
See the [development tools documentation](https://foresightjs.com/docs/debugging/devtools) for more details.
|
|
79
55
|
|
|
80
|
-
## Prediction Strategies
|
|
81
|
-
|
|
82
|
-
ForesightJS uses different prediction strategies depending on the device type:
|
|
83
|
-
|
|
84
|
-
**Desktop/Keyboard Users**: Mouse trajectory prediction, keyboard navigation tracking, and scroll-based prefetching. [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#keyboardmouse-users)
|
|
85
|
-
|
|
86
|
-
**Mobile Devices**: Viewport enter detection and touch start events (configurable via [`touchDeviceStrategy`]). [Read more](https://foresightjs.com/docs/getting-started/what-is-foresightjs#touch-devices-v330)
|
|
87
|
-
|
|
88
56
|
## Providing Context to AI Tools
|
|
89
57
|
|
|
90
58
|
ForesightJS is a newer library, so most AI assistants and LLMs may not have much built-in knowledge about it. To improve their responses, you can provide the following context:
|
|
91
59
|
|
|
92
60
|
- Use [llms.txt](https://foresightjs.com/llms.txt) for a concise overview of the API and usage patterns.
|
|
93
61
|
- Use [llms-full.txt](https://foresightjs.com/llms-full.txt) for a full markdown version of the docs, ideal for AI tools that support context injection or uploads.
|
|
94
|
-
- All documentation pages are also available in markdown. You can view them by adding .md to the end of any URL, for example: https://foresightjs.com/docs/
|
|
62
|
+
- All documentation pages are also available in markdown. You can view them by adding .md to the end of any URL, for example: [https://foresightjs.com/docs/getting-started/what-is-foresightjs.md](https://foresightjs.com/docs/getting-started/what-is-foresightjs.md).
|
|
95
63
|
|
|
96
64
|
[Read more](https://foresightjs.com/docs/ai-context)
|
|
97
65
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class{get isConnected(){return this._isConnected}constructor(e){this._isConnected=!1,this._cachedLogStyle=null,this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,this.hasListeners=e.hasListeners,this.updateElementState=e.updateElementState,this.updateElementBounds=e.updateElementBounds,this.settings=e.settings}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.isConnected||(this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0)}devLog(e){this.settings.enableManagerLogging&&(this._cachedLogStyle===null&&(this._cachedLogStyle=`color: ${this.moduleName.includes(`Predictor`)?`#ea580c`:`#2563eb`}; font-weight: bold;`),console.log(`%c${this.moduleName}: ${e}`,this._cachedLogStyle))}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};export{e as t};
|
|
2
|
+
//# sourceMappingURL=BaseForesightModule-CJzWVlF2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseForesightModule-CJzWVlF2.mjs","names":[],"sources":["../src/core/BaseForesightModule.ts"],"sourcesContent":["import type {\n CallbackHitType,\n ElementBounds,\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n ForesightEvent,\n ForesightEventMap,\n ForesightManagerSettings,\n} from \"../types/types\"\n\ntype CallCallbackFunction = (\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType\n) => void\n\ntype EmitFunction = <K extends ForesightEvent>(event: ForesightEventMap[K]) => void\n\nexport type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean\n\ntype UpdateElementStateFunction = (\n entry: ForesightElementInternal,\n patch: Partial<ForesightElementState>\n) => ForesightElementState\n\ntype UpdateElementBoundsFunction = (\n entry: ForesightElementInternal,\n next: ElementBounds\n) => ElementBounds\n\nexport type ForesightModuleDependencies = {\n elements: ReadonlyMap<ForesightElement, ForesightElementInternal>\n callCallback: CallCallbackFunction\n emit: EmitFunction\n hasListeners: HasListenersFunction\n updateElementState: UpdateElementStateFunction\n updateElementBounds: UpdateElementBoundsFunction\n settings: ForesightManagerSettings\n}\n\nexport abstract class BaseForesightModule {\n protected abortController?: AbortController\n protected elements: ReadonlyMap<ForesightElement, ForesightElementInternal>\n protected callCallback: CallCallbackFunction\n protected emit: EmitFunction\n protected hasListeners: HasListenersFunction\n protected updateElementState: UpdateElementStateFunction\n protected updateElementBounds: UpdateElementBoundsFunction\n protected settings: ForesightManagerSettings\n private _isConnected = false\n private _cachedLogStyle: string | null = null\n\n public get isConnected(): boolean {\n return this._isConnected\n }\n\n protected abstract readonly moduleName: string // Name of the implementor class for debugging purposes\n\n constructor(dependencies: ForesightModuleDependencies) {\n this.elements = dependencies.elements\n this.callCallback = dependencies.callCallback\n this.emit = dependencies.emit\n this.hasListeners = dependencies.hasListeners\n this.updateElementState = dependencies.updateElementState\n this.updateElementBounds = dependencies.updateElementBounds\n this.settings = dependencies.settings\n }\n\n public disconnect(): void {\n if (!this.isConnected) {\n return\n }\n\n this.devLog(`Disconnecting ${this.moduleName}...`)\n this.abortController?.abort(`${this.moduleName} module disconnected`)\n this.onDisconnect()\n this._isConnected = false\n }\n\n public connect(): void {\n if (this.isConnected) {\n return\n }\n\n this.devLog(`Connecting ${this.moduleName}...`)\n this.onConnect()\n this._isConnected = true\n }\n\n public devLog(message: string): void {\n if (!this.settings.enableManagerLogging) {\n return\n }\n\n // Cache the log style on first use to avoid repeated string operations\n if (this._cachedLogStyle === null) {\n const color = this.moduleName.includes(\"Predictor\") ? \"#ea580c\" : \"#2563eb\"\n this._cachedLogStyle = `color: ${color}; font-weight: bold;`\n }\n\n console.log(`%c${this.moduleName}: ${message}`, this._cachedLogStyle)\n }\n\n protected abstract onConnect(): void\n protected abstract onDisconnect(): void\n\n protected createAbortController(): void {\n if (this.abortController && !this.abortController.signal.aborted) {\n return\n }\n\n this.abortController = new AbortController()\n\n this.devLog(`Created new AbortController for ${this.moduleName}`)\n }\n}\n"],"mappings":"AAwCA,IAAsB,EAAtB,KAA0C,CAYxC,IAAW,aAAuB,CAChC,OAAO,KAAK,aAKd,YAAY,EAA2C,mBAThC,wBACkB,KASvC,KAAK,SAAW,EAAa,SAC7B,KAAK,aAAe,EAAa,aACjC,KAAK,KAAO,EAAa,KACzB,KAAK,aAAe,EAAa,aACjC,KAAK,mBAAqB,EAAa,mBACvC,KAAK,oBAAsB,EAAa,oBACxC,KAAK,SAAW,EAAa,SAG/B,YAA0B,CACnB,KAAK,cAIV,KAAK,OAAO,iBAAiB,KAAK,WAAW,KAAK,CAClD,KAAK,iBAAiB,MAAM,GAAG,KAAK,WAAW,sBAAsB,CACrE,KAAK,cAAc,CACnB,KAAK,aAAe,IAGtB,SAAuB,CACjB,KAAK,cAIT,KAAK,OAAO,cAAc,KAAK,WAAW,KAAK,CAC/C,KAAK,WAAW,CAChB,KAAK,aAAe,IAGtB,OAAc,EAAuB,CAC9B,KAAK,SAAS,uBAKf,KAAK,kBAAoB,OAE3B,KAAK,gBAAkB,UADT,KAAK,WAAW,SAAS,YAAY,CAAG,UAAY,UAC3B,uBAGzC,QAAQ,IAAI,KAAK,KAAK,WAAW,IAAI,IAAW,KAAK,gBAAgB,EAMvE,uBAAwC,CAClC,KAAK,iBAAmB,CAAC,KAAK,gBAAgB,OAAO,UAIzD,KAAK,gBAAkB,IAAI,gBAE3B,KAAK,OAAO,mCAAmC,KAAK,aAAa"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{n as e,r as t,t as n}from"./rectAndHitSlop-T7Z3PZlb.mjs";import{t as r}from"./BaseForesightModule-CJzWVlF2.mjs";import{t as i}from"./ElementObservingModule-QE8ZBcnz.mjs";import{t as a}from"./lineSegmentIntersectsRect-x9nicliA.mjs";import{PositionObserver as o}from"position-observer";const s=(e,t,n,r)=>{let i=performance.now();t.add({point:{x:e.x,y:e.y},time:i});let{x:a,y:o}=e;if(t.length<2){r.x=a,r.y=o;return}let[s,c]=t.getFirstLast();if(!s||!c){r.x=a,r.y=o;return}let l=(c.time-s.time)*.001;if(l===0){r.x=a,r.y=o;return}let u=c.point.x-s.point.x,d=c.point.y-s.point.y,f=u/l,p=d/l,m=n*.001;r.x=a+f*m,r.y=o+p*m};var c=class extends r{constructor(e){super(e.dependencies),this.moduleName=`MousePredictor`,this.trajectoryPositions=e.trajectoryPositions,this.mouseTrajectoryEvent={type:`mouseTrajectoryUpdate`,predictionEnabled:!1,trajectoryPositions:this.trajectoryPositions}}updatePointerState(e){let t=this.trajectoryPositions.currentPoint;t.x=e.clientX,t.y=e.clientY,this.settings.enableMousePrediction?s(t,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=t.x,this.trajectoryPositions.predictedPoint.y=t.y)}processMouseMovement(e){this.updatePointerState(e);let n=this.settings.enableMousePrediction,r=this.trajectoryPositions.currentPoint;for(let e of this.elements.values()){let i=e.state;if(!i.isIntersectingWithViewport||!i.isActive||i.isPredicted)continue;let o=e.bounds.expandedRect;if(n)a(r,this.trajectoryPositions.predictedPoint,o)&&this.callCallback(e,{kind:`mouse`,subType:`trajectory`});else if(t(r,o)){this.callCallback(e,{kind:`mouse`,subType:`hover`});continue}}this.hasListeners(`mouseTrajectoryUpdate`)&&(this.mouseTrajectoryEvent.predictionEnabled=n,this.emit(this.mouseTrajectoryEvent))}onDisconnect(){}onConnect(){}},l=class{constructor(e){if(this.head=0,this.count=0,e<=0)throw Error(`CircularBuffer capacity must be greater than 0`);this.capacity=e,this.buffer=Array(e)}add(e){this.buffer[this.head]=e,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let e=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[e,e]}return[this.getFirst(),this.getLast()]}resize(e){if(e<=0)throw Error(`CircularBuffer capacity must be greater than 0`);if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=Array(e),this.head=0,this.count=0,t.length>e){let n=t.slice(-e);for(let e of n)this.add(e)}else for(let e of t)this.add(e)}getAllItems(){if(this.count===0)return[];let e=Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)e[t]=this.buffer[t];else{let t=this.head;for(let n=0;n<this.capacity;n++){let r=(t+n)%this.capacity;e[n]=this.buffer[r]}}return e}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}},u=class extends i{constructor(r){super(r),this.moduleName=`DesktopHandler`,this.tabPredictor=null,this.scrollPredictor=null,this.positionObserver=null,this.trajectoryPositions={positions:new l(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}},this.handlePositionChange=e=>{let t=this.settings.enableScrollPrediction;for(let n of e){let e=this.elements.get(n.target);e&&(t?this.scrollPredictor?.handleScrollPrefetch(e,n.boundingClientRect):this.checkForMouseHover(e),this.handlePositionChangeDataUpdates(e,n))}t&&(this.scrollPredictor?.emitTrajectoryUpdate(),this.scrollPredictor?.resetScrollProps())},this.checkForMouseHover=e=>{t(this.trajectoryPositions.currentPoint,e.bounds.expandedRect)&&this.callCallback(e,{kind:`mouse`,subType:`hover`})},this.handlePositionChangeDataUpdates=(t,r)=>{let i=r.isIntersecting;i&&!n(r.boundingClientRect,t.bounds.originalRect)&&this.updateElementBounds(t,{originalRect:r.boundingClientRect,expandedRect:e(r.boundingClientRect,t.state.hitSlop)}),t.state.isIntersectingWithViewport!==i&&this.updateElementState(t,{isIntersectingWithViewport:i})},this.processMouseMovement=e=>this.mousePredictor.processMouseMovement(e),this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache(),this.connectTabPredictor=async()=>{if(!this.tabPredictor){let{TabPredictor:e}=await import(`./TabPredictor-C2zyaEQZ.mjs`);this.tabPredictor=new e(this.storedDependencies),this.devLog(`TabPredictor lazy loaded`)}this.tabPredictor.connect()},this.connectScrollPredictor=async()=>{if(!this.scrollPredictor){let{ScrollPredictor:e}=await import(`./ScrollPredictor-ZHRqX0lh.mjs`);this.scrollPredictor=new e({dependencies:this.storedDependencies,trajectoryPositions:this.trajectoryPositions}),this.devLog(`ScrollPredictor lazy loaded`)}this.scrollPredictor.connect()},this.connectMousePredictor=()=>this.mousePredictor.connect(),this.disconnectTabPredictor=()=>this.tabPredictor?.disconnect(),this.disconnectScrollPredictor=()=>this.scrollPredictor?.disconnect(),this.disconnectMousePredictor=()=>this.mousePredictor.disconnect(),this.storedDependencies=r,this.mousePredictor=new c({dependencies:r,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new o(this.handlePositionChange);let e=[`mouse`];this.settings.enableTabPrediction&&e.push(`tab (loading...)`),this.settings.enableScrollPrediction&&e.push(`scroll (loading...)`),this.devLog(`Connected predictors: [${e.join(`, `)}] and PositionObserver`);for(let e of this.elements.keys())this.positionObserver.observe(e)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}observeElement(e){this.positionObserver?.observe(e)}unobserveElement(e){this.positionObserver?.unobserve(e)}get loadedPredictors(){return{mouse:this.mousePredictor!==null,tab:this.tabPredictor!==null,scroll:this.scrollPredictor!==null}}};export{u as DesktopHandler};
|
|
2
|
+
//# sourceMappingURL=DesktopHandler-CR9nLuzu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DesktopHandler-CR9nLuzu.mjs","names":[],"sources":["../src/helpers/predictNextMousePosition.ts","../src/predictors/MousePredictor.ts","../src/helpers/CircularBuffer.ts","../src/managers/DesktopHandler.ts"],"sourcesContent":["import type { MousePosition, Point } from \"../types/types\"\nimport type { CircularBuffer } from \"./CircularBuffer\"\n\n/**\n * Predicts the next mouse position based on a history of recent movements.\n * It calculates velocity from the historical data and extrapolates a future point.\n * The `buffer` is mutated by this function: the new `currentPoint` is added,\n * automatically overwriting the oldest entry when the buffer is full.\n *\n * @param currentPoint - The current actual mouse coordinates.\n * @param buffer - A circular buffer of previous mouse positions with timestamps.\n * This buffer will be modified by this function.\n * @param trajectoryPredictionTimeInMs - How far into the future (in milliseconds)\n * to predict the mouse position.\n * @param out - Output point to mutate with the predicted position.\n */\nexport const predictNextMousePosition = (\n currentPoint: Point,\n buffer: CircularBuffer<MousePosition>,\n trajectoryPredictionTimeInMs: number,\n out: Point\n): void => {\n const now = performance.now()\n // Create a copy of currentPoint for buffer storage (buffer needs independent copies)\n buffer.add({ point: { x: currentPoint.x, y: currentPoint.y }, time: now })\n\n const { x, y } = currentPoint\n\n if (buffer.length < 2) {\n out.x = x\n out.y = y\n\n return\n }\n\n const [first, last] = buffer.getFirstLast()\n if (!first || !last) {\n out.x = x\n out.y = y\n\n return\n }\n\n const dt = (last.time - first.time) * 0.001\n if (dt === 0) {\n out.x = x\n out.y = y\n\n return\n }\n\n const dx = last.point.x - first.point.x\n const dy = last.point.y - first.point.y\n const vx = dx / dt\n const vy = dy / dt\n\n const trajectoryPredictionTimeInSeconds = trajectoryPredictionTimeInMs * 0.001\n out.x = x + vx * trajectoryPredictionTimeInSeconds\n out.y = y + vy * trajectoryPredictionTimeInSeconds\n}\n","import { lineSegmentIntersectsRect } from \"../helpers/lineSegmentIntersectsRect\"\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\nimport { isPointInRectangle } from \"../helpers/rectAndHitSlop\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { MouseTrajectoryUpdateEvent, TrajectoryPositions } from \"../types/types\"\n\ninterface MousePredictorConfig {\n dependencies: ForesightModuleDependencies\n trajectoryPositions: TrajectoryPositions\n}\n\nexport class MousePredictor extends BaseForesightModule {\n protected readonly moduleName = \"MousePredictor\"\n\n private trajectoryPositions: TrajectoryPositions\n\n // Pre-allocated event object to avoid creating a new object every frame (~60/sec)\n private readonly mouseTrajectoryEvent: MouseTrajectoryUpdateEvent\n\n constructor(config: MousePredictorConfig) {\n super(config.dependencies)\n\n this.trajectoryPositions = config.trajectoryPositions\n this.mouseTrajectoryEvent = {\n type: \"mouseTrajectoryUpdate\",\n predictionEnabled: false,\n trajectoryPositions: this.trajectoryPositions,\n }\n }\n\n private updatePointerState(e: MouseEvent): void {\n // Mutate existing point objects instead of creating new ones (perf optimization)\n const currentPoint = this.trajectoryPositions.currentPoint\n currentPoint.x = e.clientX\n currentPoint.y = e.clientY\n\n if (this.settings.enableMousePrediction) {\n predictNextMousePosition(\n currentPoint,\n this.trajectoryPositions.positions,\n this.settings.trajectoryPredictionTime,\n this.trajectoryPositions.predictedPoint // reuse existing object\n )\n } else {\n this.trajectoryPositions.predictedPoint.x = currentPoint.x\n this.trajectoryPositions.predictedPoint.y = currentPoint.y\n }\n }\n\n public processMouseMovement(e: MouseEvent): void {\n this.updatePointerState(e)\n const enablePrediction = this.settings.enableMousePrediction\n const currentPoint = this.trajectoryPositions.currentPoint\n\n for (const internal of this.elements.values()) {\n const state = internal.state\n if (!state.isIntersectingWithViewport || !state.isActive || state.isPredicted) {\n continue\n }\n\n const expandedRect = internal.bounds.expandedRect\n\n if (!enablePrediction) {\n if (isPointInRectangle(currentPoint, expandedRect)) {\n this.callCallback(internal, { kind: \"mouse\", subType: \"hover\" })\n\n continue\n }\n\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\n } else if (\n lineSegmentIntersectsRect(\n currentPoint,\n this.trajectoryPositions.predictedPoint,\n expandedRect\n )\n ) {\n this.callCallback(internal, { kind: \"mouse\", subType: \"trajectory\" })\n }\n }\n\n if (this.hasListeners(\"mouseTrajectoryUpdate\")) {\n this.mouseTrajectoryEvent.predictionEnabled = enablePrediction\n this.emit(this.mouseTrajectoryEvent)\n }\n }\n\n protected onDisconnect(): void {}\n protected onConnect(): void {}\n}\n","export class CircularBuffer<T> {\n private buffer: T[]\n private head: number = 0\n private count: number = 0\n private capacity: number\n\n constructor(capacity: number) {\n if (capacity <= 0) {\n throw new Error(\"CircularBuffer capacity must be greater than 0\")\n }\n\n this.capacity = capacity\n this.buffer = new Array(capacity)\n }\n\n add(item: T): void {\n this.buffer[this.head] = item\n this.head = (this.head + 1) % this.capacity\n\n if (this.count < this.capacity) {\n this.count++\n }\n }\n\n getFirst(): T | undefined {\n if (this.count === 0) {\n return undefined\n }\n\n if (this.count < this.capacity) {\n return this.buffer[0]\n } else {\n return this.buffer[this.head]\n }\n }\n\n getLast(): T | undefined {\n if (this.count === 0) {\n return undefined\n }\n\n if (this.count < this.capacity) {\n return this.buffer[this.count - 1]\n } else {\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity\n\n return this.buffer[lastIndex]\n }\n }\n\n getFirstLast(): [T | undefined, T | undefined] {\n if (this.count === 0) {\n return [undefined, undefined]\n }\n\n if (this.count === 1) {\n const item = this.count < this.capacity ? this.buffer[0] : this.buffer[this.head]\n\n return [item, item]\n }\n\n const first = this.getFirst()\n const last = this.getLast()\n\n return [first, last]\n }\n\n resize(newCapacity: number): void {\n if (newCapacity <= 0) {\n throw new Error(\"CircularBuffer capacity must be greater than 0\")\n }\n\n if (newCapacity === this.capacity) {\n return\n }\n\n const currentItems = this.getAllItems()\n this.capacity = newCapacity\n this.buffer = new Array(newCapacity)\n this.head = 0\n this.count = 0\n\n if (currentItems.length > newCapacity) {\n const itemsToKeep = currentItems.slice(-newCapacity)\n for (const item of itemsToKeep) {\n this.add(item)\n }\n } else {\n for (const item of currentItems) {\n this.add(item)\n }\n }\n }\n\n private getAllItems(): T[] {\n if (this.count === 0) {\n return []\n }\n\n const result: T[] = new Array(this.count)\n\n if (this.count < this.capacity) {\n for (let i = 0; i < this.count; i++) {\n result[i] = this.buffer[i]\n }\n } else {\n const startIndex = this.head\n for (let i = 0; i < this.capacity; i++) {\n const bufferIndex = (startIndex + i) % this.capacity\n result[i] = this.buffer[bufferIndex]\n }\n }\n\n return result\n }\n\n clear(): void {\n this.head = 0\n this.count = 0\n }\n\n get length(): number {\n return this.count\n }\n\n get size(): number {\n return this.capacity\n }\n\n get isFull(): boolean {\n return this.count === this.capacity\n }\n\n get isEmpty(): boolean {\n return this.count === 0\n }\n}\n","import type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { MousePredictor } from \"../predictors/MousePredictor\"\nimport type { ScrollPredictor } from \"../predictors/ScrollPredictor\"\nimport type { TabPredictor } from \"../predictors/TabPredictor\"\nimport { PositionObserver, PositionObserverEntry } from \"position-observer\"\nimport type {\n ForesightElement,\n ForesightElementInternal,\n TrajectoryPositions,\n} from \"../types/types\"\nimport { CircularBuffer } from \"../helpers/CircularBuffer\"\nimport { DEFAULT_POSITION_HISTORY_SIZE } from \"../constants\"\nimport { areRectsEqual, getExpandedRect, isPointInRectangle } from \"../helpers/rectAndHitSlop\"\n\nexport class DesktopHandler extends ElementObservingModule {\n protected readonly moduleName = \"DesktopHandler\"\n\n private mousePredictor: MousePredictor\n private tabPredictor: TabPredictor | null = null\n private scrollPredictor: ScrollPredictor | null = null\n private positionObserver: PositionObserver | null = null\n private storedDependencies: ForesightModuleDependencies\n\n public trajectoryPositions: TrajectoryPositions = {\n positions: new CircularBuffer(DEFAULT_POSITION_HISTORY_SIZE),\n currentPoint: { x: 0, y: 0 },\n predictedPoint: { x: 0, y: 0 },\n }\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n this.storedDependencies = dependencies\n\n // Only MousePredictor is instantiated immediately - Tab and Scroll are lazy-loaded\n this.mousePredictor = new MousePredictor({\n dependencies,\n trajectoryPositions: this.trajectoryPositions,\n })\n }\n\n protected onConnect(): void {\n if (this.settings.enableTabPrediction) {\n this.connectTabPredictor()\n }\n\n if (this.settings.enableScrollPrediction) {\n this.connectScrollPredictor()\n }\n\n this.connectMousePredictor() // We always connect the mouse predictor\n this.positionObserver = new PositionObserver(this.handlePositionChange)\n\n const enabledPredictors = [\"mouse\"]\n\n if (this.settings.enableTabPrediction) {\n enabledPredictors.push(\"tab (loading...)\")\n }\n\n if (this.settings.enableScrollPrediction) {\n enabledPredictors.push(\"scroll (loading...)\")\n }\n\n this.devLog(`Connected predictors: [${enabledPredictors.join(\", \")}] and PositionObserver`)\n\n for (const element of this.elements.keys()) {\n this.positionObserver.observe(element)\n }\n }\n\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\n const enableScrollPosition = this.settings.enableScrollPrediction\n\n for (const positionEntry of entries) {\n const entry = this.elements.get(positionEntry.target)\n\n if (!entry) {\n continue\n }\n\n if (enableScrollPosition) {\n this.scrollPredictor?.handleScrollPrefetch(entry, positionEntry.boundingClientRect)\n } else {\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\n this.checkForMouseHover(entry)\n }\n\n // Must run AFTER handleScrollPrefetch/checkForMouseHover - scroll\n // direction is derived from the difference between the old and new\n // originalRect, so entry.bounds must not be updated before they read it.\n this.handlePositionChangeDataUpdates(entry, positionEntry)\n }\n\n if (enableScrollPosition) {\n this.scrollPredictor?.emitTrajectoryUpdate()\n this.scrollPredictor?.resetScrollProps()\n }\n }\n\n private checkForMouseHover = (entry: ForesightElementInternal) => {\n if (isPointInRectangle(this.trajectoryPositions.currentPoint, entry.bounds.expandedRect)) {\n this.callCallback(entry, {\n kind: \"mouse\",\n subType: \"hover\",\n })\n }\n }\n\n private handlePositionChangeDataUpdates = (\n entry: ForesightElementInternal,\n positionEntry: PositionObserverEntry\n ) => {\n const isNowIntersecting = positionEntry.isIntersecting\n\n // Bounds are updated BEFORE the state patch so state subscribers always\n // read fresh geometry.\n if (\n isNowIntersecting &&\n !areRectsEqual(positionEntry.boundingClientRect, entry.bounds.originalRect)\n ) {\n this.updateElementBounds(entry, {\n originalRect: positionEntry.boundingClientRect,\n expandedRect: getExpandedRect(positionEntry.boundingClientRect, entry.state.hitSlop),\n })\n }\n\n if (entry.state.isIntersectingWithViewport !== isNowIntersecting) {\n this.updateElementState(entry, { isIntersectingWithViewport: isNowIntersecting })\n }\n }\n\n protected onDisconnect(): void {\n this.disconnectMousePredictor()\n this.disconnectTabPredictor()\n this.disconnectScrollPredictor()\n this.positionObserver?.disconnect()\n this.positionObserver = null\n }\n\n public processMouseMovement = (event: PointerEvent) =>\n this.mousePredictor.processMouseMovement(event)\n\n public invalidateTabCache = () => this.tabPredictor?.invalidateCache()\n\n public observeElement(element: ForesightElement): void {\n this.positionObserver?.observe(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.positionObserver?.unobserve(element)\n }\n\n public connectTabPredictor = async () => {\n if (!this.tabPredictor) {\n const { TabPredictor } = await import(\"../predictors/TabPredictor\")\n this.tabPredictor = new TabPredictor(this.storedDependencies)\n this.devLog(\"TabPredictor lazy loaded\")\n }\n\n this.tabPredictor.connect()\n }\n\n public connectScrollPredictor = async () => {\n if (!this.scrollPredictor) {\n const { ScrollPredictor } = await import(\"../predictors/ScrollPredictor\")\n this.scrollPredictor = new ScrollPredictor({\n dependencies: this.storedDependencies,\n trajectoryPositions: this.trajectoryPositions,\n })\n\n this.devLog(\"ScrollPredictor lazy loaded\")\n }\n\n this.scrollPredictor.connect()\n }\n\n public connectMousePredictor = () => this.mousePredictor.connect()\n\n public disconnectTabPredictor = () => this.tabPredictor?.disconnect()\n public disconnectScrollPredictor = () => this.scrollPredictor?.disconnect()\n public disconnectMousePredictor = () => this.mousePredictor.disconnect()\n\n /** For debugging: returns which predictors have been lazy loaded */\n public get loadedPredictors() {\n return {\n mouse: this.mousePredictor !== null,\n tab: this.tabPredictor !== null,\n scroll: this.scrollPredictor !== null,\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ElementObservingModule-QE8ZBcnz.mjs","names":[],"sources":["../src/core/ElementObservingModule.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport { BaseForesightModule } from \"./BaseForesightModule\"\n\n/**\n * Abstract base for modules that observe and unobserve individual elements.\n *\n * Declaring these methods on a shared base lets the manager call them through\n * a single type (e.g. `currentlyActiveHandler.observeElement(el)`) while still\n * keeping each subclass responsible for its own observation strategy\n * (PositionObserver, IntersectionObserver, pointerdown listeners, etc.).\n */\nexport abstract class ElementObservingModule extends BaseForesightModule {\n public abstract observeElement(element: ForesightElement): void\n public abstract unobserveElement(element: ForesightElement): void\n}\n"],"mappings":"uDAWA,IAAsB,EAAtB,cAAqD,CAAoB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./BaseForesightModule-CJzWVlF2.mjs";import{t}from"./lineSegmentIntersectsRect-x9nicliA.mjs";const n=(e,t)=>{let n=t.top-e.top,r=t.left-e.left;return n<-1?`down`:n>1?`up`:r<-1?`right`:r>1?`left`:`none`},r=(e,t,n)=>{let{x:r,y:i}=e,a={x:r,y:i};switch(t){case`down`:a.y+=n;break;case`up`:a.y-=n;break;case`left`:a.x-=n;break;case`right`:a.x+=n;break;case`none`:break;default:}return a};var i=class extends e{constructor(e){super(e.dependencies),this.moduleName=`ScrollPredictor`,this.predictedScrollPoint=null,this.scrollDirection=null,this.onDisconnect=()=>this.resetScrollProps(),this.trajectoryPositions=e.trajectoryPositions,this.scrollTrajectoryEvent={type:`scrollTrajectoryUpdate`,currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:{x:0,y:0},scrollDirection:`none`}}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(e,i){let a=e.state;!a.isIntersectingWithViewport||a.isPredicted||!a.isActive||(this.scrollDirection=this.scrollDirection??n(e.bounds.originalRect,i),this.scrollDirection!==`none`&&(this.predictedScrollPoint=this.predictedScrollPoint??r(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),t(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,e.bounds.expandedRect)&&this.callCallback(e,{kind:`scroll`,subType:this.scrollDirection})))}emitTrajectoryUpdate(){!this.scrollDirection||this.scrollDirection===`none`||!this.predictedScrollPoint||!this.hasListeners(`scrollTrajectoryUpdate`)||(this.scrollTrajectoryEvent.predictedPoint=this.predictedScrollPoint,this.scrollTrajectoryEvent.scrollDirection=this.scrollDirection,this.emit(this.scrollTrajectoryEvent))}onConnect(){}};export{i as ScrollPredictor};
|
|
2
|
+
//# sourceMappingURL=ScrollPredictor-ZHRqX0lh.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollPredictor-ZHRqX0lh.mjs","names":[],"sources":["../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/predictors/ScrollPredictor.ts"],"sourcesContent":["import type { Rect, ScrollDirection } from \"../types/types\"\n\nexport const getScrollDirection = (oldRect: Rect, newRect: Rect): ScrollDirection => {\n const scrollThreshold = 1\n const deltaY = newRect.top - oldRect.top\n const deltaX = newRect.left - oldRect.left\n\n // Check vertical scroll first (most common)\n if (deltaY < -scrollThreshold) {\n return \"down\" // Element moved up in viewport = scrolled down\n } else if (deltaY > scrollThreshold) {\n return \"up\" // Element moved down in viewport = scrolled up\n }\n\n // Check horizontal scroll\n if (deltaX < -scrollThreshold) {\n return \"right\" // Element moved left in viewport = scrolled right\n } else if (deltaX > scrollThreshold) {\n return \"left\" // Element moved right in viewport = scrolled left\n }\n\n return \"none\" // No significant movement detected\n}\n","import type { Point, ScrollDirection } from \"../types/types\"\n\nexport const predictNextScrollPosition = (\n currentPoint: Point,\n direction: ScrollDirection,\n scrollMargin: number\n) => {\n const { x, y } = currentPoint\n const predictedPoint = { x, y }\n\n switch (direction) {\n case \"down\":\n predictedPoint.y += scrollMargin\n break\n case \"up\":\n predictedPoint.y -= scrollMargin\n break\n case \"left\":\n predictedPoint.x -= scrollMargin\n break\n case \"right\":\n predictedPoint.x += scrollMargin\n break\n case \"none\":\n break\n default:\n direction satisfies never\n }\n\n return predictedPoint\n}\n","import { getScrollDirection } from \"../helpers/getScrollDirection\"\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSegmentIntersectsRect\"\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\nimport type {\n ForesightElementInternal,\n Point,\n ScrollDirection,\n ScrollTrajectoryUpdateEvent,\n TrajectoryPositions,\n} from \"../types/types\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\n\ninterface ScrollPredictorConfig {\n dependencies: ForesightModuleDependencies\n trajectoryPositions: Readonly<TrajectoryPositions>\n}\n\nexport class ScrollPredictor extends BaseForesightModule {\n protected readonly moduleName = \"ScrollPredictor\"\n\n private predictedScrollPoint: Point | null = null\n private scrollDirection: ScrollDirection | null = null\n private trajectoryPositions: Readonly<TrajectoryPositions>\n\n // Pre-allocated event object to avoid creating a new object every scroll frame\n private readonly scrollTrajectoryEvent: ScrollTrajectoryUpdateEvent\n\n constructor(config: ScrollPredictorConfig) {\n super(config.dependencies)\n this.trajectoryPositions = config.trajectoryPositions\n this.scrollTrajectoryEvent = {\n type: \"scrollTrajectoryUpdate\",\n currentPoint: this.trajectoryPositions.currentPoint,\n predictedPoint: { x: 0, y: 0 },\n scrollDirection: \"none\",\n }\n }\n\n public resetScrollProps(): void {\n this.scrollDirection = null\n this.predictedScrollPoint = null\n }\n\n public handleScrollPrefetch(entry: ForesightElementInternal, newRect: DOMRect): void {\n const state = entry.state\n if (!state.isIntersectingWithViewport || state.isPredicted || !state.isActive) {\n return\n }\n\n // ONCE per handlePositionChange batch we decide what the scroll direction is.\n // entry.bounds still holds the PRE-scroll rect here - the DesktopHandler only\n // updates bounds after this runs (see handlePositionChange ordering).\n this.scrollDirection =\n this.scrollDirection ?? getScrollDirection(entry.bounds.originalRect, newRect)\n\n if (this.scrollDirection === \"none\") {\n return\n }\n\n // ONCE per handlePositionChange batch we decide the predicted scroll point\n this.predictedScrollPoint =\n this.predictedScrollPoint ??\n predictNextScrollPosition(\n this.trajectoryPositions.currentPoint,\n this.scrollDirection,\n this.settings.scrollMargin\n )\n\n // Check if the scroll is going to intersect with an registered element\n if (\n lineSegmentIntersectsRect(\n this.trajectoryPositions.currentPoint,\n this.predictedScrollPoint,\n entry.bounds.expandedRect\n )\n ) {\n this.callCallback(entry, {\n kind: \"scroll\",\n subType: this.scrollDirection,\n })\n }\n }\n\n /**\n * Emits a single trajectory update for the whole position-change batch;\n * direction and predicted point are identical for every element in it.\n * Called by the DesktopHandler after the batch, before resetScrollProps.\n */\n public emitTrajectoryUpdate(): void {\n if (\n !this.scrollDirection ||\n this.scrollDirection === \"none\" ||\n !this.predictedScrollPoint ||\n !this.hasListeners(\"scrollTrajectoryUpdate\")\n ) {\n return\n }\n\n this.scrollTrajectoryEvent.predictedPoint = this.predictedScrollPoint\n this.scrollTrajectoryEvent.scrollDirection = this.scrollDirection\n this.emit(this.scrollTrajectoryEvent)\n }\n\n protected onConnect() {}\n protected onDisconnect = () => this.resetScrollProps()\n}\n"],"mappings":"+GAEA,MAAa,GAAsB,EAAe,IAAmC,CACnF,IACM,EAAS,EAAQ,IAAM,EAAQ,IAC/B,EAAS,EAAQ,KAAO,EAAQ,KAgBtC,OAbI,EAAS,GACJ,OACE,EAAS,EACX,KAIL,EAAS,GACJ,QACE,EAAS,EACX,OAGF,QCnBI,GACX,EACA,EACA,IACG,CACH,GAAM,CAAE,IAAG,KAAM,EACX,EAAiB,CAAE,IAAG,IAAG,CAE/B,OAAQ,EAAR,CACE,IAAK,OACH,EAAe,GAAK,EACpB,MACF,IAAK,KACH,EAAe,GAAK,EACpB,MACF,IAAK,OACH,EAAe,GAAK,EACpB,MACF,IAAK,QACH,EAAe,GAAK,EACpB,MACF,IAAK,OACH,MACF,SAIF,OAAO,GCZT,IAAa,EAAb,cAAqC,CAAoB,CAUvD,YAAY,EAA+B,CACzC,MAAM,EAAO,aAAa,iBAVI,4CAEa,0BACK,2BAmFnB,KAAK,kBAAkB,CA3EpD,KAAK,oBAAsB,EAAO,oBAClC,KAAK,sBAAwB,CAC3B,KAAM,yBACN,aAAc,KAAK,oBAAoB,aACvC,eAAgB,CAAE,EAAG,EAAG,EAAG,EAAG,CAC9B,gBAAiB,OAClB,CAGH,kBAAgC,CAC9B,KAAK,gBAAkB,KACvB,KAAK,qBAAuB,KAG9B,qBAA4B,EAAiC,EAAwB,CACnF,IAAM,EAAQ,EAAM,MAChB,CAAC,EAAM,4BAA8B,EAAM,aAAe,CAAC,EAAM,WAOrE,KAAK,gBACH,KAAK,iBAAmB,EAAmB,EAAM,OAAO,aAAc,EAAQ,CAE5E,KAAK,kBAAoB,SAK7B,KAAK,qBACH,KAAK,sBACL,EACE,KAAK,oBAAoB,aACzB,KAAK,gBACL,KAAK,SAAS,aACf,CAID,EACE,KAAK,oBAAoB,aACzB,KAAK,qBACL,EAAM,OAAO,aACd,EAED,KAAK,aAAa,EAAO,CACvB,KAAM,SACN,QAAS,KAAK,gBACf,CAAC,GASN,sBAAoC,CAEhC,CAAC,KAAK,iBACN,KAAK,kBAAoB,QACzB,CAAC,KAAK,sBACN,CAAC,KAAK,aAAa,yBAAyB,GAK9C,KAAK,sBAAsB,eAAiB,KAAK,qBACjD,KAAK,sBAAsB,gBAAkB,KAAK,gBAClD,KAAK,KAAK,KAAK,sBAAsB,EAGvC,WAAsB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./BaseForesightModule-CJzWVlF2.mjs";import{tabbable as t}from"tabbable";const n=(e,t,n,r)=>{if(t!==null&&t>-1){let i=e?t-1:t+1;if(i>=0&&i<n.length&&n[i]===r)return i}return n.findIndex(e=>e===r)};var r=class extends e{constructor(e){super(e),this.moduleName=`TabPredictor`,this.lastKeyDown=null,this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.handleKeyDown=e=>{e.key===`Tab`&&(this.lastKeyDown=e)},this.handleFocusIn=e=>{if(!this.lastKeyDown||!(e.target instanceof HTMLElement))return;let t=this.lastKeyDown.shiftKey;this.lastKeyDown=null,this.ensureTabbableCache();let r=n(t,this.lastFocusedIndex,this.tabbableElementsCache,e.target);this.lastFocusedIndex=r,this.predictUpcomingTabs(r,t)}}invalidateCache(){this.tabbableElementsCache.length&&this.devLog(`Invalidating tabbable elements cache`),this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){typeof document>`u`||(this.createAbortController(),document.addEventListener(`keydown`,this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener(`focusin`,this.handleFocusIn,{signal:this.abortController?.signal,passive:!0}))}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}ensureTabbableCache(){this.tabbableElementsCache.length&&this.lastFocusedIndex!==-1||(this.devLog(`Caching tabbable elements`),this.tabbableElementsCache=t(document.documentElement))}predictUpcomingTabs(e,t){let n=this.settings.tabOffset,r=t?`reverse`:`forwards`;for(let i=0;i<=n;i++){let n=t?e-i:e+i,a=this.tabbableElementsCache[n];if(!(a instanceof Element))continue;let o=this.elements.get(a);o&&!o.state.isPredicted&&o.state.isActive&&this.callCallback(o,{kind:`tab`,subType:r})}}};export{r as TabPredictor};
|
|
2
|
+
//# sourceMappingURL=TabPredictor-C2zyaEQZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TabPredictor-C2zyaEQZ.mjs","names":[],"sources":["../src/helpers/getFocusedElementIndex.ts","../src/predictors/TabPredictor.ts"],"sourcesContent":["import type { FocusableElement } from \"tabbable\"\n\n/**\n * Finds the index of a focused element within a cache of tabbable elements.\n * It uses a predictive search for O(1) performance in the common case of\n * sequential tabbing, and falls back to a linear search O(n) if the\n * prediction fails.\n *\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\n * @param tabbableElementsCache - The array of all tabbable elements.\n * @param targetElement - The new HTML element that has received focus.\n * @returns The index of the targetElement in the cache, or -1 if not found.\n */\nexport const getFocusedElementIndex = (\n isReversed: boolean,\n lastFocusedIndex: number | null,\n tabbableElementsCache: FocusableElement[],\n targetElement: HTMLElement\n): number => {\n // First, try to predict the next index based on the last known position.\n if (lastFocusedIndex !== null && lastFocusedIndex > -1) {\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\n\n // Check if the prediction is valid and correct.\n if (\n predictedIndex >= 0 &&\n predictedIndex < tabbableElementsCache.length &&\n tabbableElementsCache[predictedIndex] === targetElement\n ) {\n return predictedIndex\n }\n }\n\n return tabbableElementsCache.findIndex(element => element === targetElement)\n}\n","import { tabbable, type FocusableElement } from \"tabbable\"\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\nimport { BaseForesightModule, type ForesightModuleDependencies } from \"../core/BaseForesightModule\"\n\n/**\n * Manages the prediction of user intent based on Tab key navigation.\n *\n * This class is a specialist module controlled by the ForesightManager.\n * Its responsibilities are:\n * - Listening for `keydown` and `focusin` events to detect tabbing.\n * - Caching the list of tabbable elements on the page for performance.\n * - Invalidating the cache when the DOM changes.\n * - Predicting which registered elements the user is about to focus.\n * - Calling a provided callback when a prediction is made.\n */\nexport class TabPredictor extends BaseForesightModule {\n protected readonly moduleName = \"TabPredictor\"\n\n // Internal state for tab prediction\n private lastKeyDown: KeyboardEvent | null = null\n private tabbableElementsCache: FocusableElement[] = []\n private lastFocusedIndex: number | null = null\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n public invalidateCache() {\n if (this.tabbableElementsCache.length) {\n this.devLog(\"Invalidating tabbable elements cache\")\n }\n\n this.tabbableElementsCache = []\n this.lastFocusedIndex = null\n }\n\n protected onConnect(): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n this.createAbortController()\n\n document.addEventListener(\"keydown\", this.handleKeyDown, {\n signal: this.abortController?.signal,\n passive: true,\n })\n\n document.addEventListener(\"focusin\", this.handleFocusIn, {\n signal: this.abortController?.signal,\n passive: true,\n })\n }\n\n protected onDisconnect(): void {\n this.tabbableElementsCache = []\n this.lastFocusedIndex = null\n this.lastKeyDown = null\n }\n\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\n // We dont use handleKeyDown for the full event because of 2 main reasons:\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\n private handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Tab\") {\n this.lastKeyDown = e\n }\n }\n\n private handleFocusIn = (e: FocusEvent) => {\n if (!this.lastKeyDown || !(e.target instanceof HTMLElement)) {\n return\n }\n\n const isReversed = this.lastKeyDown.shiftKey\n this.lastKeyDown = null\n\n this.ensureTabbableCache()\n\n const currentIndex = getFocusedElementIndex(\n isReversed,\n this.lastFocusedIndex,\n this.tabbableElementsCache,\n e.target\n )\n this.lastFocusedIndex = currentIndex\n\n this.predictUpcomingTabs(currentIndex, isReversed)\n }\n\n private ensureTabbableCache(): void {\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\n if (this.tabbableElementsCache.length && this.lastFocusedIndex !== -1) {\n return\n }\n\n this.devLog(\"Caching tabbable elements\")\n this.tabbableElementsCache = tabbable(document.documentElement)\n }\n\n private predictUpcomingTabs(currentIndex: number, isReversed: boolean): void {\n const tabOffset = this.settings.tabOffset\n const subType = isReversed ? \"reverse\" : \"forwards\"\n\n for (let i = 0; i <= tabOffset; i++) {\n const elementIndex = isReversed ? currentIndex - i : currentIndex + i\n const element = this.tabbableElementsCache[elementIndex]\n\n if (!(element instanceof Element)) {\n continue\n }\n\n const entry = this.elements.get(element)\n if (entry && !entry.state.isPredicted && entry.state.isActive) {\n this.callCallback(entry, { kind: \"tab\", subType })\n }\n }\n }\n}\n"],"mappings":"2FAcA,MAAa,GACX,EACA,EACA,EACA,IACW,CAEX,GAAI,IAAqB,MAAQ,EAAmB,GAAI,CACtD,IAAM,EAAiB,EAAa,EAAmB,EAAI,EAAmB,EAG9E,GACE,GAAkB,GAClB,EAAiB,EAAsB,QACvC,EAAsB,KAAoB,EAE1C,OAAO,EAIX,OAAO,EAAsB,UAAU,GAAW,IAAY,EAAc,ECnB9E,IAAa,EAAb,cAAkC,CAAoB,CAQpD,YAAY,EAA2C,CACrD,MAAM,EAAa,iBARW,gCAGY,gCACQ,EAAE,uBACZ,wBA2CjB,GAAqB,CACxC,EAAE,MAAQ,QACZ,KAAK,YAAc,uBAIE,GAAkB,CACzC,GAAI,CAAC,KAAK,aAAe,EAAE,EAAE,kBAAkB,aAC7C,OAGF,IAAM,EAAa,KAAK,YAAY,SACpC,KAAK,YAAc,KAEnB,KAAK,qBAAqB,CAE1B,IAAM,EAAe,EACnB,EACA,KAAK,iBACL,KAAK,sBACL,EAAE,OACH,CACD,KAAK,iBAAmB,EAExB,KAAK,oBAAoB,EAAc,EAAW,EA7DpD,iBAAyB,CACnB,KAAK,sBAAsB,QAC7B,KAAK,OAAO,uCAAuC,CAGrD,KAAK,sBAAwB,EAAE,CAC/B,KAAK,iBAAmB,KAG1B,WAA4B,CACtB,OAAO,SAAa,MAIxB,KAAK,uBAAuB,CAE5B,SAAS,iBAAiB,UAAW,KAAK,cAAe,CACvD,OAAQ,KAAK,iBAAiB,OAC9B,QAAS,GACV,CAAC,CAEF,SAAS,iBAAiB,UAAW,KAAK,cAAe,CACvD,OAAQ,KAAK,iBAAiB,OAC9B,QAAS,GACV,CAAC,EAGJ,cAA+B,CAC7B,KAAK,sBAAwB,EAAE,CAC/B,KAAK,iBAAmB,KACxB,KAAK,YAAc,KAkCrB,qBAAoC,CAE9B,KAAK,sBAAsB,QAAU,KAAK,mBAAqB,KAInE,KAAK,OAAO,4BAA4B,CACxC,KAAK,sBAAwB,EAAS,SAAS,gBAAgB,EAGjE,oBAA4B,EAAsB,EAA2B,CAC3E,IAAM,EAAY,KAAK,SAAS,UAC1B,EAAU,EAAa,UAAY,WAEzC,IAAK,IAAI,EAAI,EAAG,GAAK,EAAW,IAAK,CACnC,IAAM,EAAe,EAAa,EAAe,EAAI,EAAe,EAC9D,EAAU,KAAK,sBAAsB,GAE3C,GAAI,EAAE,aAAmB,SACvB,SAGF,IAAM,EAAQ,KAAK,SAAS,IAAI,EAAQ,CACpC,GAAS,CAAC,EAAM,MAAM,aAAe,EAAM,MAAM,UACnD,KAAK,aAAa,EAAO,CAAE,KAAM,MAAO,UAAS,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./ElementObservingModule-QE8ZBcnz.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`TouchDeviceHandler`,this.viewportPredictor=null,this.touchStartPredictor=null,this.predictor=null,this.onDisconnect=()=>{this.devLog(`Disconnecting touch predictor`),this.predictor?.disconnect()},this.onConnect=()=>this.setTouchPredictor(),this.storedDependencies=e}async getOrCreateViewportPredictor(){if(!this.viewportPredictor){let{ViewportPredictor:e}=await import(`./ViewportPredictor-aTE2EmU2.mjs`);this.viewportPredictor=new e(this.storedDependencies),this.devLog(`ViewportPredictor lazy loaded`)}return this.viewportPredictor}async getOrCreateTouchStartPredictor(){if(!this.touchStartPredictor){let{TouchStartPredictor:e}=await import(`./TouchStartPredictor-r7R_E74t.mjs`);this.touchStartPredictor=new e(this.storedDependencies),this.devLog(`TouchStartPredictor lazy loaded`)}return this.touchStartPredictor}async setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case`viewport`:this.predictor=await this.getOrCreateViewportPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`);break;case`onTouchStart`:this.predictor=await this.getOrCreateTouchStartPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`);break;case`none`:this.predictor=null,this.devLog(`Touch strategy set to "none" - no predictor connected`);return;default:this.settings.touchDeviceStrategy}this.predictor?.connect();for(let e of this.elements.keys())this.predictor?.observeElement(e)}observeElement(e){this.predictor?.observeElement(e)}unobserveElement(e){this.predictor?.unobserveElement(e)}get loadedPredictors(){return{viewport:this.viewportPredictor!==null,touchStart:this.touchStartPredictor!==null}}};export{t as TouchDeviceHandler};
|
|
2
|
+
//# sourceMappingURL=TouchDeviceHandler-BPzPdr-z.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TouchDeviceHandler-BPzPdr-z.mjs","names":[],"sources":["../src/managers/TouchDeviceHandler.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport type { ViewportPredictor } from \"../predictors/ViewportPredictor\"\nimport type { TouchStartPredictor } from \"../predictors/TouchStartPredictor\"\n\nexport class TouchDeviceHandler extends ElementObservingModule {\n protected readonly moduleName = \"TouchDeviceHandler\"\n\n private viewportPredictor: ViewportPredictor | null = null\n private touchStartPredictor: TouchStartPredictor | null = null\n private predictor: ElementObservingModule | null = null\n private storedDependencies: ForesightModuleDependencies\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n this.storedDependencies = dependencies\n }\n\n private async getOrCreateViewportPredictor(): Promise<ViewportPredictor> {\n if (!this.viewportPredictor) {\n const { ViewportPredictor } = await import(\"../predictors/ViewportPredictor\")\n this.viewportPredictor = new ViewportPredictor(this.storedDependencies)\n this.devLog(\"ViewportPredictor lazy loaded\")\n }\n\n return this.viewportPredictor\n }\n\n private async getOrCreateTouchStartPredictor(): Promise<TouchStartPredictor> {\n if (!this.touchStartPredictor) {\n const { TouchStartPredictor } = await import(\"../predictors/TouchStartPredictor\")\n this.touchStartPredictor = new TouchStartPredictor(this.storedDependencies)\n this.devLog(\"TouchStartPredictor lazy loaded\")\n }\n\n return this.touchStartPredictor\n }\n\n public async setTouchPredictor() {\n this.predictor?.disconnect()\n\n switch (this.settings.touchDeviceStrategy) {\n case \"viewport\":\n this.predictor = await this.getOrCreateViewportPredictor()\n this.devLog(\n `Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`\n )\n break\n case \"onTouchStart\":\n this.predictor = await this.getOrCreateTouchStartPredictor()\n this.devLog(\n `Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`\n )\n break\n case \"none\":\n this.predictor = null\n this.devLog(`Touch strategy set to \"none\" - no predictor connected`)\n\n return\n default:\n this.settings.touchDeviceStrategy satisfies never\n }\n\n this.predictor?.connect()\n\n for (const element of this.elements.keys()) {\n this.predictor?.observeElement(element)\n }\n }\n\n protected onDisconnect = () => {\n this.devLog(\"Disconnecting touch predictor\")\n this.predictor?.disconnect()\n }\n protected onConnect = () => this.setTouchPredictor()\n\n public observeElement(element: ForesightElement): void {\n this.predictor?.observeElement(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.predictor?.unobserveElement(element)\n }\n\n /** For debugging: returns which predictors have been lazy loaded */\n public get loadedPredictors() {\n return {\n viewport: this.viewportPredictor !== null,\n touchStart: this.touchStartPredictor !== null,\n }\n }\n}\n"],"mappings":"0DAMA,IAAa,EAAb,cAAwC,CAAuB,CAQ7D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBARW,4CAEsB,8BACI,oBACP,2BA4DpB,CAC7B,KAAK,OAAO,gCAAgC,CAC5C,KAAK,WAAW,YAAY,qBAEF,KAAK,mBAAmB,CA3DlD,KAAK,mBAAqB,EAG5B,MAAc,8BAA2D,CACvE,GAAI,CAAC,KAAK,kBAAmB,CAC3B,GAAM,CAAE,qBAAsB,MAAM,OAAO,oCAC3C,KAAK,kBAAoB,IAAI,EAAkB,KAAK,mBAAmB,CACvE,KAAK,OAAO,gCAAgC,CAG9C,OAAO,KAAK,kBAGd,MAAc,gCAA+D,CAC3E,GAAI,CAAC,KAAK,oBAAqB,CAC7B,GAAM,CAAE,uBAAwB,MAAM,OAAO,sCAC7C,KAAK,oBAAsB,IAAI,EAAoB,KAAK,mBAAmB,CAC3E,KAAK,OAAO,kCAAkC,CAGhD,OAAO,KAAK,oBAGd,MAAa,mBAAoB,CAG/B,OAFA,KAAK,WAAW,YAAY,CAEpB,KAAK,SAAS,oBAAtB,CACE,IAAK,WACH,KAAK,UAAY,MAAM,KAAK,8BAA8B,CAC1D,KAAK,OACH,6BAA6B,KAAK,SAAS,oBAAoB,sBAChE,CACD,MACF,IAAK,eACH,KAAK,UAAY,MAAM,KAAK,gCAAgC,CAC5D,KAAK,OACH,6BAA6B,KAAK,SAAS,oBAAoB,wBAChE,CACD,MACF,IAAK,OACH,KAAK,UAAY,KACjB,KAAK,OAAO,wDAAwD,CAEpE,OACF,QACE,KAAK,SAAS,oBAGlB,KAAK,WAAW,SAAS,CAEzB,IAAK,IAAM,KAAW,KAAK,SAAS,MAAM,CACxC,KAAK,WAAW,eAAe,EAAQ,CAU3C,eAAsB,EAAiC,CACrD,KAAK,WAAW,eAAe,EAAQ,CAGzC,iBAAwB,EAAiC,CACvD,KAAK,WAAW,iBAAiB,EAAQ,CAI3C,IAAW,kBAAmB,CAC5B,MAAO,CACL,SAAU,KAAK,oBAAsB,KACrC,WAAY,KAAK,sBAAwB,KAC1C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./ElementObservingModule-QE8ZBcnz.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`TouchStartPredictor`,this.onConnect=()=>this.createAbortController(),this.onDisconnect=()=>{},this.handleTouchStart=e=>{let t=e.currentTarget,n=this.elements.get(t);n&&(this.callCallback(n,{kind:`touch`}),this.unobserveElement(t))}}observeElement(e){e instanceof HTMLElement&&e.addEventListener(`pointerdown`,this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(e){e instanceof HTMLElement&&e.removeEventListener(`pointerdown`,this.handleTouchStart)}};export{t as TouchStartPredictor};
|
|
2
|
+
//# sourceMappingURL=TouchStartPredictor-r7R_E74t.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TouchStartPredictor-r7R_E74t.mjs","names":[],"sources":["../src/predictors/TouchStartPredictor.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\n\nexport class TouchStartPredictor extends ElementObservingModule {\n protected readonly moduleName = \"TouchStartPredictor\"\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n protected onConnect = () => this.createAbortController()\n protected onDisconnect = () => {}\n\n // Change to touchstart ones it is baseline https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event\n public observeElement(element: ForesightElement): void {\n if (element instanceof HTMLElement) {\n element.addEventListener(\"pointerdown\", this.handleTouchStart, {\n signal: this.abortController?.signal,\n })\n }\n }\n\n // Change to touchstart ones it is baseline https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event\n public unobserveElement(element: ForesightElement): void {\n if (element instanceof HTMLElement) {\n element.removeEventListener(\"pointerdown\", this.handleTouchStart)\n }\n }\n\n protected handleTouchStart = (e: PointerEvent): void => {\n const currentTarget = e.currentTarget as ForesightElement\n const data = this.elements.get(currentTarget)\n\n if (data) {\n this.callCallback(data, {\n kind: \"touch\",\n })\n this.unobserveElement(currentTarget)\n }\n }\n}\n"],"mappings":"0DAIA,IAAa,EAAb,cAAyC,CAAuB,CAG9D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBAHW,yCAMJ,KAAK,uBAAuB,uBACzB,yBAkBD,GAA0B,CACtD,IAAM,EAAgB,EAAE,cAClB,EAAO,KAAK,SAAS,IAAI,EAAc,CAEzC,IACF,KAAK,aAAa,EAAM,CACtB,KAAM,QACP,CAAC,CACF,KAAK,iBAAiB,EAAc,GAvBxC,eAAsB,EAAiC,CACjD,aAAmB,aACrB,EAAQ,iBAAiB,cAAe,KAAK,iBAAkB,CAC7D,OAAQ,KAAK,iBAAiB,OAC/B,CAAC,CAKN,iBAAwB,EAAiC,CACnD,aAAmB,aACrB,EAAQ,oBAAoB,cAAe,KAAK,iBAAiB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./ElementObservingModule-QE8ZBcnz.mjs";var t=class extends e{constructor(e){super(e),this.moduleName=`ViewportPredictor`,this.intersectionObserver=null,this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter),this.handleViewportEnter=e=>{for(let t of e){if(!t.isIntersecting)continue;let e=this.elements.get(t.target);e&&(this.callCallback(e,{kind:`viewport`}),this.unobserveElement(t.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}observeElement(e){this.intersectionObserver?.observe(e)}unobserveElement(e){this.intersectionObserver?.unobserve(e)}};export{t as ViewportPredictor};
|
|
2
|
+
//# sourceMappingURL=ViewportPredictor-aTE2EmU2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewportPredictor-aTE2EmU2.mjs","names":[],"sources":["../src/predictors/ViewportPredictor.ts"],"sourcesContent":["import type { ForesightElement } from \"../types/types\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport { ElementObservingModule } from \"../core/ElementObservingModule\"\n\nexport class ViewportPredictor extends ElementObservingModule {\n protected readonly moduleName = \"ViewportPredictor\"\n\n private intersectionObserver: IntersectionObserver | null = null\n\n constructor(dependencies: ForesightModuleDependencies) {\n super(dependencies)\n }\n\n protected onConnect = () =>\n (this.intersectionObserver = new IntersectionObserver(this.handleViewportEnter))\n\n protected onDisconnect() {\n this.intersectionObserver?.disconnect()\n this.intersectionObserver = null\n }\n\n public observeElement(element: ForesightElement): void {\n this.intersectionObserver?.observe(element)\n }\n\n public unobserveElement(element: ForesightElement): void {\n this.intersectionObserver?.unobserve(element)\n }\n\n protected handleViewportEnter = (entries: IntersectionObserverEntry[]) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n continue\n }\n\n const data = this.elements.get(entry.target as ForesightElement)\n\n if (!data) {\n continue\n }\n\n this.callCallback(data, {\n kind: \"viewport\",\n })\n\n this.unobserveElement(entry.target as ForesightElement)\n }\n }\n}\n"],"mappings":"0DAIA,IAAa,EAAb,cAAuC,CAAuB,CAK5D,YAAY,EAA2C,CACrD,MAAM,EAAa,iBALW,8CAE4B,wBAOzD,KAAK,qBAAuB,IAAI,qBAAqB,KAAK,oBAAoB,0BAehD,GAAyC,CACxE,IAAK,IAAM,KAAS,EAAS,CAC3B,GAAI,CAAC,EAAM,eACT,SAGF,IAAM,EAAO,KAAK,SAAS,IAAI,EAAM,OAA2B,CAE3D,IAIL,KAAK,aAAa,EAAM,CACtB,KAAM,WACP,CAAC,CAEF,KAAK,iBAAiB,EAAM,OAA2B,IA7B3D,cAAyB,CACvB,KAAK,sBAAsB,YAAY,CACvC,KAAK,qBAAuB,KAG9B,eAAsB,EAAiC,CACrD,KAAK,sBAAsB,QAAQ,EAAQ,CAG7C,iBAAwB,EAAiC,CACvD,KAAK,sBAAsB,UAAU,EAAQ"}
|