js.foresight 3.4.0 → 3.5.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/README.md +100 -100
- package/dist/index.d.ts +23 -13
- package/dist/index.js +1 -1
- package/package.json +75 -75
package/README.md
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
# [ForesightJS](https://foresightjs.com/)
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/js.foresight)
|
|
4
|
-
[](https://www.npmjs.com/package/js.foresight)
|
|
5
|
-
[](https://bundlephobia.com/package/js.foresight)
|
|
6
|
-
[](https://github.com/spaansba/ForesightJS/commits)
|
|
7
|
-
|
|
8
|
-
[](https://github.com/spaansba/ForesightJS)
|
|
9
|
-
[](https://bestofjs.org/projects/foresightjs)
|
|
10
|
-
|
|
11
|
-
[](http://www.typescriptlang.org/)
|
|
12
|
-
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
[](https://foresightjs.com#playground)
|
|
14
|
-
|
|
15
|
-
ForesightJS is a lightweight JavaScript library that predicts user intent to prefetch content before it's needed. **It works completely out of the box without configuration**, supporting both desktop and mobile devices with different prediction strategies.
|
|
16
|
-
|
|
17
|
-
### [Playground](https://foresightjs.com/)
|
|
18
|
-
|
|
19
|
-

|
|
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
|
-
|
|
22
|
-
## Download
|
|
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
|
|
33
|
-
|
|
34
|
-
This basic example is in vanilla JS, ofcourse most people will use ForesightJS with a framework. You can read about framework integrations in the [docs](https://foresightjs.com/docs/integrations/react/useForesight).
|
|
35
|
-
|
|
36
|
-
```javascript
|
|
37
|
-
import { ForesightManager } from "js.
|
|
38
|
-
|
|
39
|
-
// Initialize the manager if you want custom global settings (do this once at app startup)
|
|
40
|
-
ForesightManager.initialize({
|
|
41
|
-
// Optional props (see configuration)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Register
|
|
45
|
-
const myButton = document.getElementById("my-button")
|
|
46
|
-
|
|
47
|
-
ForesightManager.instance.register({
|
|
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
|
|
58
|
-
|
|
59
|
-
Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. Ready-to-use implementations are available for:
|
|
60
|
-
|
|
61
|
-
- **React**: [Next.js](https://foresightjs.com/docs/react/nextjs), [React Router](https://foresightjs.com/docs/react/react-router), and [useForesight hook](https://foresightjs.com/docs/react/hook)
|
|
62
|
-
- **Vue**: [Directive](https://foresightjs.com/docs/vue/directive) and [Composable](https://foresightjs.com/docs/vue/composable)
|
|
63
|
-
|
|
64
|
-
Sharing integrations for other frameworks/packages is highly appreciated!
|
|
65
|
-
|
|
66
|
-
## Configuration
|
|
67
|
-
|
|
68
|
-
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/element-settings) if needed.
|
|
69
|
-
|
|
70
|
-
## Development Tools
|
|
71
|
-
|
|
72
|
-
ForesightJS has dedicated [Development Tools](https://github.com/spaansba/ForesightJS/tree/main/packages/js.foresight-devtools) created with [Foresight Events](https://foresightjs.com/docs/events) that help you understand and tune how foresight is working in your application. This standalone development package provides real-time visualization of mouse trajectory predictions, element bounds, and callback execution.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
pnpm add js.foresight-devtools
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
See the [development tools documentation](https://foresightjs.com/docs/debugging/devtools) for more details.
|
|
79
|
-
|
|
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
|
-
## Providing Context to AI Tools
|
|
89
|
-
|
|
90
|
-
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
|
-
|
|
92
|
-
- Use [llms.txt](https://foresightjs.com/llms.txt) for a concise overview of the API and usage patterns.
|
|
93
|
-
- 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/getting_started.md.
|
|
95
|
-
|
|
96
|
-
[Read more](https://foresightjs.com/docs/ai-context)
|
|
97
|
-
|
|
98
|
-
# Contributing
|
|
99
|
-
|
|
100
|
-
Please see the [contributing guidelines](/CONTRIBUTING.md)
|
|
1
|
+
# [ForesightJS](https://foresightjs.com/)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/js.foresight)
|
|
4
|
+
[](https://www.npmjs.com/package/js.foresight)
|
|
5
|
+
[](https://bundlephobia.com/package/js.foresight)
|
|
6
|
+
[](https://github.com/spaansba/ForesightJS/commits)
|
|
7
|
+
|
|
8
|
+
[](https://github.com/spaansba/ForesightJS)
|
|
9
|
+
[](https://bestofjs.org/projects/foresightjs)
|
|
10
|
+
|
|
11
|
+
[](http://www.typescriptlang.org/)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
[](https://foresightjs.com#playground)
|
|
14
|
+
|
|
15
|
+
ForesightJS is a lightweight JavaScript library that predicts user intent to prefetch content before it's needed. **It works completely out of the box without configuration**, supporting both desktop and mobile devices with different prediction strategies.
|
|
16
|
+
|
|
17
|
+
### [Playground](https://foresightjs.com/)
|
|
18
|
+
|
|
19
|
+

|
|
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
|
+
|
|
22
|
+
## Download
|
|
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
|
|
33
|
+
|
|
34
|
+
This basic example is in vanilla JS, ofcourse most people will use ForesightJS with a framework. You can read about framework integrations in the [docs](https://foresightjs.com/docs/integrations/react/useForesight).
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { ForesightManager } from "js.foresight"
|
|
38
|
+
|
|
39
|
+
// Initialize the manager if you want custom global settings (do this once at app startup)
|
|
40
|
+
ForesightManager.initialize({
|
|
41
|
+
// Optional props (see configuration)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Register a single element (or NodeList)
|
|
45
|
+
const myButton = document.getElementById("my-button")
|
|
46
|
+
|
|
47
|
+
ForesightManager.instance.register({
|
|
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
|
|
58
|
+
|
|
59
|
+
Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework. Ready-to-use implementations are available for:
|
|
60
|
+
|
|
61
|
+
- **React**: [Next.js](https://foresightjs.com/docs/react/nextjs), [React Router](https://foresightjs.com/docs/react/react-router), and [useForesight hook](https://foresightjs.com/docs/react/hook)
|
|
62
|
+
- **Vue**: [Directive](https://foresightjs.com/docs/vue/directive) and [Composable](https://foresightjs.com/docs/vue/composable)
|
|
63
|
+
|
|
64
|
+
Sharing integrations for other frameworks/packages is highly appreciated!
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
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/element-settings) if needed.
|
|
69
|
+
|
|
70
|
+
## Development Tools
|
|
71
|
+
|
|
72
|
+
ForesightJS has dedicated [Development Tools](https://github.com/spaansba/ForesightJS/tree/main/packages/js.foresight-devtools) created with [Foresight Events](https://foresightjs.com/docs/events) that help you understand and tune how foresight is working in your application. This standalone development package provides real-time visualization of mouse trajectory predictions, element bounds, and callback execution.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pnpm add js.foresight-devtools
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
See the [development tools documentation](https://foresightjs.com/docs/debugging/devtools) for more details.
|
|
79
|
+
|
|
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
|
+
## Providing Context to AI Tools
|
|
89
|
+
|
|
90
|
+
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
|
+
|
|
92
|
+
- Use [llms.txt](https://foresightjs.com/llms.txt) for a concise overview of the API and usage patterns.
|
|
93
|
+
- 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/getting_started.md.
|
|
95
|
+
|
|
96
|
+
[Read more](https://foresightjs.com/docs/ai-context)
|
|
97
|
+
|
|
98
|
+
# Contributing
|
|
99
|
+
|
|
100
|
+
Please see the [contributing guidelines](/CONTRIBUTING.md)
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ type Rect = {
|
|
|
27
27
|
* A callback function that is executed when a foresight interaction
|
|
28
28
|
* (e.g., hover, trajectory hit) occurs on a registered element.
|
|
29
29
|
*/
|
|
30
|
-
type ForesightCallback = () => void;
|
|
30
|
+
type ForesightCallback = (element: ForesightElementData) => void;
|
|
31
31
|
/**
|
|
32
32
|
* Represents the HTML element that is being tracked by the ForesightManager.
|
|
33
33
|
* This is typically a standard DOM `Element`.
|
|
@@ -77,6 +77,8 @@ type ForesightRegisterResult = {
|
|
|
77
77
|
* @deprecated no longer need to call this manually, you can call Foresightmanager.instance.unregister if needed
|
|
78
78
|
*/
|
|
79
79
|
unregister: () => void;
|
|
80
|
+
/** The data associated with the registered element. This will be null if the element was not registered */
|
|
81
|
+
elementData: ForesightElementData | null;
|
|
80
82
|
};
|
|
81
83
|
/**
|
|
82
84
|
* Represents the data associated with a registered foresight element.
|
|
@@ -316,8 +318,20 @@ type UpdateForsightManagerSettings = BaseForesightManagerSettings & {
|
|
|
316
318
|
/**
|
|
317
319
|
* Type used to register elements to the foresight manager
|
|
318
320
|
*/
|
|
319
|
-
type ForesightRegisterOptions = {
|
|
321
|
+
type ForesightRegisterOptions = ForesightRegisterOptionsWithoutElement & {
|
|
320
322
|
element: ForesightElement;
|
|
323
|
+
};
|
|
324
|
+
type ForesightRegisterNodeListOptions = ForesightRegisterOptionsWithoutElement & {
|
|
325
|
+
element: NodeListOf<ForesightElement>;
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
328
|
+
* Use full for if you want to create a custom button component in a modern framework (for example React).
|
|
329
|
+
* And you want to have the ForesightRegisterOptions used in ForesightManager.instance.register({})
|
|
330
|
+
* without the element as the element will be the ref of the component.
|
|
331
|
+
*
|
|
332
|
+
* @link https://foresightjs.com/docs/getting_started/typescript#foresightregisteroptionswithoutelement
|
|
333
|
+
*/
|
|
334
|
+
type ForesightRegisterOptionsWithoutElement = {
|
|
321
335
|
callback: ForesightCallback;
|
|
322
336
|
hitSlop?: HitSlop;
|
|
323
337
|
/**
|
|
@@ -336,14 +350,6 @@ type ForesightRegisterOptions = {
|
|
|
336
350
|
*/
|
|
337
351
|
reactivateAfter?: number;
|
|
338
352
|
};
|
|
339
|
-
/**
|
|
340
|
-
* Usefull for if you want to create a custom button component in a modern framework (for example React).
|
|
341
|
-
* And you want to have the ForesightRegisterOptions used in ForesightManager.instance.register({})
|
|
342
|
-
* without the element as the element will be the ref of the component.
|
|
343
|
-
*
|
|
344
|
-
* @link https://foresightjs.com/docs/getting_started/typescript#foresightregisteroptionswithoutelement
|
|
345
|
-
*/
|
|
346
|
-
type ForesightRegisterOptionsWithoutElement = Omit<ForesightRegisterOptions, "element">;
|
|
347
353
|
/**
|
|
348
354
|
* Fully invisible "slop" around the element.
|
|
349
355
|
* Basically increases the hover hitbox
|
|
@@ -491,9 +497,13 @@ declare class ForesightManager {
|
|
|
491
497
|
hasListeners<K extends ForesightEvent>(eventType: K): boolean;
|
|
492
498
|
get getManagerData(): Readonly<ForesightManagerData>;
|
|
493
499
|
get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData>;
|
|
500
|
+
register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[];
|
|
494
501
|
register(options: ForesightRegisterOptions): ForesightRegisterResult;
|
|
495
|
-
|
|
496
|
-
|
|
502
|
+
private registerElement;
|
|
503
|
+
unregister(element: ForesightElement | NodeListOf<ForesightElement>, unregisterReason?: ElementUnregisteredReason): void;
|
|
504
|
+
private unregisterElement;
|
|
505
|
+
reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void;
|
|
506
|
+
private reactivateElement;
|
|
497
507
|
private clearReactivateTimeout;
|
|
498
508
|
updateCheckableStatus(elementData: ForesightElementData): void;
|
|
499
509
|
private callCallback;
|
|
@@ -518,4 +528,4 @@ declare class ForesightManager {
|
|
|
518
528
|
|
|
519
529
|
type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean;
|
|
520
530
|
|
|
521
|
-
export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementCallbackInfo, type ElementDataUpdatedEvent, type ElementReactivatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HasListenersFunction, type HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
|
|
531
|
+
export { type CallbackCompletedEvent, type CallbackHitType, type CallbackHits, type CallbackInvokedEvent, type DeviceStrategyChangedEvent, type ElementCallbackInfo, type ElementDataUpdatedEvent, type ElementReactivatedEvent, type ElementRegisteredEvent, type ElementUnregisteredEvent, type ForesightCallback, type ForesightElement, type ForesightElementData, type ForesightEvent, ForesightManager, type ForesightManagerSettings, type Rect as ForesightRect, type ForesightRegisterNodeListOptions, type ForesightRegisterOptions, type ForesightRegisterOptionsWithoutElement, type ForesightRegisterResult, type HasListenersFunction, type HitSlop, type ManagerSettingsChangedEvent, type MinimumConnectionType, type MouseTrajectoryUpdateEvent, type ScrollTrajectoryUpdateEvent, type TouchDeviceStrategy, type UpdateForsightManagerSettings, type UpdatedManagerSetting };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as C,b as u,c as v,d as p}from"./chunk-PAYO6NXN.js";function k(){let i=S(),e=I();return{isTouchDevice:i,isLimitedConnection:e,shouldRegister:!e}}function S(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function I(){let i=navigator.connection;if(!i)return!1;let e=g.instance.getManagerData.globalSettings.minimumConnectionType,t=["slow-2g","2g","3g","4g"],n=t.indexOf(i.effectiveType),a=t.indexOf(e);return n<a||i.saveData}function T(i){if(typeof window>"u"||typeof document>"u")return!1;let e=window.innerWidth||document.documentElement.clientWidth,t=window.innerHeight||document.documentElement.clientHeight;return i.top<t&&i.bottom>0&&i.left<e&&i.right>0}function H(){return{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}}function F(){return{debug:!1,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"}}function M(i,e,t){let{element:n,callback:a,hitSlop:s,name:o,meta:c,reactivateAfter:b}=i,d=n.getBoundingClientRect(),r=s?u(s):t;return{id:e,element:n,callback:a,elementBounds:{originalRect:d,expandedRect:v(d,r),hitSlop:r},name:o||n.id||"unnamed",isIntersectingWithViewport:T(d),registerCount:1,meta:c??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:b??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}}}var f=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,n){if(n?.signal?.aborted)return;let a=this.eventListeners.get(e)??[];a.push(t),this.eventListeners.set(e,a),n?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let n=this.eventListeners.get(e);if(!n)return;let a=n.indexOf(t);a>-1&&n.splice(a,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 a=t[n];a&&a(e)}catch(a){console.error(`Error in ForesightManager event listener ${n} for ${e.type}:`,a)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};function y(i,e){return i!==void 0&&e!==i}var L={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}};function h(i,e,t){if(!y(t,i[e]))return!1;let{min:n,max:a}=L[e];return i[e]=C(t,n,a,e),!0}function m(i,e,t){return y(t,i[e])?(i[e]=t,!0):!1}function D(i,e){h(i,"trajectoryPredictionTime",e.trajectoryPredictionTime),h(i,"positionHistorySize",e.positionHistorySize),h(i,"scrollMargin",e.scrollMargin),h(i,"tabOffset",e.tabOffset),m(i,"enableMousePrediction",e.enableMousePrediction),m(i,"enableScrollPrediction",e.enableScrollPrediction),m(i,"enableTabPrediction",e.enableTabPrediction),m(i,"enableManagerLogging",e.enableManagerLogging),e.defaultHitSlop!==void 0&&(i.defaultHitSlop=u(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(i.touchDeviceStrategy=e.touchDeviceStrategy),e.minimumConnectionType!==void 0&&(i.minimumConnectionType=e.minimumConnectionType),e.debug!==void 0&&(i.debug=e.debug)}function R(i,e){let t=[],n=!1,a=!1,s=!1,o=!1,c=!1;if(!e)return{changedSettings:t,positionHistorySizeChanged:n,scrollPredictionChanged:a,tabPredictionChanged:s,hitSlopChanged:o,touchStrategyChanged:c};let b=["trajectoryPredictionTime","positionHistorySize","scrollMargin","tabOffset"];for(let r of b){let l=i[r];h(i,r,e[r])&&(t.push({setting:r,oldValue:l,newValue:i[r]}),r==="positionHistorySize"&&(n=!0))}let d=["enableMousePrediction","enableScrollPrediction","enableTabPrediction"];for(let r of d){let l=i[r];m(i,r,e[r])&&(t.push({setting:r,oldValue:l,newValue:i[r]}),r==="enableScrollPrediction"&&(a=!0),r==="enableTabPrediction"&&(s=!0))}if(e.defaultHitSlop!==void 0){let r=i.defaultHitSlop,l=u(e.defaultHitSlop);p(r,l)||(i.defaultHitSlop=l,t.push({setting:"defaultHitSlop",oldValue:r,newValue:l}),o=!0)}if(e.touchDeviceStrategy!==void 0){let r=i.touchDeviceStrategy;i.touchDeviceStrategy=e.touchDeviceStrategy,t.push({setting:"touchDeviceStrategy",oldValue:r,newValue:e.touchDeviceStrategy}),c=!0}if(e.minimumConnectionType!==void 0){let r=i.minimumConnectionType;i.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:r,newValue:e.minimumConnectionType})}return{changedSettings:t,positionHistorySizeChanged:n,scrollPredictionChanged:a,tabPredictionChanged:s,hitSlopChanged:o,touchStrategyChanged:c}}var g=class i{constructor(e){this.elements=new Map;this.checkableElements=new Set;this.idCounter=0;this.activeElementCount=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 f;this._globalCallbackHits=H();this._globalSettings=F();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 a=e[n];if(a&&a.type==="childList"&&a.removedNodes.length>0){t=!0;break}}if(t)for(let n of this.elements.keys())n.isConnected||this.unregister(n,"disconnected")};e!==void 0&&D(this._globalSettings,e),this.handlerDependencies={elements:this.elements,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),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-BOXAW4XX.js");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-JWBQ2YOV.js");this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog("TouchDeviceHandler lazy loaded")}return this.touchDeviceHandler}static initialize(e){return this.isInitiated||(i.manager=new i(e)),i.manager}static get isInitiated(){return!!i.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)}get getManagerData(){let e=this.desktopHandler?.loadedPredictors,t=this.touchDeviceHandler?.loadedPredictors;return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount,loadedModules:{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}}}}get registeredElements(){return this.elements}register(e){let{isTouchDevice:t,isLimitedConnection:n,shouldRegister:a}=k();if(!a)return{isLimitedConnection:n,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};let s=this.elements.get(e.element);if(s)return s.registerCount++,{isLimitedConnection:n,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let o=M(e,this.generateId(),this._globalSettings.defaultHitSlop);return this.elements.set(e.element,o),this.activeElementCount++,this.updateCheckableStatus(o),this.currentlyActiveHandler?.observeElement(e.element),this.eventEmitter.emit({type:"elementRegistered",timestamp:Date.now(),elementData:o}),{isTouchDevice:t,isLimitedConnection:n,isRegistered:!0,unregister:()=>{this.unregister(e.element)}}}unregister(e,t){let n=this.elements.get(e);if(!n)return;this.clearReactivateTimeout(n),this.currentlyActiveHandler?.unobserveElement(e),this.elements.delete(e),this.checkableElements.delete(n),n.callbackInfo.isCallbackActive&&this.activeElementCount--;let a=this.elements.size===0&&this.isSetup;a&&(this.devLog("All elements unregistered, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"elementUnregistered",elementData:n,timestamp:Date.now(),unregisterReason:t??"by user",wasLastRegisteredElement:a})}reactivate(e){let t=this.elements.get(e);t&&(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),t.callbackInfo.isRunningCallback||(t.callbackInfo.isCallbackActive=!0,this.activeElementCount++,this.updateCheckableStatus(t),this.currentlyActiveHandler?.observeElement(e),this.eventEmitter.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}clearReactivateTimeout(e){clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0}updateCheckableStatus(e){e.isIntersectingWithViewport&&e.callbackInfo.isCallbackActive&&!e.callbackInfo.isRunningCallback?this.checkableElements.add(e):this.checkableElements.delete(e)}callCallback(e,t){e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,this.clearReactivateTimeout(e),this.checkableElements.delete(e)}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let n=performance.now(),a,s=null;try{await e.callback(),a="success"}catch(o){s=o instanceof Error?o.message:String(o),a="error",console.error(`Error in callback for element ${e.name}:`,o)}this.finalizeCallback(e,t,n,a,s)}finalizeCallback(e,t,n,a,s){e.callbackInfo.lastCallbackCompletedAt=Date.now(),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.activeElementCount--,this.currentlyActiveHandler?.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter));let o=this.activeElementCount===0;o&&(this.devLog("All elements unactivated, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-n,status:e.callbackInfo.lastCallbackStatus=a,errorMessage:e.callbackInfo.lastCallbackErrorMessage=s,wasLastActiveElement:o})}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),this.rafId=null),this.pendingPointerEvent=null)}alterGlobalSettings(e){let t=R(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.elements.values())e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),n=v(t,e.elementBounds.hitSlop);if(!p(n,e.elementBounds.expandedRect)){let a={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:n}};this.elements.set(e.element,a),this.eventEmitter.emit({type:"elementDataUpdated",elementData:a,updatedProps:["bounds"]})}}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{g as ForesightManager};
|
|
1
|
+
import{a as C,b as g,c as v,d as p}from"./chunk-PAYO6NXN.js";function k(){let n=E(),e=L();return{isTouchDevice:n,isLimitedConnection:e,shouldRegister:!e}}function E(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function L(){let n=navigator.connection;if(!n)return!1;let e=u.instance.getManagerData.globalSettings.minimumConnectionType,t=["slow-2g","2g","3g","4g"],i=t.indexOf(n.effectiveType),r=t.indexOf(e);return i<r||n.saveData}function F(n){if(typeof window>"u"||typeof document>"u")return!1;let e=window.innerWidth||document.documentElement.clientWidth,t=window.innerHeight||document.documentElement.clientHeight;return n.top<t&&n.bottom>0&&n.left<e&&n.right>0}function T(){return{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}}function H(){return{debug:!1,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"}}function R(n,e,t){let{element:i,callback:r,hitSlop:o,name:s,meta:c,reactivateAfter:b}=n,d=i.getBoundingClientRect(),a=o?g(o):t;return{id:e,element:i,callback:r,elementBounds:{originalRect:d,expandedRect:v(d,a),hitSlop:a},name:s||i.id||"unnamed",isIntersectingWithViewport:F(d),registerCount:1,meta:c??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:b??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}}}var f=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,i){if(i?.signal?.aborted)return;let r=this.eventListeners.get(e)??[];r.push(t),this.eventListeners.set(e,r),i?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let i=this.eventListeners.get(e);if(!i)return;let r=i.indexOf(t);r>-1&&i.splice(r,1)}emit(e){let t=this.eventListeners.get(e.type);if(!(!t||t.length===0))for(let i=0;i<t.length;i++)try{let r=t[i];r&&r(e)}catch(r){console.error(`Error in ForesightManager event listener ${i} for ${e.type}:`,r)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};function y(n,e){return n!==void 0&&e!==n}var I={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}};function h(n,e,t){if(!y(t,n[e]))return!1;let{min:i,max:r}=I[e];return n[e]=C(t,i,r,e),!0}function m(n,e,t){return y(t,n[e])?(n[e]=t,!0):!1}function M(n,e){h(n,"trajectoryPredictionTime",e.trajectoryPredictionTime),h(n,"positionHistorySize",e.positionHistorySize),h(n,"scrollMargin",e.scrollMargin),h(n,"tabOffset",e.tabOffset),m(n,"enableMousePrediction",e.enableMousePrediction),m(n,"enableScrollPrediction",e.enableScrollPrediction),m(n,"enableTabPrediction",e.enableTabPrediction),m(n,"enableManagerLogging",e.enableManagerLogging),e.defaultHitSlop!==void 0&&(n.defaultHitSlop=g(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(n.touchDeviceStrategy=e.touchDeviceStrategy),e.minimumConnectionType!==void 0&&(n.minimumConnectionType=e.minimumConnectionType),e.debug!==void 0&&(n.debug=e.debug)}function D(n,e){let t=[],i=!1,r=!1,o=!1,s=!1,c=!1;if(!e)return{changedSettings:t,positionHistorySizeChanged:i,scrollPredictionChanged:r,tabPredictionChanged:o,hitSlopChanged:s,touchStrategyChanged:c};let b=["trajectoryPredictionTime","positionHistorySize","scrollMargin","tabOffset"];for(let a of b){let l=n[a];h(n,a,e[a])&&(t.push({setting:a,oldValue:l,newValue:n[a]}),a==="positionHistorySize"&&(i=!0))}let d=["enableMousePrediction","enableScrollPrediction","enableTabPrediction"];for(let a of d){let l=n[a];m(n,a,e[a])&&(t.push({setting:a,oldValue:l,newValue:n[a]}),a==="enableScrollPrediction"&&(r=!0),a==="enableTabPrediction"&&(o=!0))}if(e.defaultHitSlop!==void 0){let a=n.defaultHitSlop,l=g(e.defaultHitSlop);p(a,l)||(n.defaultHitSlop=l,t.push({setting:"defaultHitSlop",oldValue:a,newValue:l}),s=!0)}if(e.touchDeviceStrategy!==void 0){let a=n.touchDeviceStrategy;n.touchDeviceStrategy=e.touchDeviceStrategy,t.push({setting:"touchDeviceStrategy",oldValue:a,newValue:e.touchDeviceStrategy}),c=!0}if(e.minimumConnectionType!==void 0){let a=n.minimumConnectionType;n.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:a,newValue:e.minimumConnectionType})}return{changedSettings:t,positionHistorySizeChanged:i,scrollPredictionChanged:r,tabPredictionChanged:o,hitSlopChanged:s,touchStrategyChanged:c}}var u=class n{constructor(e){this.elements=new Map;this.checkableElements=new Set;this.idCounter=0;this.activeElementCount=0;this.desktopHandler=null;this.touchDeviceHandler=null;this.currentlyActiveHandler=null;this.isSetup=!1;this.pendingPointerEvent=null;this.rafId=null;this.domObserver=null;this.currentDeviceStrategy=E()?"touch":"mouse";this.eventEmitter=new f;this._globalCallbackHits=T();this._globalSettings=H();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 i=0;i<e.length;i++){let r=e[i];if(r&&r.type==="childList"&&r.removedNodes.length>0){t=!0;break}}if(t)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};e!==void 0&&M(this._globalSettings,e),this.handlerDependencies={elements:this.elements,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),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-BOXAW4XX.js");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-JWBQ2YOV.js");this.touchDeviceHandler=new e(this.handlerDependencies),this.devLog("TouchDeviceHandler lazy loaded")}return this.touchDeviceHandler}static initialize(e){return this.isInitiated||(n.manager=new n(e)),n.manager}static get isInitiated(){return!!n.manager}static get instance(){return this.initialize()}generateId(){return`foresight-${++this.idCounter}`}get isUsingDesktopHandler(){return this.currentDeviceStrategy==="mouse"||this.currentDeviceStrategy==="pen"}addEventListener(e,t,i){this.eventEmitter.addEventListener(e,t,i)}removeEventListener(e,t){this.eventEmitter.removeEventListener(e,t)}hasListeners(e){return this.eventEmitter.hasListeners(e)}get getManagerData(){let e=this.desktopHandler?.loadedPredictors,t=this.touchDeviceHandler?.loadedPredictors;return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount,loadedModules:{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}}}}get registeredElements(){return this.elements}register(e){let{element:t,...i}=e;return t instanceof NodeList?Array.from(t,r=>this.registerElement({...i,element:r})):this.registerElement({...i,element:t})}registerElement(e){let{isTouchDevice:t,isLimitedConnection:i,shouldRegister:r}=k();if(!r)return{isLimitedConnection:i,isTouchDevice:t,isRegistered:!1,unregister:()=>{},elementData:null};let o=this.elements.get(e.element);if(o)return o.registerCount++,{isLimitedConnection:i,isTouchDevice:t,isRegistered:!1,unregister:()=>{},elementData:null};this.isSetup||this.initializeGlobalListeners();let s=R(e,this.generateId(),this._globalSettings.defaultHitSlop);return this.elements.set(e.element,s),this.activeElementCount++,this.updateCheckableStatus(s),this.currentlyActiveHandler?.observeElement(e.element),this.eventEmitter.emit({type:"elementRegistered",timestamp:Date.now(),elementData:s}),{isTouchDevice:t,isLimitedConnection:i,isRegistered:!0,unregister:()=>{this.unregister(e.element)},elementData:s}}unregister(e,t){e instanceof NodeList?e.forEach(i=>this.unregisterElement(i,t)):this.unregisterElement(e,t)}unregisterElement(e,t){let i=this.elements.get(e);if(!i)return;this.clearReactivateTimeout(i),this.currentlyActiveHandler?.unobserveElement(e),this.elements.delete(e),this.checkableElements.delete(i),i.callbackInfo.isCallbackActive&&this.activeElementCount--;let r=this.elements.size===0&&this.isSetup;r&&(this.devLog("All elements unregistered, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:t??"by user",wasLastRegisteredElement:r})}reactivate(e){e instanceof NodeList?e.forEach(t=>this.reactivateElement(t)):this.reactivateElement(e)}reactivateElement(e){let t=this.elements.get(e);t&&(this.isSetup||this.initializeGlobalListeners(),this.clearReactivateTimeout(t),t.callbackInfo.isRunningCallback||(t.callbackInfo.isCallbackActive=!0,this.activeElementCount++,this.updateCheckableStatus(t),this.currentlyActiveHandler?.observeElement(e),this.eventEmitter.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}clearReactivateTimeout(e){clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0}updateCheckableStatus(e){e.isIntersectingWithViewport&&e.callbackInfo.isCallbackActive&&!e.callbackInfo.isRunningCallback?this.checkableElements.add(e):this.checkableElements.delete(e)}callCallback(e,t){e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive||(this.markElementAsRunning(e),this.executeCallbackAsync(e,t))}markElementAsRunning(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,this.clearReactivateTimeout(e),this.checkableElements.delete(e)}async executeCallbackAsync(e,t){this.updateHitCounters(t),this.eventEmitter.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let i=performance.now(),r,o=null;try{await e.callback(e),r="success"}catch(s){o=s instanceof Error?s.message:String(s),r="error",console.error(`Error in callback for element ${e.name}:`,s)}this.finalizeCallback(e,t,i,r,o)}finalizeCallback(e,t,i,r,o){e.callbackInfo.lastCallbackCompletedAt=Date.now(),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.activeElementCount--,this.currentlyActiveHandler?.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter));let s=this.activeElementCount===0;s&&(this.devLog("All elements unactivated, removing global listeners"),this.removeGlobalListeners()),this.eventEmitter.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-i,status:e.callbackInfo.lastCallbackStatus=r,errorMessage:e.callbackInfo.lastCallbackErrorMessage=o,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),this.rafId=null),this.pendingPointerEvent=null)}alterGlobalSettings(e){let t=D(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.elements.values())e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),i=v(t,e.elementBounds.hitSlop);if(!p(i,e.elementBounds.expandedRect)){let r={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:i}};this.elements.set(e.element,r),this.eventEmitter.emit({type:"elementDataUpdated",elementData:r,updatedProps:["bounds"]})}}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{u as ForesightManager};
|
package/package.json
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "js.foresight",
|
|
3
|
-
"version": "3.
|
|
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
|
-
"type": "module",
|
|
6
|
-
"sideEffects": false,
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsup --sourcemap",
|
|
9
|
-
"build:prod": "tsup",
|
|
10
|
-
"dev": "tsup --sourcemap --watch",
|
|
11
|
-
"test": "vitest",
|
|
12
|
-
"test:watch": "vitest --watch",
|
|
13
|
-
"test:ui": "vitest --ui",
|
|
14
|
-
"test:coverage": "vitest --coverage",
|
|
15
|
-
"test:run": "vitest run",
|
|
16
|
-
"prepublishOnly": "pnpm test:run && pnpm build:prod"
|
|
17
|
-
},
|
|
18
|
-
"main": "./dist/index.js",
|
|
19
|
-
"module": "./dist/index.js",
|
|
20
|
-
"types": "./dist/index.d.ts",
|
|
21
|
-
"exports": {
|
|
22
|
-
".": {
|
|
23
|
-
"types": "./dist/index.d.ts",
|
|
24
|
-
"default": "./dist/index.js"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"homepage": "https://foresightjs.com/",
|
|
28
|
-
"repository": {
|
|
29
|
-
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/spaansba/ForesightJS"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist",
|
|
34
|
-
"README.md",
|
|
35
|
-
"LICENSE"
|
|
36
|
-
],
|
|
37
|
-
"keywords": [
|
|
38
|
-
"javascript-prefetch",
|
|
39
|
-
"smart-prefetch",
|
|
40
|
-
"fast-prefetch",
|
|
41
|
-
"interaction-prediction",
|
|
42
|
-
"mobile-prefetch",
|
|
43
|
-
"mouse-trajectory",
|
|
44
|
-
"element-hitslop",
|
|
45
|
-
"foresight",
|
|
46
|
-
"interaction-prediction",
|
|
47
|
-
"cursor-prediction",
|
|
48
|
-
"vanilla-javascript",
|
|
49
|
-
"prefetching",
|
|
50
|
-
"keyboard-tracking",
|
|
51
|
-
"keyboard-prefetching",
|
|
52
|
-
"tab-prefetching"
|
|
53
|
-
],
|
|
54
|
-
"author": "Bart Spaans",
|
|
55
|
-
"license": "MIT",
|
|
56
|
-
"llms": "https://foresightjs.com/llms.txt",
|
|
57
|
-
"llmsFull": "https://foresightjs.com/llms-full.txt",
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@testing-library/dom": "^10.4.1",
|
|
60
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
61
|
-
"@types/node": "^24.10.1",
|
|
62
|
-
"@vitest/coverage-v8": "4.0.16",
|
|
63
|
-
"@vitest/ui": "^4.0.9",
|
|
64
|
-
"happy-dom": "^20.0.10",
|
|
65
|
-
"jsdom": "^27.4.0",
|
|
66
|
-
"tslib": "^2.8.1",
|
|
67
|
-
"tsup": "^8.5.1",
|
|
68
|
-
"typescript": "^5.9.3",
|
|
69
|
-
"vitest": "^4.0.9"
|
|
70
|
-
},
|
|
71
|
-
"dependencies": {
|
|
72
|
-
"position-observer": "^1.0.3",
|
|
73
|
-
"tabbable": "^6.
|
|
74
|
-
}
|
|
75
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "js.foresight",
|
|
3
|
+
"version": "3.5.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup --sourcemap",
|
|
9
|
+
"build:prod": "tsup",
|
|
10
|
+
"dev": "tsup --sourcemap --watch",
|
|
11
|
+
"test": "vitest",
|
|
12
|
+
"test:watch": "vitest --watch",
|
|
13
|
+
"test:ui": "vitest --ui",
|
|
14
|
+
"test:coverage": "vitest --coverage",
|
|
15
|
+
"test:run": "vitest run",
|
|
16
|
+
"prepublishOnly": "pnpm test:run && pnpm build:prod"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://foresightjs.com/",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/spaansba/ForesightJS"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"keywords": [
|
|
38
|
+
"javascript-prefetch",
|
|
39
|
+
"smart-prefetch",
|
|
40
|
+
"fast-prefetch",
|
|
41
|
+
"interaction-prediction",
|
|
42
|
+
"mobile-prefetch",
|
|
43
|
+
"mouse-trajectory",
|
|
44
|
+
"element-hitslop",
|
|
45
|
+
"foresight",
|
|
46
|
+
"interaction-prediction",
|
|
47
|
+
"cursor-prediction",
|
|
48
|
+
"vanilla-javascript",
|
|
49
|
+
"prefetching",
|
|
50
|
+
"keyboard-tracking",
|
|
51
|
+
"keyboard-prefetching",
|
|
52
|
+
"tab-prefetching"
|
|
53
|
+
],
|
|
54
|
+
"author": "Bart Spaans",
|
|
55
|
+
"license": "MIT",
|
|
56
|
+
"llms": "https://foresightjs.com/llms.txt",
|
|
57
|
+
"llmsFull": "https://foresightjs.com/llms-full.txt",
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@testing-library/dom": "^10.4.1",
|
|
60
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
61
|
+
"@types/node": "^24.10.1",
|
|
62
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
63
|
+
"@vitest/ui": "^4.0.9",
|
|
64
|
+
"happy-dom": "^20.0.10",
|
|
65
|
+
"jsdom": "^27.4.0",
|
|
66
|
+
"tslib": "^2.8.1",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"typescript": "^5.9.3",
|
|
69
|
+
"vitest": "^4.0.9"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"position-observer": "^1.0.3",
|
|
73
|
+
"tabbable": "^6.4.0"
|
|
74
|
+
}
|
|
75
|
+
}
|