js.foresight 3.3.3 → 3.3.5
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 +6 -1
- package/dist/index.d.ts +19 -22
- package/dist/index.js +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -56,7 +56,12 @@ ForesightManager.instance.register({
|
|
|
56
56
|
|
|
57
57
|
## Integrations
|
|
58
58
|
|
|
59
|
-
Since ForesightJS is framework agnostic, it can be integrated with any JavaScript framework.
|
|
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!
|
|
60
65
|
|
|
61
66
|
## Configuration
|
|
62
67
|
|
package/dist/index.d.ts
CHANGED
|
@@ -450,60 +450,57 @@ interface ForesightBaseEvent {
|
|
|
450
450
|
declare class ForesightManager {
|
|
451
451
|
private static manager;
|
|
452
452
|
private elements;
|
|
453
|
+
private checkableElements;
|
|
453
454
|
private idCounter;
|
|
454
455
|
private activeElementCount;
|
|
455
456
|
private desktopHandler;
|
|
456
457
|
private touchDeviceHandler;
|
|
457
458
|
private handler;
|
|
458
459
|
private isSetup;
|
|
459
|
-
private _globalCallbackHits;
|
|
460
|
-
private _globalSettings;
|
|
461
460
|
private pendingPointerEvent;
|
|
462
461
|
private rafId;
|
|
463
462
|
private domObserver;
|
|
464
|
-
private eventListeners;
|
|
465
463
|
private currentDeviceStrategy;
|
|
464
|
+
private eventEmitter;
|
|
465
|
+
private _globalCallbackHits;
|
|
466
|
+
private _globalSettings;
|
|
466
467
|
private constructor();
|
|
467
|
-
private generateId;
|
|
468
468
|
static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager;
|
|
469
|
+
static get isInitiated(): Readonly<boolean>;
|
|
470
|
+
static get instance(): ForesightManager;
|
|
471
|
+
private generateId;
|
|
469
472
|
addEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>, options?: {
|
|
470
473
|
signal?: AbortSignal;
|
|
471
|
-
}):
|
|
474
|
+
}): void;
|
|
472
475
|
removeEventListener<K extends ForesightEvent>(eventType: K, listener: ForesightEventListener<K>): void;
|
|
473
|
-
|
|
476
|
+
hasListeners<K extends ForesightEvent>(eventType: K): boolean;
|
|
474
477
|
get getManagerData(): Readonly<ForesightManagerData>;
|
|
475
|
-
static get isInitiated(): Readonly<boolean>;
|
|
476
|
-
static get instance(): ForesightManager;
|
|
477
478
|
get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData>;
|
|
478
|
-
register(
|
|
479
|
+
register(options: ForesightRegisterOptions): ForesightRegisterResult;
|
|
479
480
|
unregister(element: ForesightElement, unregisterReason?: ElementUnregisteredReason): void;
|
|
480
|
-
private updateHitCounters;
|
|
481
481
|
reactivate(element: ForesightElement): void;
|
|
482
482
|
private clearReactivateTimeout;
|
|
483
|
-
|
|
483
|
+
updateCheckableStatus(elementData: ForesightElementData): void;
|
|
484
484
|
private callCallback;
|
|
485
|
+
private markElementAsRunning;
|
|
486
|
+
private executeCallbackAsync;
|
|
487
|
+
private finalizeCallback;
|
|
488
|
+
private updateHitCounters;
|
|
485
489
|
private setDeviceStrategy;
|
|
486
490
|
private handlePointerMove;
|
|
487
491
|
private initializeGlobalListeners;
|
|
488
492
|
private removeGlobalListeners;
|
|
489
|
-
/**
|
|
490
|
-
* Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.
|
|
491
|
-
*
|
|
492
|
-
* @param mutationsList - Array of MutationRecord objects describing the DOM changes
|
|
493
|
-
*
|
|
494
|
-
*/
|
|
495
493
|
private handleDomMutations;
|
|
494
|
+
alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
|
|
496
495
|
private forceUpdateAllElementBounds;
|
|
497
496
|
/**
|
|
498
497
|
* ONLY use this function when you want to change the rect bounds via code, if the rects are changing because of updates in the DOM do not use this function.
|
|
499
498
|
* We need an observer for that
|
|
500
499
|
*/
|
|
501
500
|
private forceUpdateElementBounds;
|
|
502
|
-
private initializeManagerSettings;
|
|
503
|
-
private updateNumericSettings;
|
|
504
|
-
private updateBooleanSetting;
|
|
505
|
-
alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void;
|
|
506
501
|
private devLog;
|
|
507
502
|
}
|
|
508
503
|
|
|
509
|
-
|
|
504
|
+
type HasListenersFunction = <K extends ForesightEvent>(eventType: K) => boolean;
|
|
505
|
+
|
|
506
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function g(n,e,t,i){return n<e?console.warn(`ForesightJS: "${i}" value ${n} is below minimum bound ${e}, clamping to ${e}`):n>t&&console.warn(`ForesightJS: "${i}" value ${n} is above maximum bound ${t}, clamping to ${t}`),Math.min(Math.max(n,e),t)}function z(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 C(n){if(typeof n=="number"){let e=g(n,0,2e3,"hitslop");return{top:e,left:e,right:e,bottom:e}}return{top:g(n.top,0,2e3,"hitslop - top"),left:g(n.left,0,2e3,"hitslop - left"),right:g(n.right,0,2e3,"hitslop - right"),bottom:g(n.bottom,0,2e3,"hitslop - bottom")}}function P(n,e){return{left:n.left-e.left,right:n.right+e.right,top:n.top-e.top,bottom:n.bottom+e.bottom}}function A(n,e){return!n||!e?n===e:n.left===e.left&&n.right===e.right&&n.top===e.top&&n.bottom===e.bottom}function M(n,e){return n.x>=e.left&&n.x<=e.right&&n.y>=e.top&&n.y<=e.bottom}function K(){let n=N(),e=te();return{isTouchDevice:n,isLimitedConnection:e,shouldRegister:!e}}function N(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function te(){let n=navigator.connection;if(!n)return!1;let e=T.instance.getManagerData.globalSettings.minimumConnectionType,t=["slow-2g","2g","3g","4g"],i=t.indexOf(n.effectiveType),o=t.indexOf(e);return i<o||n.saveData}function H(n,e){return n!==void 0&&e!==n}var u=class{constructor(e){this._isConnected=!1;this.elements=e.elements,this.callCallback=e.callCallback,this.emit=e.emit,this.settings=e.settings}get isConnected(){return this._isConnected}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0}devLog(e){if(this.settings.enableManagerLogging){let t=this.moduleName.includes("Predictor")?"#ea580c":"#2563eb";console.log(`%c\u{1F6E0}\uFE0F ${this.moduleName}: ${e}`,`color: ${t}; font-weight: bold;`)}}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};function I(n,e,t){let i=0,o=1,r=e.x-n.x,l=e.y-n.y,a=(s,d)=>{if(s===0){if(d<0)return!1}else{let c=d/s;if(s<0){if(c>o)return!1;c>i&&(i=c)}else{if(c<i)return!1;c<o&&(o=c)}}return!0};return!a(-r,n.x-t.left)||!a(r,t.right-n.x)||!a(-l,n.y-t.top)||!a(l,t.bottom-n.y)?!1:i<=o}function $(n,e,t){let i=performance.now(),o={point:n,time:i},{x:r,y:l}=n;if(e.add(o),e.length<2)return{x:r,y:l};let[a,s]=e.getFirstLast();if(!a||!s)return{x:r,y:l};let d=(s.time-a.time)*.001;if(d===0)return{x:r,y:l};let c=s.point.x-a.point.x,h=s.point.y-a.point.y,y=c/d,b=h/d,L=t*.001,w=r+y*L,V=l+b*L;return{x:w,y:V}}var F=class extends u{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}updatePointerState(t){let i={x:t.clientX,y:t.clientY};this.trajectoryPositions.currentPoint=i,this.settings.enableMousePrediction?this.trajectoryPositions.predictedPoint=$(i,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime):this.trajectoryPositions.predictedPoint=i}processMouseMovement(t){this.updatePointerState(t);let i=this.settings.enableMousePrediction,o=this.trajectoryPositions.currentPoint;for(let r of this.elements.values()){if(!r.isIntersectingWithViewport||!r.callbackInfo.isCallbackActive||r.callbackInfo.isRunningCallback)continue;let l=r.elementBounds.expandedRect;if(i)I(o,this.trajectoryPositions.predictedPoint,l)&&this.callCallback(r,{kind:"mouse",subType:"trajectory"});else if(M(o,l)){this.callCallback(r,{kind:"mouse",subType:"hover"});return}}this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:i,trajectoryPositions:this.trajectoryPositions})}onDisconnect(){}onConnect(){}};function Y(n,e){let i=e.top-n.top,o=e.left-n.left;return i<-1?"down":i>1?"up":o<-1?"right":o>1?"left":"none"}function G(n,e,t){let{x:i,y:o}=n,r={x:i,y:o};switch(e){case"down":r.y+=t;break;case"up":r.y-=t;break;case"left":r.x-=t;break;case"right":r.x+=t;break;case"none":break;default:}return r}var D=class extends u{constructor(t){super(t.dependencies);this.moduleName="ScrollPredictor";this.predictedScrollPoint=null;this.scrollDirection=null;this.onDisconnect=()=>this.resetScrollProps();this.trajectoryPositions=t.trajectoryPositions}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(t,i){!t.isIntersectingWithViewport||t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive||(this.scrollDirection=this.scrollDirection??Y(t.elementBounds.originalRect,i),this.scrollDirection!=="none"&&(this.predictedScrollPoint=this.predictedScrollPoint??G(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),I(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})))}onConnect(){}};import{tabbable as ie}from"tabbable";function X(n,e,t,i){if(e!==null&&e>-1){let o=n?e-1:e+1;if(o>=0&&o<t.length&&t[o]===i)return o}return t.findIndex(o=>o===i)}var _=class extends u{constructor(t){super(t);this.moduleName="TabPredictor";this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=t=>{t.key==="Tab"&&(this.lastKeyDown=t)};this.handleFocusIn=t=>{if(!this.lastKeyDown)return;let i=t.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.devLog("Caching tabbable elements"),this.tabbableElementsCache=ie(document.documentElement));let o=this.lastKeyDown.shiftKey,r=X(o,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let l=[],a=this.settings.tabOffset,s=this.elements;for(let d=0;d<=a;d++){let c=o?r-d:r+d,h=this.tabbableElementsCache[c];h&&h instanceof Element&&s.has(h)&&l.push(h)}for(let d of l){let c=s.get(d);c&&!c.callbackInfo.isRunningCallback&&c.callbackInfo.isCallbackActive&&this.callCallback(c,{kind:"tab",subType:o?"reverse":"forwards"})}}}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}};import{PositionObserver as ne}from"position-observer";var R=class{constructor(e){this.head=0;this.count=0;if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=e,this.buffer=new Array(e)}add(e){this.buffer[this.head]=e,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let i=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[i,i]}let e=this.getFirst(),t=this.getLast();return[e,t]}resize(e){if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=new Array(e),this.head=0,this.count=0,t.length>e){let i=t.slice(-e);for(let o of i)this.add(o)}else for(let i of t)this.add(i)}getAllItems(){if(this.count===0)return[];let e=new Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)e[t]=this.buffer[t];else{let t=this.head;for(let i=0;i<this.capacity;i++){let o=(t+i)%this.capacity;e[i]=this.buffer[o]}}return e}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};var m=class extends u{constructor(t){super(t);this.moduleName="DesktopHandler";this.positionObserver=null;this.trajectoryPositions={positions:new R(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.handlePositionChange=t=>{let i=this.settings.enableScrollPrediction;for(let o of t){let r=this.elements.get(o.target);r&&(i?this.scrollPredictor?.handleScrollPrefetch(r,o.boundingClientRect):this.checkForMouseHover(r),this.handlePositionChangeDataUpdates(r,o))}i&&this.scrollPredictor?.resetScrollProps()};this.checkForMouseHover=t=>{M(this.trajectoryPositions.currentPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"mouse",subType:"hover"})};this.handlePositionChangeDataUpdates=(t,i)=>{let o=[],r=i.isIntersecting;t.isIntersectingWithViewport!==r&&(o.push("visibility"),t.isIntersectingWithViewport=r),r&&(o.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:i.boundingClientRect,expandedRect:P(i.boundingClientRect,t.elementBounds.hitSlop)}),o.length&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:o})};this.processMouseMovement=t=>this.mousePredictor.processMouseMovement(t);this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache();this.observeElement=t=>this.positionObserver?.observe(t);this.unobserveElement=t=>this.positionObserver?.unobserve(t);this.connectTabPredictor=()=>this.tabPredictor.connect();this.connectScrollPredictor=()=>this.scrollPredictor.connect();this.connectMousePredictor=()=>this.mousePredictor.connect();this.disconnectTabPredictor=()=>this.tabPredictor.disconnect();this.disconnectScrollPredictor=()=>this.scrollPredictor.disconnect();this.disconnectMousePredictor=()=>this.mousePredictor.disconnect();this.tabPredictor=new _(t),this.scrollPredictor=new D({dependencies:t,trajectoryPositions:this.trajectoryPositions}),this.mousePredictor=new F({dependencies:t,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new ne(this.handlePositionChange);let t=["mouse"];this.settings.enableTabPrediction&&t.push("tab"),this.settings.enableScrollPrediction&&t.push("scroll"),this.devLog(`Connected predictors: [${t.join(", ")}] and PositionObserver`);for(let i of this.elements.keys())this.positionObserver.observe(i)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}};var k=class extends u{constructor(t){super(t);this.moduleName="ViewportPredictor";this.intersectionObserver=null;this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter);this.observeElement=t=>this.intersectionObserver?.observe(t);this.unobserveElement=t=>this.intersectionObserver?.unobserve(t);this.handleViewportEnter=t=>{for(let i of t){if(!i.isIntersecting)continue;let o=this.elements.get(i.target);o&&(this.callCallback(o,{kind:"viewport"}),this.unobserveElement(i.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}};var O=class extends u{constructor(t){super(t);this.moduleName="TouchStartPredictor";this.onConnect=()=>this.createAbortController();this.onDisconnect=()=>{};this.handleTouchStart=t=>{let i=t.currentTarget,o=this.elements.get(i);o&&(this.callCallback(o,{kind:"touch"}),this.unobserveElement(i))}}observeElement(t){t instanceof HTMLElement&&t.addEventListener("pointerdown",this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(t){t instanceof HTMLElement&&t.removeEventListener("pointerdown",this.handleTouchStart)}};var v=class extends u{constructor(t){super(t);this.moduleName="TouchDeviceHandler";this.predictor=null;this.onDisconnect=()=>{this.devLog("Disconnecting touch predictor"),this.predictor?.disconnect()};this.onConnect=()=>this.setTouchPredictor();this.observeElement=t=>this.predictor?.observeElement(t);this.unobserveElement=t=>this.predictor?.unobserveElement(t);this.viewportPredictor=new k(t),this.touchStartPredictor=new O(t),this.predictor=this.viewportPredictor}setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case"viewport":this.predictor=this.viewportPredictor,this.devLog("Connected touch strategy: viewport (ViewportPredictor)");break;case"onTouchStart":this.predictor=this.touchStartPredictor,this.devLog("Connected touch strategy: onTouchStart (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 t of this.elements.keys())this.predictor?.observeElement(t)}};var T=class n{constructor(e){this.elements=new Map;this.idCounter=0;this.activeElementCount=0;this.isSetup=!1;this._globalCallbackHits={mouse:{hover:0,trajectory:0},tab:{forwards:0,reverse:0},scroll:{down:0,left:0,right:0,up:0},touch:0,viewport:0,total:0};this._globalSettings={debug:!1,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"};this.pendingPointerEvent=null;this.rafId=null;this.domObserver=null;this.eventListeners=new Map;this.currentDeviceStrategy=N()?"touch":"mouse";this.handlePointerMove=e=>{this.pendingPointerEvent=e,e.pointerType!=this.currentDeviceStrategy&&(this.emit({type:"deviceStrategyChanged",timestamp:Date.now(),newStrategy:e.pointerType,oldStrategy:this.currentDeviceStrategy}),this.setDeviceStrategy(this.currentDeviceStrategy=e.pointerType)),!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(this.handler instanceof v){this.rafId=null;return}this.pendingPointerEvent&&this.handler.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 o=e[i];if(o.type==="childList"&&o.removedNodes.length>0){t=!0;break}}if(t)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};e!==void 0&&this.initializeManagerSettings(e);let t={elements:this.elements,callCallback:this.callCallback.bind(this),emit:this.emit.bind(this),settings:this._globalSettings};this.desktopHandler=new m(t),this.touchDeviceHandler=new v(t),this.handler=this.currentDeviceStrategy==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}generateId(){return`foresight-${++this.idCounter}`}static initialize(e){return this.isInitiated||(n.manager=new n(e)),n.manager}addEventListener(e,t,i){if(i?.signal?.aborted)return()=>{};let o=this.eventListeners.get(e)??[];o.push(t),this.eventListeners.set(e,o),i?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let i=this.eventListeners.get(e);if(!i)return;let o=i.indexOf(t);o>-1&&i.splice(o,1)}emit(e){let t=this.eventListeners.get(e.type)?.slice();if(t)for(let i=0;i<t.length;i++)try{t[i](e)}catch(o){console.error(`Error in ForesightManager event listener ${i} for ${e.type}:`,o)}}get getManagerData(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventListeners,currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount}}static get isInitiated(){return!!n.manager}static get instance(){return this.initialize()}get registeredElements(){return this.elements}register({element:e,callback:t,hitSlop:i,name:o,meta:r,reactivateAfter:l}){let{isTouchDevice:a,isLimitedConnection:s,shouldRegister:d}=K();if(!d)return{isLimitedConnection:s,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};let c=this.elements.get(e);if(c)return c.registerCount++,{isLimitedConnection:s,isTouchDevice:a,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let h=e.getBoundingClientRect(),y=i?C(i):this._globalSettings.defaultHitSlop,b={id:this.generateId(),element:e,callback:t,elementBounds:{originalRect:h,expandedRect:P(h,y),hitSlop:y},name:o||e.id||"unnamed",isIntersectingWithViewport:z(h),registerCount:1,meta:r??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:l??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}};return this.elements.set(e,b),this.activeElementCount++,this.handler.observeElement(e),this.emit({type:"elementRegistered",timestamp:Date.now(),elementData:b}),{isTouchDevice:a,isLimitedConnection:s,isRegistered:!0,unregister:()=>{this.unregister(e)}}}unregister(e,t){let i=this.elements.get(e);if(!i)return;this.clearReactivateTimeout(i),this.handler.unobserveElement(e),this.elements.delete(e),i.callbackInfo.isCallbackActive&&this.activeElementCount--;let o=this.elements.size===0&&this.isSetup;o&&(this.devLog("All elements unregistered, removing global listeners"),this.removeGlobalListeners()),i&&this.emit({type:"elementUnregistered",elementData:i,timestamp:Date.now(),unregisterReason:t??"by user",wasLastRegisteredElement: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++}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.handler.observeElement(e),this.emit({type:"elementReactivated",elementData:t,timestamp:Date.now()})))}clearReactivateTimeout(e){clearTimeout(e.callbackInfo.reactivateTimeoutId),e.callbackInfo.reactivateTimeoutId=void 0}makeElementUnactive(e){e.callbackInfo.callbackFiredCount++,e.callbackInfo.lastCallbackInvokedAt=Date.now(),e.callbackInfo.isRunningCallback=!0,this.clearReactivateTimeout(e)}callCallback(e,t){if(e.callbackInfo.isRunningCallback||!e.callbackInfo.isCallbackActive)return;this.makeElementUnactive(e),(async()=>{this.updateHitCounters(t),this.emit({type:"callbackInvoked",timestamp:Date.now(),elementData:e,hitType:t});let o=performance.now(),r,l=null;try{await e.callback(),r="success"}catch(s){l=s instanceof Error?s.message:String(s),r="error",console.error(`Error in callback for element ${e.name}:`,s)}e.callbackInfo.lastCallbackCompletedAt=Date.now(),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.activeElementCount--,this.handler.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter));let a=this.activeElementCount===0;a&&(this.devLog("All elements unactivated, removing global listeners"),this.removeGlobalListeners()),this.emit({type:"callbackCompleted",timestamp:Date.now(),elementData:e,hitType:t,elapsed:e.callbackInfo.lastCallbackRuntime=performance.now()-o,status:e.callbackInfo.lastCallbackStatus=r,errorMessage:e.callbackInfo.lastCallbackErrorMessage=l,wasLastActiveElement:a})})()}setDeviceStrategy(e){let t=this.handler instanceof m?"mouse":"touch";t!==e&&this.devLog(`Switching device strategy from ${t} to ${e}`),this.handler.disconnect(),this.handler=e==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.handler.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.handler.disconnect(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingPointerEvent=null)}forceUpdateAllElementBounds(){for(let[,e]of this.elements)e.isIntersectingWithViewport&&this.forceUpdateElementBounds(e)}forceUpdateElementBounds(e){let t=e.element.getBoundingClientRect(),i=P(t,e.elementBounds.hitSlop);if(!A(i,e.elementBounds.expandedRect)){let o={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:i}};this.elements.set(e.element,o),this.emit({type:"elementDataUpdated",elementData:o,updatedProps:["bounds"]})}}initializeManagerSettings(e){this.updateNumericSettings(e.trajectoryPredictionTime,"trajectoryPredictionTime",10,200),this.updateNumericSettings(e.positionHistorySize,"positionHistorySize",2,30),this.updateNumericSettings(e.scrollMargin,"scrollMargin",30,300),this.updateNumericSettings(e.tabOffset,"tabOffset",0,20),this.updateBooleanSetting(e.enableMousePrediction,"enableMousePrediction"),this.updateBooleanSetting(e.enableScrollPrediction,"enableScrollPrediction"),this.updateBooleanSetting(e.enableTabPrediction,"enableTabPrediction"),this.updateBooleanSetting(e.enableManagerLogging,"enableManagerLogging"),e.defaultHitSlop!==void 0&&(this._globalSettings.defaultHitSlop=C(e.defaultHitSlop)),e.touchDeviceStrategy!==void 0&&(this._globalSettings.touchDeviceStrategy=e.touchDeviceStrategy),e.minimumConnectionType!==void 0&&(this._globalSettings.minimumConnectionType=e.minimumConnectionType),e.debug!==void 0&&(this._globalSettings.debug=e.debug)}updateNumericSettings(e,t,i,o){return H(e,this._globalSettings[t])?(this._globalSettings[t]=g(e,i,o,t),!0):!1}updateBooleanSetting(e,t){return H(e,this._globalSettings[t])?(this._globalSettings[t]=e,!0):!1}alterGlobalSettings(e){let t=[],i=this._globalSettings.trajectoryPredictionTime;this.updateNumericSettings(e?.trajectoryPredictionTime,"trajectoryPredictionTime",10,200)&&t.push({setting:"trajectoryPredictionTime",oldValue:i,newValue:this._globalSettings.trajectoryPredictionTime});let r=this._globalSettings.positionHistorySize;this.updateNumericSettings(e?.positionHistorySize,"positionHistorySize",2,30)&&(t.push({setting:"positionHistorySize",oldValue:r,newValue:this._globalSettings.positionHistorySize}),this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize));let a=this._globalSettings.scrollMargin;if(this.updateNumericSettings(e?.scrollMargin,"scrollMargin",30,300)){let p=this._globalSettings.scrollMargin;t.push({setting:"scrollMargin",oldValue:a,newValue:p})}let d=this._globalSettings.tabOffset;this.updateNumericSettings(e?.tabOffset,"tabOffset",0,20)&&(this._globalSettings.tabOffset=g(e.tabOffset,0,20,"tabOffset"),t.push({setting:"tabOffset",oldValue:d,newValue:this._globalSettings.tabOffset}));let h=this._globalSettings.enableMousePrediction;this.updateBooleanSetting(e?.enableMousePrediction,"enableMousePrediction")&&t.push({setting:"enableMousePrediction",oldValue:h,newValue:this._globalSettings.enableMousePrediction});let b=this._globalSettings.enableScrollPrediction;this.updateBooleanSetting(e?.enableScrollPrediction,"enableScrollPrediction")&&(this.handler instanceof m&&(this._globalSettings.enableScrollPrediction?this.handler.connectScrollPredictor():this.handler.disconnectScrollPredictor()),t.push({setting:"enableScrollPrediction",oldValue:b,newValue:this._globalSettings.enableScrollPrediction}));let w=this._globalSettings.enableTabPrediction;if(this.updateBooleanSetting(e?.enableTabPrediction,"enableTabPrediction")&&(this.handler instanceof m&&(this._globalSettings.enableTabPrediction?this.handler.connectTabPredictor():this.handler.disconnectTabPredictor()),t.push({setting:"enableTabPrediction",oldValue:w,newValue:this._globalSettings.enableTabPrediction})),e?.defaultHitSlop!==void 0){let p=this._globalSettings.defaultHitSlop,f=C(e.defaultHitSlop);A(p,f)||(this._globalSettings.defaultHitSlop=f,t.push({setting:"defaultHitSlop",oldValue:p,newValue:f}),this.forceUpdateAllElementBounds())}if(e?.touchDeviceStrategy!==void 0){let p=this._globalSettings.touchDeviceStrategy,f=e.touchDeviceStrategy;this._globalSettings.touchDeviceStrategy=f,t.push({setting:"touchDeviceStrategy",oldValue:p,newValue:f}),this.handler instanceof v&&this.handler.setTouchPredictor()}if(e?.minimumConnectionType!==void 0){let p=this._globalSettings.minimumConnectionType;this._globalSettings.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:p,newValue:this._globalSettings.minimumConnectionType})}t.length>0&&this.emit({type:"managerSettingsChanged",timestamp:Date.now(),managerData:this.getManagerData,updatedSettings:t})}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{T as ForesightManager};
|
|
1
|
+
function p(n,e,t,i){return n<e?console.warn(`ForesightJS: "${i}" value ${n} is below minimum bound ${e}, clamping to ${e}`):n>t&&console.warn(`ForesightJS: "${i}" value ${n} is above maximum bound ${t}, clamping to ${t}`),Math.min(Math.max(n,e),t)}function E(n){if(typeof n=="number"){let e=p(n,0,2e3,"hitslop");return{top:e,left:e,right:e,bottom:e}}return{top:p(n.top,0,2e3,"hitslop - top"),left:p(n.left,0,2e3,"hitslop - left"),right:p(n.right,0,2e3,"hitslop - right"),bottom:p(n.bottom,0,2e3,"hitslop - bottom")}}function g(n,e){return{left:n.left-e.left,right:n.right+e.right,top:n.top-e.top,bottom:n.bottom+e.bottom}}function b(n,e){return!n||!e?n===e:n.left===e.left&&n.right===e.right&&n.top===e.top&&n.bottom===e.bottom}function T(n,e){return n.x>=e.left&&n.x<=e.right&&n.y>=e.top&&n.y<=e.bottom}function N(){let n=O(),e=Z();return{isTouchDevice:n,isLimitedConnection:e,shouldRegister:!e}}function O(){return typeof window>"u"||typeof navigator>"u"?!1:window.matchMedia("(pointer: coarse)").matches&&navigator.maxTouchPoints>0}function Z(){let n=navigator.connection;if(!n)return!1;let e=S.instance.getManagerData.globalSettings.minimumConnectionType,t=["slow-2g","2g","3g","4g"],i=t.indexOf(n.effectiveType),o=t.indexOf(e);return i<o||n.saveData}function U(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 j(){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 B(){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 K(n,e,t){let{element:i,callback:o,hitSlop:r,name:a,meta:c,reactivateAfter:h}=n,l=i.getBoundingClientRect(),s=r?E(r):t;return{id:e,element:i,callback:o,elementBounds:{originalRect:l,expandedRect:g(l,s),hitSlop:s},name:a||i.id||"unnamed",isIntersectingWithViewport:U(l),registerCount:1,meta:c??{},callbackInfo:{callbackFiredCount:0,lastCallbackInvokedAt:void 0,lastCallbackCompletedAt:void 0,lastCallbackRuntime:void 0,lastCallbackStatus:void 0,lastCallbackErrorMessage:void 0,reactivateAfter:h??1/0,isCallbackActive:!0,isRunningCallback:!1,reactivateTimeoutId:void 0}}}var I=class{constructor(){this.eventListeners=new Map}addEventListener(e,t,i){if(i?.signal?.aborted)return;let o=this.eventListeners.get(e)??[];o.push(t),this.eventListeners.set(e,o),i?.signal?.addEventListener("abort",()=>this.removeEventListener(e,t))}removeEventListener(e,t){let i=this.eventListeners.get(e);if(!i)return;let o=i.indexOf(t);o>-1&&i.splice(o,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 o=t[i];o&&o(e)}catch(o){console.error(`Error in ForesightManager event listener ${i} for ${e.type}:`,o)}}hasListeners(e){let t=this.eventListeners.get(e);return t!==void 0&&t.length>0}getEventListeners(){return this.eventListeners}};function w(n,e){return n!==void 0&&e!==n}var pe={trajectoryPredictionTime:{min:10,max:200},positionHistorySize:{min:2,max:30},scrollMargin:{min:30,max:300},tabOffset:{min:0,max:20}};function C(n,e,t){if(!w(t,n[e]))return!1;let{min:i,max:o}=pe[e];return n[e]=p(t,i,o,e),!0}function P(n,e,t){return w(t,n[e])?(n[e]=t,!0):!1}function z(n,e){C(n,"trajectoryPredictionTime",e.trajectoryPredictionTime),C(n,"positionHistorySize",e.positionHistorySize),C(n,"scrollMargin",e.scrollMargin),C(n,"tabOffset",e.tabOffset),P(n,"enableMousePrediction",e.enableMousePrediction),P(n,"enableScrollPrediction",e.enableScrollPrediction),P(n,"enableTabPrediction",e.enableTabPrediction),P(n,"enableManagerLogging",e.enableManagerLogging),e.defaultHitSlop!==void 0&&(n.defaultHitSlop=E(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 V(n,e){let t=[],i=!1,o=!1,r=!1,a=!1,c=!1;if(!e)return{changedSettings:t,positionHistorySizeChanged:i,scrollPredictionChanged:o,tabPredictionChanged:r,hitSlopChanged:a,touchStrategyChanged:c};let h=["trajectoryPredictionTime","positionHistorySize","scrollMargin","tabOffset"];for(let s of h){let u=n[s];C(n,s,e[s])&&(t.push({setting:s,oldValue:u,newValue:n[s]}),s==="positionHistorySize"&&(i=!0))}let l=["enableMousePrediction","enableScrollPrediction","enableTabPrediction"];for(let s of l){let u=n[s];P(n,s,e[s])&&(t.push({setting:s,oldValue:u,newValue:n[s]}),s==="enableScrollPrediction"&&(o=!0),s==="enableTabPrediction"&&(r=!0))}if(e.defaultHitSlop!==void 0){let s=n.defaultHitSlop,u=E(e.defaultHitSlop);b(s,u)||(n.defaultHitSlop=u,t.push({setting:"defaultHitSlop",oldValue:s,newValue:u}),a=!0)}if(e.touchDeviceStrategy!==void 0){let s=n.touchDeviceStrategy;n.touchDeviceStrategy=e.touchDeviceStrategy,t.push({setting:"touchDeviceStrategy",oldValue:s,newValue:e.touchDeviceStrategy}),c=!0}if(e.minimumConnectionType!==void 0){let s=n.minimumConnectionType;n.minimumConnectionType=e.minimumConnectionType,t.push({setting:"minimumConnectionType",oldValue:s,newValue:e.minimumConnectionType})}return{changedSettings:t,positionHistorySizeChanged:i,scrollPredictionChanged:o,tabPredictionChanged:r,hitSlopChanged:a,touchStrategyChanged:c}}var d=class{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.settings=e.settings}get isConnected(){return this._isConnected}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0}devLog(e){if(this.settings.enableManagerLogging){if(this._cachedLogStyle===null){let t=this.moduleName.includes("Predictor")?"#ea580c":"#2563eb";this._cachedLogStyle=`color: ${t}; 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}`))}};function M(n,e,t){let i=0,o=1,r=e.x-n.x,a=e.y-n.y,c=(h,l)=>{if(h===0){if(l<0)return!1}else{let s=l/h;if(h<0){if(s>o)return!1;s>i&&(i=s)}else{if(s<i)return!1;s<o&&(o=s)}}return!0};return!c(-r,n.x-t.left)||!c(r,t.right-n.x)||!c(-a,n.y-t.top)||!c(a,t.bottom-n.y)?!1:i<=o}function $(n,e,t,i){let o=performance.now();e.add({point:{x:n.x,y:n.y},time:o});let{x:r,y:a}=n;if(e.length<2){i.x=r,i.y=a;return}let[c,h]=e.getFirstLast();if(!c||!h){i.x=r,i.y=a;return}let l=(h.time-c.time)*.001;if(l===0){i.x=r,i.y=a;return}let s=h.point.x-c.point.x,u=h.point.y-c.point.y,W=s/l,J=u/l,H=t*.001;i.x=r+W*H,i.y=a+J*H}var k=class extends d{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}updatePointerState(t){let i=this.trajectoryPositions.currentPoint;i.x=t.clientX,i.y=t.clientY,this.settings.enableMousePrediction?$(i,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=i.x,this.trajectoryPositions.predictedPoint.y=i.y)}processMouseMovement(t){this.updatePointerState(t);let i=this.settings.enableMousePrediction,o=this.trajectoryPositions.currentPoint;for(let r of this.elements.values()){if(!r.isIntersectingWithViewport||!r.callbackInfo.isCallbackActive||r.callbackInfo.isRunningCallback)continue;let a=r.elementBounds.expandedRect;if(i)M(o,this.trajectoryPositions.predictedPoint,a)&&this.callCallback(r,{kind:"mouse",subType:"trajectory"});else if(T(o,a)){this.callCallback(r,{kind:"mouse",subType:"hover"});return}}this.hasListeners("mouseTrajectoryUpdate")&&this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:i,trajectoryPositions:this.trajectoryPositions})}onDisconnect(){}onConnect(){}};function G(n,e){let i=e.top-n.top,o=e.left-n.left;return i<-1?"down":i>1?"up":o<-1?"right":o>1?"left":"none"}function Y(n,e,t){let{x:i,y:o}=n,r={x:i,y:o};switch(e){case"down":r.y+=t;break;case"up":r.y-=t;break;case"left":r.x-=t;break;case"right":r.x+=t;break;case"none":break;default:}return r}var R=class extends d{constructor(t){super(t.dependencies);this.moduleName="ScrollPredictor";this.predictedScrollPoint=null;this.scrollDirection=null;this.onDisconnect=()=>this.resetScrollProps();this.trajectoryPositions=t.trajectoryPositions}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(t,i){!t.isIntersectingWithViewport||t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive||(this.scrollDirection=this.scrollDirection??G(t.elementBounds.originalRect,i),this.scrollDirection!=="none"&&(this.predictedScrollPoint=this.predictedScrollPoint??Y(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),M(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.hasListeners("scrollTrajectoryUpdate")&&this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})))}onConnect(){}};import{tabbable as me}from"tabbable";function X(n,e,t,i){if(e!==null&&e>-1){let o=n?e-1:e+1;if(o>=0&&o<t.length&&t[o]===i)return o}return t.findIndex(o=>o===i)}var x=class extends d{constructor(t){super(t);this.moduleName="TabPredictor";this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=t=>{t.key==="Tab"&&(this.lastKeyDown=t)};this.handleFocusIn=t=>{if(!this.lastKeyDown)return;let i=t.target;if(!(i instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.devLog("Caching tabbable elements"),this.tabbableElementsCache=me(document.documentElement));let o=this.lastKeyDown.shiftKey,r=X(o,this.lastFocusedIndex,this.tabbableElementsCache,i);this.lastFocusedIndex=r,this.lastKeyDown=null;let a=[],c=this.settings.tabOffset,h=this.elements;for(let l=0;l<=c;l++){let s=o?r-l:r+l,u=this.tabbableElementsCache[s];u&&u instanceof Element&&h.has(u)&&a.push(u)}for(let l of a){let s=h.get(l);s&&!s.callbackInfo.isRunningCallback&&s.callbackInfo.isCallbackActive&&this.callCallback(s,{kind:"tab",subType:o?"reverse":"forwards"})}}}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}};import{PositionObserver as ge}from"position-observer";var D=class{constructor(e){this.head=0;this.count=0;if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=e,this.buffer=new Array(e)}add(e){this.buffer[this.head]=e,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let e=(this.head-1+this.capacity)%this.capacity;return this.buffer[e]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let i=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[i,i]}let e=this.getFirst(),t=this.getLast();return[e,t]}resize(e){if(e<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(e===this.capacity)return;let t=this.getAllItems();if(this.capacity=e,this.buffer=new Array(e),this.head=0,this.count=0,t.length>e){let i=t.slice(-e);for(let o of i)this.add(o)}else for(let i of t)this.add(i)}getAllItems(){if(this.count===0)return[];let e=new Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)e[t]=this.buffer[t];else{let t=this.head;for(let i=0;i<this.capacity;i++){let o=(t+i)%this.capacity;e[i]=this.buffer[o]}}return e}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};var m=class extends d{constructor(t){super(t);this.moduleName="DesktopHandler";this.positionObserver=null;this.trajectoryPositions={positions:new D(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.handlePositionChange=t=>{let i=this.settings.enableScrollPrediction;for(let o of t){let r=this.elements.get(o.target);r&&(i?this.scrollPredictor?.handleScrollPrefetch(r,o.boundingClientRect):this.checkForMouseHover(r),this.handlePositionChangeDataUpdates(r,o))}i&&this.scrollPredictor?.resetScrollProps()};this.checkForMouseHover=t=>{T(this.trajectoryPositions.currentPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"mouse",subType:"hover"})};this.handlePositionChangeDataUpdates=(t,i)=>{let o=[],r=i.isIntersecting;t.isIntersectingWithViewport!==r&&(o.push("visibility"),t.isIntersectingWithViewport=r),r&&!b(i.boundingClientRect,t.elementBounds.originalRect)&&(o.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:i.boundingClientRect,expandedRect:g(i.boundingClientRect,t.elementBounds.hitSlop)}),o.length&&this.hasListeners("elementDataUpdated")&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:o})};this.processMouseMovement=t=>this.mousePredictor.processMouseMovement(t);this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache();this.observeElement=t=>this.positionObserver?.observe(t);this.unobserveElement=t=>this.positionObserver?.unobserve(t);this.connectTabPredictor=()=>this.tabPredictor.connect();this.connectScrollPredictor=()=>this.scrollPredictor.connect();this.connectMousePredictor=()=>this.mousePredictor.connect();this.disconnectTabPredictor=()=>this.tabPredictor.disconnect();this.disconnectScrollPredictor=()=>this.scrollPredictor.disconnect();this.disconnectMousePredictor=()=>this.mousePredictor.disconnect();this.tabPredictor=new x(t),this.scrollPredictor=new R({dependencies:t,trajectoryPositions:this.trajectoryPositions}),this.mousePredictor=new k({dependencies:t,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new ge(this.handlePositionChange);let t=["mouse"];this.settings.enableTabPrediction&&t.push("tab"),this.settings.enableScrollPrediction&&t.push("scroll"),this.devLog(`Connected predictors: [${t.join(", ")}] and PositionObserver`);for(let i of this.elements.keys())this.positionObserver.observe(i)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}};var L=class extends d{constructor(t){super(t);this.moduleName="ViewportPredictor";this.intersectionObserver=null;this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter);this.observeElement=t=>this.intersectionObserver?.observe(t);this.unobserveElement=t=>this.intersectionObserver?.unobserve(t);this.handleViewportEnter=t=>{for(let i of t){if(!i.isIntersecting)continue;let o=this.elements.get(i.target);o&&(this.callCallback(o,{kind:"viewport"}),this.unobserveElement(i.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}};var _=class extends d{constructor(t){super(t);this.moduleName="TouchStartPredictor";this.onConnect=()=>this.createAbortController();this.onDisconnect=()=>{};this.handleTouchStart=t=>{let i=t.currentTarget,o=this.elements.get(i);o&&(this.callCallback(o,{kind:"touch"}),this.unobserveElement(i))}}observeElement(t){t instanceof HTMLElement&&t.addEventListener("pointerdown",this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(t){t instanceof HTMLElement&&t.removeEventListener("pointerdown",this.handleTouchStart)}};var f=class extends d{constructor(t){super(t);this.moduleName="TouchDeviceHandler";this.predictor=null;this.onDisconnect=()=>{this.devLog("Disconnecting touch predictor"),this.predictor?.disconnect()};this.onConnect=()=>this.setTouchPredictor();this.observeElement=t=>this.predictor?.observeElement(t);this.unobserveElement=t=>this.predictor?.unobserveElement(t);this.viewportPredictor=new L(t),this.touchStartPredictor=new _(t),this.predictor=this.viewportPredictor}setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case"viewport":this.predictor=this.viewportPredictor,this.devLog("Connected touch strategy: viewport (ViewportPredictor)");break;case"onTouchStart":this.predictor=this.touchStartPredictor,this.devLog("Connected touch strategy: onTouchStart (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 t of this.elements.keys())this.predictor?.observeElement(t)}};var S=class n{constructor(e){this.elements=new Map;this.checkableElements=new Set;this.idCounter=0;this.activeElementCount=0;this.isSetup=!1;this.pendingPointerEvent=null;this.rafId=null;this.domObserver=null;this.currentDeviceStrategy=O()?"touch":"mouse";this.eventEmitter=new I;this._globalCallbackHits=j();this._globalSettings=B();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.setDeviceStrategy(this.currentDeviceStrategy=e.pointerType)),!this.rafId&&(this.rafId=requestAnimationFrame(()=>{if(this.handler instanceof f){this.rafId=null;return}this.pendingPointerEvent&&this.handler.processMouseMovement(this.pendingPointerEvent),this.rafId=null}))};this.handleDomMutations=e=>{if(!e.length)return;this.desktopHandler?.invalidateTabCache();let t=!1;for(let i=0;i<e.length;i++){let o=e[i];if(o&&o.type==="childList"&&o.removedNodes.length>0){t=!0;break}}if(t)for(let i of this.elements.keys())i.isConnected||this.unregister(i,"disconnected")};e!==void 0&&z(this._globalSettings,e);let t={elements:this.elements,checkableElements:this.checkableElements,callCallback:this.callCallback.bind(this),emit:this.eventEmitter.emit.bind(this.eventEmitter),hasListeners:this.eventEmitter.hasListeners.bind(this.eventEmitter),updateCheckableStatus:this.updateCheckableStatus.bind(this),settings:this._globalSettings};this.desktopHandler=new m(t),this.touchDeviceHandler=new f(t),this.handler=this.currentDeviceStrategy==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`),this.initializeGlobalListeners()}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}`}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(){return{registeredElements:this.elements,globalSettings:this._globalSettings,globalCallbackHits:this._globalCallbackHits,eventListeners:this.eventEmitter.getEventListeners(),currentDeviceStrategy:this.currentDeviceStrategy,activeElementCount:this.activeElementCount}}get registeredElements(){return this.elements}register(e){let{isTouchDevice:t,isLimitedConnection:i,shouldRegister:o}=N();if(!o)return{isLimitedConnection:i,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};let r=this.elements.get(e.element);if(r)return r.registerCount++,{isLimitedConnection:i,isTouchDevice:t,isRegistered:!1,unregister:()=>{}};this.isSetup||this.initializeGlobalListeners();let a=K(e,this.generateId(),this._globalSettings.defaultHitSlop);return this.elements.set(e.element,a),this.activeElementCount++,this.updateCheckableStatus(a),this.handler.observeElement(e.element),this.eventEmitter.emit({type:"elementRegistered",timestamp:Date.now(),elementData:a}),{isTouchDevice:t,isLimitedConnection:i,isRegistered:!0,unregister:()=>{this.unregister(e.element)}}}unregister(e,t){let i=this.elements.get(e);if(!i)return;this.clearReactivateTimeout(i),this.handler.unobserveElement(e),this.elements.delete(e),this.checkableElements.delete(i),i.callbackInfo.isCallbackActive&&this.activeElementCount--;let o=this.elements.size===0&&this.isSetup;o&&(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:o})}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.handler.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(),o,r=null;try{await e.callback(),o="success"}catch(a){r=a instanceof Error?a.message:String(a),o="error",console.error(`Error in callback for element ${e.name}:`,a)}this.finalizeCallback(e,t,i,o,r)}finalizeCallback(e,t,i,o,r){e.callbackInfo.lastCallbackCompletedAt=Date.now(),e.callbackInfo.isRunningCallback=!1,e.callbackInfo.isCallbackActive=!1,this.activeElementCount--,this.handler.unobserveElement(e.element),e.callbackInfo.reactivateAfter!==1/0&&(e.callbackInfo.reactivateTimeoutId=setTimeout(()=>{this.reactivate(e.element)},e.callbackInfo.reactivateAfter));let a=this.activeElementCount===0;a&&(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=o,errorMessage:e.callbackInfo.lastCallbackErrorMessage=r,wasLastActiveElement:a})}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++}setDeviceStrategy(e){let t=this.handler instanceof m?"mouse":"touch";t!==e&&this.devLog(`Switching device strategy from ${t} to ${e}`),this.handler.disconnect(),this.handler=e==="mouse"?this.desktopHandler:this.touchDeviceHandler,this.handler.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.handler.disconnect(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.pendingPointerEvent=null)}alterGlobalSettings(e){let t=V(this._globalSettings,e);t.positionHistorySizeChanged&&this.desktopHandler.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize),t.scrollPredictionChanged&&this.handler instanceof m&&(this._globalSettings.enableScrollPrediction?this.handler.connectScrollPredictor():this.handler.disconnectScrollPredictor()),t.tabPredictionChanged&&this.handler instanceof m&&(this._globalSettings.enableTabPrediction?this.handler.connectTabPredictor():this.handler.disconnectTabPredictor()),t.hitSlopChanged&&this.forceUpdateAllElementBounds(),t.touchStrategyChanged&&this.handler instanceof f&&this.handler.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=g(t,e.elementBounds.hitSlop);if(!b(i,e.elementBounds.expandedRect)){let o={...e,elementBounds:{...e.elementBounds,originalRect:t,expandedRect:i}};this.elements.set(e.element,o),this.eventEmitter.emit({type:"elementDataUpdated",elementData:o,updatedProps:["bounds"]})}}devLog(e){this._globalSettings.enableManagerLogging&&console.log(`%c\u{1F6E0}\uFE0F ForesightManager: ${e}`,"color: #16a34a; font-weight: bold;")}};export{S as ForesightManager};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.foresight",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
4
4
|
"description": "Predicts where users will click based on mouse movement, keyboard navigation, and scroll behavior. Includes touch device support. Triggers callbacks before interactions happen to enable prefetching and faster UI responses. Works with any framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -45,20 +45,20 @@
|
|
|
45
45
|
"llmsFull": "https://foresightjs.com/llms-full.txt",
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@testing-library/dom": "^10.4.1",
|
|
48
|
-
"@testing-library/jest-dom": "^6.
|
|
49
|
-
"@types/node": "^24.
|
|
50
|
-
"@vitest/coverage-v8": "
|
|
51
|
-
"@vitest/ui": "^
|
|
52
|
-
"happy-dom": "^
|
|
53
|
-
"jsdom": "^
|
|
48
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
49
|
+
"@types/node": "^24.10.1",
|
|
50
|
+
"@vitest/coverage-v8": "4.0.16",
|
|
51
|
+
"@vitest/ui": "^4.0.9",
|
|
52
|
+
"happy-dom": "^20.0.10",
|
|
53
|
+
"jsdom": "^27.4.0",
|
|
54
54
|
"tslib": "^2.8.1",
|
|
55
|
-
"tsup": "^8.5.
|
|
56
|
-
"typescript": "^5.9.
|
|
57
|
-
"vitest": "^
|
|
55
|
+
"tsup": "^8.5.1",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"vitest": "^4.0.9"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"position-observer": "^1.0.3",
|
|
61
|
-
"tabbable": "^6.
|
|
61
|
+
"tabbable": "^6.3.0"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "tsup --sourcemap",
|