js.foresight 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.md +133 -130
- package/dist/index.d.ts +31 -21
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +8 -10
- package/dist/index.d.mts +0 -398
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/manager/ForesightManager.ts","../src/helpers/shouldRegister.ts","../src/helpers/clampNumber.ts","../src/helpers/lineSigmentIntersectsRect.ts","../src/helpers/predictNextMousePosition.ts","../src/helpers/rectAndHitSlop.ts","../src/helpers/shouldUpdateSetting.ts","../src/helpers/getFocusedElementIndex.ts","../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/helpers/initialViewportState.ts"],"sourcesContent":["export { ForesightManager } from \"./manager/ForesightManager\"\r\nexport type {\r\n Rect as ForesightRect,\r\n ForesightElement,\r\n ForesightRegisterResult,\r\n UpdateForsightManagerSettings,\r\n ForesightManagerSettings,\r\n ForesightRegisterOptions,\r\n ForesightRegisterOptionsWithoutElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ElementRegisteredEvent,\r\n ElementUnregisteredEvent,\r\n ElementDataUpdatedEvent,\r\n CallbackInvokedEvent,\r\n CallbackCompletedEvent,\r\n MouseTrajectoryUpdateEvent,\r\n ScrollTrajectoryUpdateEvent,\r\n ManagerSettingsChangedEvent,\r\n UpdatedManagerSetting,\r\n HitSlop,\r\n CallbackHitType,\r\n CallbackHits,\r\n} from \"./types/types\"\r\n","import { tabbable, type FocusableElement } from \"tabbable\"\r\nimport { evaluateRegistrationConditions } from \"../helpers/shouldRegister\"\r\nimport type {\r\n CallbackHits,\r\n ElementUnregisteredReason,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEventMap,\r\n ForesightEvent,\r\n ForesightManagerData,\r\n ForesightManagerSettings,\r\n ForesightRegisterOptions,\r\n ForesightRegisterResult,\r\n CallbackHitType,\r\n ManagerBooleanSettingKeys,\r\n NumericSettingKeys,\r\n Point,\r\n ScrollDirection,\r\n TrajectoryPositions,\r\n UpdateForsightManagerSettings,\r\n ForesightEventListener,\r\n UpdatedDataPropertyNames,\r\n UpdatedManagerSetting,\r\n NumericSettingConfig,\r\n} from \"../types/types\"\r\nimport {\r\n DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n DEFAULT_ENABLE_TAB_PREDICTION,\r\n DEFAULT_HITSLOP,\r\n DEFAULT_POSITION_HISTORY_SIZE,\r\n DEFAULT_SCROLL_MARGIN,\r\n DEFAULT_TAB_OFFSET,\r\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n MAX_POSITION_HISTORY_SIZE,\r\n MAX_SCROLL_MARGIN,\r\n MAX_TAB_OFFSET,\r\n MAX_TRAJECTORY_PREDICTION_TIME,\r\n MIN_POSITION_HISTORY_SIZE,\r\n MIN_SCROLL_MARGIN,\r\n MIN_TAB_OFFSET,\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n} from \"../constants\"\r\nimport { clampNumber } from \"../helpers/clampNumber\"\r\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\r\nimport {\r\n areRectsEqual,\r\n getExpandedRect,\r\n isPointInRectangle,\r\n normalizeHitSlop,\r\n} from \"../helpers/rectAndHitSlop\"\r\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\r\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\r\nimport { getScrollDirection } from \"../helpers/getScrollDirection\"\r\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\r\nimport { PositionObserver, PositionObserverEntry } from \"position-observer\"\r\nimport { initialViewportState } from \"../helpers/initialViewportState\"\r\n\r\n/**\r\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\r\n *\r\n * ForesightManager is a singleton class responsible for:\r\n * - Registering HTML elements to monitor.\r\n * - Tracking mouse movements and predicting future cursor positions.\r\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\r\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\r\n * - Optionally unregistering elements after their callback is triggered.\r\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\r\n * - Automatically updating element bounds on resize using {@link ResizeObserver}.\r\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\r\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\r\n *\r\n * It should be initialized once using {@link ForesightManager.initialize} and then\r\n * accessed via the static getter {@link ForesightManager.instance}.\r\n */\r\n\r\nexport class ForesightManager {\r\n private static manager: ForesightManager\r\n private elements: Map<ForesightElement, ForesightElementData> = new Map()\r\n\r\n private isSetup: boolean = false\r\n private _globalCallbackHits: CallbackHits = {\r\n mouse: {\r\n hover: 0,\r\n trajectory: 0,\r\n },\r\n tab: {\r\n forwards: 0,\r\n reverse: 0,\r\n },\r\n scroll: {\r\n down: 0,\r\n left: 0,\r\n right: 0,\r\n up: 0,\r\n },\r\n total: 0,\r\n }\r\n private _globalSettings: ForesightManagerSettings = {\r\n debug: false,\r\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\r\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n scrollMargin: DEFAULT_SCROLL_MARGIN,\r\n defaultHitSlop: {\r\n top: DEFAULT_HITSLOP,\r\n left: DEFAULT_HITSLOP,\r\n right: DEFAULT_HITSLOP,\r\n bottom: DEFAULT_HITSLOP,\r\n },\r\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\r\n tabOffset: DEFAULT_TAB_OFFSET,\r\n }\r\n private trajectoryPositions: TrajectoryPositions = {\r\n positions: [],\r\n currentPoint: { x: 0, y: 0 },\r\n predictedPoint: { x: 0, y: 0 },\r\n }\r\n\r\n private tabbableElementsCache: FocusableElement[] = []\r\n private lastFocusedIndex: number | null = null\r\n\r\n private predictedScrollPoint: Point | null = null\r\n private scrollDirection: ScrollDirection | null = null\r\n private domObserver: MutationObserver | null = null\r\n private positionObserver: PositionObserver | null = null\r\n // Track the last keydown event to determine if focus change was due to Tab\r\n private lastKeyDown: KeyboardEvent | null = null\r\n\r\n // AbortController for managing global event listeners\r\n private globalListenersController: AbortController = new AbortController()\r\n\r\n // RequestAnimationFrame throttling for mouse events\r\n private rafId: number | null = null\r\n private pendingMouseEvent: MouseEvent | null = null\r\n\r\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\r\n\r\n // Never put something in the constructor, use initialize instead\r\n private constructor() {}\r\n\r\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\r\n if (!this.isInitiated) {\r\n ForesightManager.manager = new ForesightManager()\r\n }\r\n if (props !== undefined) {\r\n ForesightManager.manager.alterGlobalSettings(props)\r\n }\r\n return ForesightManager.manager\r\n }\r\n\r\n public addEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>,\r\n options?: { signal?: AbortSignal }\r\n ) {\r\n if (options?.signal?.aborted) {\r\n return () => {}\r\n }\r\n const listeners = this.eventListeners.get(eventType) ?? []\r\n listeners.push(listener as ForesightEventListener)\r\n this.eventListeners.set(eventType, listeners)\r\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\r\n }\r\n\r\n public removeEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>\r\n ): void {\r\n const listeners = this.eventListeners.get(eventType)\r\n if (!listeners) {\r\n return\r\n }\r\n const index = listeners.indexOf(listener as ForesightEventListener)\r\n if (index > -1) {\r\n listeners.splice(index, 1)\r\n }\r\n }\r\n\r\n private emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\r\n const listeners = this.eventListeners.get(event.type)\r\n if (!listeners) {\r\n return\r\n }\r\n listeners.forEach(listener => {\r\n try {\r\n listener(event)\r\n } catch (error) {\r\n console.error(`Error in ForesightManager event listener for ${event.type}:`, error)\r\n }\r\n })\r\n }\r\n\r\n public get getManagerData(): Readonly<ForesightManagerData> {\r\n return {\r\n registeredElements: this.elements,\r\n globalSettings: this._globalSettings,\r\n globalCallbackHits: this._globalCallbackHits,\r\n eventListeners: this.eventListeners,\r\n }\r\n }\r\n\r\n public static get isInitiated(): Readonly<boolean> {\r\n return !!ForesightManager.manager\r\n }\r\n\r\n public static get instance(): ForesightManager {\r\n return this.initialize()\r\n }\r\n\r\n public get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData> {\r\n return this.elements\r\n }\r\n\r\n public register({\r\n element,\r\n callback,\r\n hitSlop,\r\n name,\r\n }: ForesightRegisterOptions): ForesightRegisterResult {\r\n const { shouldRegister, isTouchDevice, isLimitedConnection } = evaluateRegistrationConditions()\r\n if (!shouldRegister) {\r\n return {\r\n isLimitedConnection,\r\n isTouchDevice,\r\n isRegistered: false,\r\n unregister: () => {},\r\n }\r\n }\r\n\r\n // Setup global listeners on every first element added to the manager. It gets removed again when the map is emptied\r\n if (!this.isSetup) {\r\n this.initializeGlobalListeners()\r\n }\r\n\r\n const initialRect = element.getBoundingClientRect()\r\n\r\n const normalizedHitSlop = hitSlop\r\n ? normalizeHitSlop(hitSlop)\r\n : this._globalSettings.defaultHitSlop\r\n\r\n const elementData: ForesightElementData = {\r\n element: element,\r\n callback,\r\n elementBounds: {\r\n originalRect: initialRect,\r\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\r\n hitSlop: normalizedHitSlop,\r\n },\r\n isHovering: false,\r\n trajectoryHitData: {\r\n isTrajectoryHit: false,\r\n trajectoryHitTime: 0,\r\n trajectoryHitExpirationTimeoutId: undefined,\r\n },\r\n name: name || element.id || \"unnamed\",\r\n isIntersectingWithViewport: initialViewportState(initialRect),\r\n isRunningCallback: false,\r\n registerCount: (this.registeredElements.get(element)?.registerCount ?? 0) + 1,\r\n }\r\n\r\n this.elements.set(element, elementData)\r\n\r\n this.positionObserver?.observe(element)\r\n\r\n this.emit({\r\n type: \"elementRegistered\",\r\n timestamp: Date.now(),\r\n elementData,\r\n })\r\n\r\n return {\r\n isTouchDevice,\r\n isLimitedConnection,\r\n isRegistered: true,\r\n unregister: () => this.unregister(element, \"apiCall\"),\r\n }\r\n }\r\n\r\n private unregister(element: ForesightElement, unregisterReason: ElementUnregisteredReason) {\r\n if (!this.elements.has(element)) {\r\n return\r\n }\r\n\r\n const elementData = this.elements.get(element)\r\n\r\n // Clear any pending trajectory expiration timeout\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\r\n }\r\n\r\n this.positionObserver?.unobserve(element)\r\n this.elements.delete(element)\r\n\r\n if (this.elements.size === 0 && this.isSetup) {\r\n this.removeGlobalListeners()\r\n }\r\n\r\n if (elementData) {\r\n this.emit({\r\n type: \"elementUnregistered\",\r\n elementData: elementData,\r\n timestamp: Date.now(),\r\n unregisterReason: unregisterReason,\r\n })\r\n }\r\n }\r\n\r\n private updateNumericSettings(\r\n newValue: number | undefined,\r\n setting: NumericSettingKeys,\r\n min: number,\r\n max: number\r\n ) {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n\r\n this._globalSettings[setting] = clampNumber(newValue, min, max, setting)\r\n\r\n return true\r\n }\r\n\r\n private updateBooleanSetting(\r\n newValue: boolean | undefined,\r\n setting: ManagerBooleanSettingKeys\r\n ): boolean {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n this._globalSettings[setting] = newValue\r\n return true\r\n }\r\n\r\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\r\n const changedSettings: UpdatedManagerSetting[] = []\r\n const NUMERIC_SETTING_CONFIGS: readonly NumericSettingConfig[] = [\r\n {\r\n setting: \"positionHistorySize\",\r\n min: MIN_POSITION_HISTORY_SIZE,\r\n max: MAX_POSITION_HISTORY_SIZE,\r\n },\r\n {\r\n setting: \"trajectoryPredictionTime\",\r\n min: MIN_TRAJECTORY_PREDICTION_TIME,\r\n max: MAX_TRAJECTORY_PREDICTION_TIME,\r\n },\r\n {\r\n setting: \"scrollMargin\",\r\n min: MIN_SCROLL_MARGIN,\r\n max: MAX_SCROLL_MARGIN,\r\n },\r\n {\r\n setting: \"tabOffset\",\r\n min: MIN_TAB_OFFSET,\r\n max: MAX_TAB_OFFSET,\r\n },\r\n ]\r\n\r\n NUMERIC_SETTING_CONFIGS.forEach(({ setting, min, max }) => {\r\n const newValue = props?.[setting]\r\n if (newValue === undefined) return\r\n\r\n const oldValue = this._globalSettings[setting]\r\n const changed = this.updateNumericSettings(newValue, setting, min, max)\r\n\r\n if (changed) {\r\n changedSettings.push({\r\n setting,\r\n oldValue,\r\n newValue: this._globalSettings[setting],\r\n })\r\n\r\n if (\r\n setting === \"positionHistorySize\" &&\r\n this._globalSettings.positionHistorySize < oldValue\r\n ) {\r\n const newSize = this._globalSettings.positionHistorySize\r\n if (this.trajectoryPositions.positions.length > newSize) {\r\n this.trajectoryPositions.positions = this.trajectoryPositions.positions.slice(-newSize)\r\n }\r\n }\r\n }\r\n })\r\n\r\n const booleanSettings: ManagerBooleanSettingKeys[] = [\r\n \"enableMousePrediction\",\r\n \"enableScrollPrediction\",\r\n \"enableTabPrediction\",\r\n ]\r\n\r\n booleanSettings.forEach(setting => {\r\n const newValue = props?.[setting]\r\n if (newValue === undefined) return\r\n\r\n const oldValue = this._globalSettings[setting]\r\n const changed = this.updateBooleanSetting(newValue, setting)\r\n\r\n if (changed) {\r\n changedSettings.push({ setting, oldValue, newValue: this._globalSettings[setting] })\r\n }\r\n })\r\n\r\n if (props?.defaultHitSlop !== undefined) {\r\n const oldHitSlop = this._globalSettings.defaultHitSlop\r\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\r\n\r\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\r\n this._globalSettings.defaultHitSlop = normalizedNewHitSlop\r\n changedSettings.push({\r\n setting: \"defaultHitSlop\",\r\n oldValue: oldHitSlop,\r\n newValue: normalizedNewHitSlop,\r\n })\r\n this.forceUpdateAllElementBounds()\r\n }\r\n }\r\n\r\n if (changedSettings.length > 0) {\r\n this.emit({\r\n type: \"managerSettingsChanged\",\r\n timestamp: Date.now(),\r\n managerData: this.getManagerData,\r\n updatedSettings: changedSettings,\r\n })\r\n }\r\n }\r\n\r\n private forceUpdateAllElementBounds() {\r\n for (const [_, elementData] of this.elements) {\r\n if (elementData.isIntersectingWithViewport) {\r\n this.forceUpdateElementBounds(elementData)\r\n }\r\n }\r\n }\r\n\r\n private updatePointerState(e: MouseEvent): void {\r\n const currentPoint = { x: e.clientX, y: e.clientY }\r\n this.trajectoryPositions.currentPoint = currentPoint\r\n\r\n if (this._globalSettings.enableMousePrediction) {\r\n this.trajectoryPositions.predictedPoint = predictNextMousePosition(\r\n currentPoint,\r\n this.trajectoryPositions.positions,\r\n this._globalSettings.positionHistorySize,\r\n this._globalSettings.trajectoryPredictionTime\r\n )\r\n } else {\r\n this.trajectoryPositions.predictedPoint = currentPoint\r\n }\r\n }\r\n\r\n private handleMouseMove = (e: MouseEvent) => {\r\n this.pendingMouseEvent = e\r\n // Throttle processing to animation frames for better performance\r\n if (this.rafId) return\r\n\r\n this.rafId = requestAnimationFrame(() => {\r\n if (this.pendingMouseEvent) {\r\n this.processMouseMovement(this.pendingMouseEvent)\r\n this.pendingMouseEvent = null\r\n }\r\n this.rafId = null\r\n })\r\n }\r\n\r\n private processMouseMovement(e: MouseEvent) {\r\n this.updatePointerState(e)\r\n\r\n // Use for...of instead of forEach for better performance in hot code path\r\n // Avoids function call overhead and iterator creation on every mouse move\r\n for (const currentData of this.elements.values()) {\r\n if (!currentData.isIntersectingWithViewport) {\r\n continue\r\n }\r\n const expandedRect = currentData.elementBounds.expandedRect\r\n\r\n if (!this._globalSettings.enableMousePrediction) {\r\n if (isPointInRectangle(this.trajectoryPositions.currentPoint, expandedRect)) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"hover\" })\r\n return\r\n }\r\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\r\n } else if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.trajectoryPositions.predictedPoint,\r\n expandedRect\r\n )\r\n ) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"trajectory\" })\r\n }\r\n }\r\n\r\n this.emit({\r\n type: \"mouseTrajectoryUpdate\",\r\n predictionEnabled: this._globalSettings.enableMousePrediction,\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n }\r\n\r\n /**\r\n * Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.\r\n *\r\n * @param mutationsList - Array of MutationRecord objects describing the DOM changes\r\n *\r\n */\r\n private handleDomMutations = (mutationsList: MutationRecord[]) => {\r\n // Invalidate tabbale elements cache\r\n if (mutationsList.length) {\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n }\r\n for (const mutation of mutationsList) {\r\n if (mutation.type === \"childList\" && mutation.removedNodes.length > 0) {\r\n for (const element of this.elements.keys()) {\r\n if (!element.isConnected) {\r\n this.unregister(element, \"disconnected\")\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\r\n // We dont use handleKeyDown for the full event because of 2 main reasons:\r\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\r\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\r\n private handleKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Tab\") {\r\n this.lastKeyDown = e\r\n }\r\n }\r\n\r\n private handleFocusIn = (e: FocusEvent) => {\r\n if (!this.lastKeyDown || !this._globalSettings.enableTabPrediction) {\r\n return\r\n }\r\n const targetElement = e.target\r\n if (!(targetElement instanceof HTMLElement)) {\r\n return\r\n }\r\n\r\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\r\n if (!this.tabbableElementsCache.length) {\r\n this.tabbableElementsCache = tabbable(document.documentElement)\r\n }\r\n\r\n // Determine the range of elements to check based on the tab direction and offset\r\n const isReversed = this.lastKeyDown.shiftKey\r\n\r\n const currentIndex: number = getFocusedElementIndex(\r\n isReversed,\r\n this.lastFocusedIndex,\r\n this.tabbableElementsCache,\r\n targetElement\r\n )\r\n\r\n this.lastFocusedIndex = currentIndex\r\n\r\n this.lastKeyDown = null\r\n const elementsToPredict: ForesightElement[] = []\r\n for (let i = 0; i <= this._globalSettings.tabOffset; i++) {\r\n if (isReversed) {\r\n const element = this.tabbableElementsCache[currentIndex - i]\r\n if (this.elements.has(element as ForesightElement)) {\r\n elementsToPredict.push(element as ForesightElement)\r\n }\r\n } else {\r\n const element = this.tabbableElementsCache[currentIndex + i]\r\n if (this.elements.has(element as ForesightElement)) {\r\n elementsToPredict.push(element as ForesightElement)\r\n }\r\n }\r\n }\r\n\r\n elementsToPredict.forEach(element => {\r\n const foresightElement = this.elements.get(element)\r\n if (foresightElement) {\r\n this.callCallback(foresightElement, {\r\n kind: \"tab\",\r\n subType: isReversed ? \"reverse\" : \"forwards\",\r\n })\r\n }\r\n })\r\n }\r\n\r\n private updateHitCounters(callbackHitType: CallbackHitType, elementData: ForesightElementData) {\r\n switch (callbackHitType.kind) {\r\n case \"mouse\":\r\n this._globalCallbackHits.mouse[callbackHitType.subType]++\r\n break\r\n case \"tab\":\r\n this._globalCallbackHits.tab[callbackHitType.subType]++\r\n break\r\n case \"scroll\":\r\n this._globalCallbackHits.scroll[callbackHitType.subType]++\r\n break\r\n default:\r\n callbackHitType satisfies never\r\n }\r\n this._globalCallbackHits.total++\r\n }\r\n\r\n private callCallback(elementData: ForesightElementData, callbackHitType: CallbackHitType) {\r\n if (elementData.isRunningCallback) {\r\n return\r\n }\r\n // We have this async wrapper so we can time exactly how long the callback takes\r\n elementData.isRunningCallback = true\r\n const asyncCallbackWrapper = async () => {\r\n this.updateHitCounters(callbackHitType, elementData)\r\n this.emit({\r\n type: \"callbackInvoked\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n })\r\n const start = performance.now()\r\n try {\r\n await elementData.callback()\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"success\",\r\n })\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : String(error)\r\n console.error(\r\n `Error in callback for element ${elementData.name} (${elementData.element.tagName}):`,\r\n error\r\n )\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"error\",\r\n errorMessage,\r\n })\r\n }\r\n this.unregister(elementData.element, \"callbackHit\")\r\n }\r\n asyncCallbackWrapper()\r\n }\r\n\r\n /**\r\n * ONLY use this function when you want to change the rect bounds via code, if the rects are changing because of updates in the DOM do not use this function.\r\n * We need an observer for that\r\n */\r\n private forceUpdateElementBounds(elementData: ForesightElementData) {\r\n const newOriginalRect = elementData.element.getBoundingClientRect()\r\n const expandedRect = getExpandedRect(newOriginalRect, elementData.elementBounds.hitSlop)\r\n\r\n if (!areRectsEqual(expandedRect, elementData.elementBounds.expandedRect)) {\r\n const updatedElementData = {\r\n ...elementData,\r\n elementBounds: {\r\n ...elementData.elementBounds,\r\n originalRect: newOriginalRect,\r\n expandedRect,\r\n },\r\n }\r\n this.elements.set(elementData.element, updatedElementData)\r\n\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: updatedElementData,\r\n updatedProps: [\"bounds\"],\r\n })\r\n }\r\n }\r\n\r\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\r\n for (const entry of entries) {\r\n const elementData = this.elements.get(entry.target)\r\n if (!elementData) {\r\n continue\r\n }\r\n\r\n // Always call handlePositionChangeDataUpdates before handleScrollPrefetch\r\n this.handlePositionChangeDataUpdates(elementData, entry)\r\n this.handleScrollPrefetch(elementData, entry.boundingClientRect)\r\n }\r\n // Reset scroll prefetch\r\n this.scrollDirection = null\r\n this.predictedScrollPoint = null\r\n }\r\n\r\n private handlePositionChangeDataUpdates = (\r\n elementData: ForesightElementData,\r\n entry: PositionObserverEntry\r\n ) => {\r\n const updatedProps: UpdatedDataPropertyNames[] = []\r\n const isNowIntersecting = entry.isIntersecting\r\n\r\n // Create updated element data with new intersection state\r\n let updatedElementData = {\r\n ...elementData,\r\n isIntersectingWithViewport: isNowIntersecting,\r\n }\r\n\r\n // Track visibility changes\r\n if (elementData.isIntersectingWithViewport !== isNowIntersecting) {\r\n updatedProps.push(\"visibility\")\r\n }\r\n\r\n // Handle bounds updates for intersecting elements\r\n if (isNowIntersecting) {\r\n updatedProps.push(\"bounds\")\r\n this.handleScrollPrefetch(updatedElementData, entry.boundingClientRect)\r\n updatedElementData = {\r\n ...updatedElementData,\r\n elementBounds: {\r\n hitSlop: elementData.elementBounds.hitSlop,\r\n originalRect: entry.boundingClientRect,\r\n expandedRect: getExpandedRect(\r\n entry.boundingClientRect,\r\n elementData.elementBounds.hitSlop\r\n ),\r\n },\r\n }\r\n }\r\n\r\n // Update state and emit once\r\n this.elements.set(elementData.element, updatedElementData)\r\n if (updatedProps.length) {\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: updatedElementData,\r\n updatedProps,\r\n })\r\n }\r\n }\r\n\r\n private handleScrollPrefetch(elementData: ForesightElementData, newRect: DOMRect) {\r\n if (!elementData.isIntersectingWithViewport) {\r\n return\r\n }\r\n if (!this._globalSettings.enableScrollPrediction) {\r\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\r\n if (\r\n isPointInRectangle(\r\n this.trajectoryPositions.currentPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"mouse\",\r\n subType: \"hover\",\r\n })\r\n }\r\n } else {\r\n // ONCE per handlePositionChange batch we decide what the scroll direction is\r\n this.scrollDirection =\r\n this.scrollDirection ?? getScrollDirection(elementData.elementBounds.originalRect, newRect)\r\n if (this.scrollDirection === \"none\") {\r\n return\r\n }\r\n // ONCE per handlePositionChange batch we decide the predicted scroll point\r\n this.predictedScrollPoint =\r\n this.predictedScrollPoint ??\r\n predictNextScrollPosition(\r\n this.trajectoryPositions.currentPoint,\r\n this.scrollDirection,\r\n this._globalSettings.scrollMargin\r\n )\r\n\r\n // Check if the scroll is going to intersect with an registered element\r\n if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.predictedScrollPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"scroll\",\r\n subType: this.scrollDirection,\r\n })\r\n }\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.predictedScrollPoint,\r\n scrollDirection: this.scrollDirection,\r\n })\r\n }\r\n }\r\n\r\n private initializeGlobalListeners() {\r\n if (this.isSetup) {\r\n return\r\n }\r\n // To avoid setting up listeners while ssr\r\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\r\n return\r\n }\r\n\r\n const { signal } = this.globalListenersController\r\n //TODO only add event listeners when the events are enabled (mouse/tab)\r\n document.addEventListener(\"mousemove\", this.handleMouseMove) // Dont add signal we still need to emit events even without elements\r\n document.addEventListener(\"keydown\", this.handleKeyDown, { signal })\r\n document.addEventListener(\"focusin\", this.handleFocusIn, { signal })\r\n\r\n //Mutation observer is to automatically unregister elements when they leave the DOM. Its a fail-safe for if the user forgets to do it.\r\n this.domObserver = new MutationObserver(this.handleDomMutations)\r\n this.domObserver.observe(document.documentElement, {\r\n childList: true,\r\n subtree: true,\r\n attributes: false,\r\n })\r\n\r\n // Handles all position based changes and update the rects of the elements. completely async to avoid dirtying the main thread.\r\n // Handles resize of elements\r\n // Handles resize of viewport\r\n // Handles scrolling\r\n this.positionObserver = new PositionObserver(this.handlePositionChange)\r\n\r\n this.isSetup = true\r\n }\r\n\r\n private removeGlobalListeners() {\r\n this.isSetup = false\r\n\r\n this.globalListenersController?.abort() // Remove all event listeners only in non debug mode\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n this.domObserver?.disconnect()\r\n this.domObserver = null\r\n this.positionObserver?.disconnect()\r\n this.positionObserver = null\r\n\r\n // Cancel any pending RAF and clean up mouse event throttling\r\n if (this.rafId) {\r\n cancelAnimationFrame(this.rafId)\r\n this.rafId = null\r\n }\r\n this.pendingMouseEvent = null\r\n }\r\n}\r\n","type ShouldRegister = {\r\n shouldRegister: boolean\r\n isTouchDevice: boolean\r\n isLimitedConnection: boolean\r\n}\r\n\r\nexport function evaluateRegistrationConditions(): ShouldRegister {\r\n const isTouchDevice = userUsesTouchDevice()\r\n const isLimitedConnection = hasConnectionLimitations()\r\n const shouldRegister = !isTouchDevice && !isLimitedConnection\r\n return { isTouchDevice, isLimitedConnection, shouldRegister }\r\n}\r\n\r\n/**\r\n * Detects if the current device is likely a touch-enabled device.\r\n * It checks for coarse pointer media query and the presence of touch points.\r\n *\r\n * @returns `true` if the device is likely touch-enabled, `false` otherwise.\r\n */\r\nfunction userUsesTouchDevice(): boolean {\r\n return window.matchMedia(\"(pointer: coarse)\").matches && navigator.maxTouchPoints > 0\r\n}\r\n\r\n/**\r\n * Checks if the user has connection limitations (slow network or data saver enabled).\r\n *\r\n * @returns {boolean} True if connection is limited, false if safe to prefetch\r\n * @example\r\n * if (!hasConnectionLimitations()) {\r\n * prefetchResource('/api/data');\r\n * }\r\n */\r\nfunction hasConnectionLimitations(): boolean {\r\n const connection = (navigator as any).connection\r\n if (!connection) return false\r\n\r\n return /2g/.test(connection.effectiveType) || connection.saveData\r\n}\r\n","export function clampNumber(\r\n number: number,\r\n lowerBound: number,\r\n upperBound: number,\r\n settingName: string\r\n) {\r\n if (number < lowerBound) {\r\n console.warn(\r\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\r\n )\r\n } else if (number > upperBound) {\r\n console.warn(\r\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\r\n )\r\n }\r\n\r\n return Math.min(Math.max(number, lowerBound), upperBound)\r\n}\r\n","import type { Point, Rect } from \"../types/types\"\r\n\r\n/**\r\n * Determines if a line segment intersects with a given rectangle.\r\n * This function implements the Liang-Barsky line clipping algorithm.\r\n *\r\n * @param p1 - The starting {@link Point} of the line segment.\r\n * @param p2 - The ending {@link Point} of the line segment.\r\n * @param rect - The {@link Rect} to check for intersection.\r\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\r\n */\r\nexport function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rect): boolean {\r\n let t0 = 0.0\r\n let t1 = 1.0\r\n const dx = p2.x - p1.x\r\n const dy = p2.y - p1.y\r\n\r\n const clipTest = (p: number, q: number): boolean => {\r\n if (p === 0) {\r\n // Line is parallel to this clipping edge\r\n if (q < 0) return false // Line is outside the clipping edge\r\n } else {\r\n const r = q / p\r\n if (p < 0) {\r\n // Line proceeds from outside to inside\r\n if (r > t1) return false // Line segment ends before crossing edge\r\n if (r > t0) t0 = r // Update entry point\r\n } else {\r\n // Line proceeds from inside to outside\r\n if (r < t0) return false // Line segment starts after crossing edge\r\n if (r < t1) t1 = r // Update exit point\r\n }\r\n }\r\n return true\r\n }\r\n\r\n // Clip against all four edges of the rectangle\r\n if (!clipTest(-dx, p1.x - rect.left)) return false // Left edge\r\n if (!clipTest(dx, rect.right - p1.x)) return false // Right edge\r\n if (!clipTest(-dy, p1.y - rect.top)) return false // Top edge\r\n if (!clipTest(dy, rect.bottom - p1.y)) return false // Bottom edge\r\n\r\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\r\n return t0 <= t1\r\n}\r\n","import type { MousePosition, Point } from \"../types/types\"\r\n\r\n/**\r\n * Predicts the next mouse position based on a history of recent movements.\r\n * It calculates velocity from the historical data and extrapolates a future point.\r\n * The `history` array is mutated by this function: the new `currentPoint` is added,\r\n * and if the history exceeds `positionHistorySize`, the oldest entry is removed.\r\n *\r\n * @param currentPoint - The current actual mouse coordinates.\r\n * @param history - An array of previous mouse positions with timestamps.\r\n * This array will be modified by this function.\r\n * @param positionHistorySize - The maximum number of past positions to store and consider\r\n * for the prediction.\r\n * @param trajectoryPredictionTimeInMs - How far into the future (in milliseconds)\r\n * to predict the mouse position.\r\n * @returns The predicted {@link Point} (x, y coordinates). If history is insufficient\r\n * (less than 2 points) or time delta is zero, returns the `currentPoint`.\r\n */\r\nexport function predictNextMousePosition(\r\n currentPoint: Point,\r\n history: MousePosition[],\r\n positionHistorySize: number,\r\n trajectoryPredictionTimeInMs: number\r\n): Point {\r\n const now = performance.now()\r\n const currentPosition: MousePosition = { point: currentPoint, time: now }\r\n const { x, y } = currentPoint\r\n\r\n history.push(currentPosition)\r\n if (history.length > positionHistorySize) {\r\n history.shift()\r\n }\r\n\r\n if (history.length < 2) {\r\n return { x, y }\r\n }\r\n\r\n const first = history[0]\r\n const last = history[history.length - 1]\r\n const dt = (last.time - first.time) / 1000\r\n\r\n if (dt === 0) {\r\n return { x, y }\r\n }\r\n\r\n const dx = last.point.x - first.point.x\r\n const dy = last.point.y - first.point.y\r\n const vx = dx / dt\r\n const vy = dy / dt\r\n\r\n const trajectoryPredictionTimeInSeconds = trajectoryPredictionTimeInMs / 1000\r\n const predictedX = x + vx * trajectoryPredictionTimeInSeconds\r\n const predictedY = y + vy * trajectoryPredictionTimeInSeconds\r\n\r\n return { x: predictedX, y: predictedY }\r\n}\r\n","import type { HitSlop, Point, Rect } from \"../types/types\"\r\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\r\nimport { clampNumber } from \"./clampNumber\"\r\n\r\n/**\r\n * Normalizes a `hitSlop` value into a {@link Rect} object.\r\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\r\n * If `hitSlop` is already a `Rect` object, it's returned as is.\r\n *\r\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\r\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\r\n */\r\nexport function normalizeHitSlop(hitSlop: HitSlop): Rect {\r\n if (typeof hitSlop === \"number\") {\r\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\r\n return {\r\n top: clampedValue,\r\n left: clampedValue,\r\n right: clampedValue,\r\n bottom: clampedValue,\r\n }\r\n }\r\n\r\n return {\r\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\r\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\r\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\r\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\r\n }\r\n}\r\n\r\n/**\r\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\r\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\r\n *\r\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\r\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\r\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\r\n * @returns A new {@link Rect} object representing the expanded area.\r\n */\r\nexport function getExpandedRect(baseRect: Rect | DOMRect, hitSlop: Rect): Rect {\r\n return {\r\n left: baseRect.left - hitSlop.left,\r\n right: baseRect.right + hitSlop.right,\r\n top: baseRect.top - hitSlop.top,\r\n bottom: baseRect.bottom + hitSlop.bottom,\r\n }\r\n}\r\n\r\n/**\r\n * Checks if two rectangle objects are equal by comparing their respective\r\n * `top`, `left`, `right`, and `bottom` properties.\r\n * Handles cases where one or both rects might be null or undefined.\r\n *\r\n * @param rect1 - The first {@link Rect} object to compare.\r\n * @param rect2 - The second {@link Rect} object to compare.\r\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\r\n * `false` otherwise.\r\n */\r\nexport function areRectsEqual(rect1: Rect, rect2: Rect): boolean {\r\n if (!rect1 || !rect2) return rect1 === rect2\r\n return (\r\n rect1.left === rect2.left &&\r\n rect1.right === rect2.right &&\r\n rect1.top === rect2.top &&\r\n rect1.bottom === rect2.bottom\r\n )\r\n}\r\n\r\nexport function isPointInRectangle(point: Point, rect: Rect): boolean {\r\n return (\r\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\r\n )\r\n}\r\n","/**\r\n * Checks if a setting should be updated.\r\n * Returns true if the newValue is defined and different from the currentValue.\r\n * Uses a type predicate to narrow the type of newValue in the calling scope.\r\n *\r\n * @param newValue The potentially new value for the setting (can be undefined).\r\n * @param currentValue The current value of the setting.\r\n * @returns True if the setting should be updated, false otherwise.\r\n */\r\nexport function shouldUpdateSetting<T>(\r\n newValue: T | undefined,\r\n currentValue: T\r\n): newValue is NonNullable<T> {\r\n // NonNullable<T> ensures that if T itself could be undefined (e.g. T = number | undefined),\r\n // the predicate narrows to the non-undefined part (e.g. number).\r\n // If T is already non-nullable (e.g. T = number), it remains T (e.g. number).\r\n return newValue !== undefined && currentValue !== newValue\r\n}\r\n","import type { FocusableElement } from \"tabbable\"\r\n\r\n/**\r\n * Finds the index of a focused element within a cache of tabbable elements.\r\n * It uses a predictive search for O(1) performance in the common case of\r\n * sequential tabbing, and falls back to a linear search O(n) if the\r\n * prediction fails.\r\n *\r\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\r\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\r\n * @param tabbableElementsCache - The array of all tabbable elements.\r\n * @param targetElement - The new HTML element that has received focus.\r\n * @returns The index of the targetElement in the cache, or -1 if not found.\r\n */\r\nexport function getFocusedElementIndex(\r\n isReversed: boolean,\r\n lastFocusedIndex: number | null,\r\n tabbableElementsCache: FocusableElement[],\r\n targetElement: HTMLElement\r\n): number {\r\n // First, try to predict the next index based on the last known position.\r\n if (lastFocusedIndex !== null) {\r\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\r\n\r\n // Check if the prediction is valid and correct.\r\n if (\r\n predictedIndex >= 0 &&\r\n predictedIndex < tabbableElementsCache.length &&\r\n tabbableElementsCache[predictedIndex] === targetElement\r\n ) {\r\n return predictedIndex\r\n }\r\n }\r\n\r\n // Slow way if we dont find it\r\n return tabbableElementsCache.findIndex((element) => element === targetElement)\r\n}\r\n","import type { Rect, ScrollDirection } from \"../types/types\"\r\n\r\nexport function getScrollDirection(oldRect: Rect, newRect: Rect): ScrollDirection {\r\n const scrollThreshold = 1\r\n const deltaY = newRect.top - oldRect.top\r\n const deltaX = newRect.left - oldRect.left\r\n\r\n // Check vertical scroll first (most common)\r\n if (deltaY < -scrollThreshold) {\r\n return \"down\" // Element moved up in viewport = scrolled down\r\n } else if (deltaY > scrollThreshold) {\r\n return \"up\" // Element moved down in viewport = scrolled up\r\n }\r\n\r\n // Check horizontal scroll\r\n if (deltaX < -scrollThreshold) {\r\n return \"right\" // Element moved left in viewport = scrolled right\r\n } else if (deltaX > scrollThreshold) {\r\n return \"left\" // Element moved right in viewport = scrolled left\r\n }\r\n\r\n return \"none\" // No significant movement detected\r\n}\r\n","import type { Point, ScrollDirection } from \"../types/types\"\r\n\r\nexport function predictNextScrollPosition(\r\n currentPoint: Point,\r\n direction: ScrollDirection,\r\n scrollMargin: number\r\n) {\r\n const { x, y } = currentPoint\r\n const predictedPoint = { x, y }\r\n\r\n switch (direction) {\r\n case \"down\":\r\n predictedPoint.y += scrollMargin\r\n break\r\n case \"up\":\r\n predictedPoint.y -= scrollMargin\r\n break\r\n case \"left\":\r\n predictedPoint.x -= scrollMargin\r\n break\r\n case \"right\":\r\n predictedPoint.x += scrollMargin\r\n break\r\n case \"none\":\r\n break\r\n default:\r\n direction satisfies never\r\n }\r\n return predictedPoint\r\n}\r\n","export function initialViewportState(rect: DOMRect) {\r\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth\r\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight\r\n\r\n return rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0\r\n}\r\n"],"mappings":"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","names":["index_exports","__export","ForesightManager","__toCommonJS","import_tabbable","evaluateRegistrationConditions","isTouchDevice","userUsesTouchDevice","isLimitedConnection","hasConnectionLimitations","connection","clampNumber","number","lowerBound","upperBound","settingName","lineSegmentIntersectsRect","p1","p2","rect","t0","t1","dx","dy","clipTest","p","q","r","predictNextMousePosition","currentPoint","history","positionHistorySize","trajectoryPredictionTimeInMs","now","currentPosition","x","y","first","last","dt","dx","dy","vx","vy","trajectoryPredictionTimeInSeconds","predictedX","predictedY","normalizeHitSlop","hitSlop","clampedValue","clampNumber","getExpandedRect","baseRect","areRectsEqual","rect1","rect2","isPointInRectangle","point","rect","shouldUpdateSetting","newValue","currentValue","getFocusedElementIndex","isReversed","lastFocusedIndex","tabbableElementsCache","targetElement","predictedIndex","element","getScrollDirection","oldRect","newRect","deltaY","deltaX","predictNextScrollPosition","currentPoint","direction","scrollMargin","x","y","predictedPoint","import_position_observer","initialViewportState","rect","viewportWidth","viewportHeight","ForesightManager","_ForesightManager","mutationsList","mutation","element","targetElement","isReversed","currentIndex","getFocusedElementIndex","elementsToPredict","i","foresightElement","entries","entry","elementData","updatedProps","isNowIntersecting","updatedElementData","getExpandedRect","props","eventType","listener","options","listeners","index","event","error","callback","hitSlop","name","shouldRegister","isTouchDevice","isLimitedConnection","evaluateRegistrationConditions","initialRect","normalizedHitSlop","normalizeHitSlop","initialViewportState","unregisterReason","newValue","setting","min","max","shouldUpdateSetting","clampNumber","changedSettings","oldValue","newSize","oldHitSlop","normalizedNewHitSlop","areRectsEqual","_","currentPoint","predictNextMousePosition","currentData","expandedRect","lineSegmentIntersectsRect","isPointInRectangle","callbackHitType","start","errorMessage","newOriginalRect","newRect","getScrollDirection","predictNextScrollPosition","signal"]}
|
|
1
|
+
{"version":3,"sources":["../src/foresight/ForesightManager.ts","../src/helpers/CircularBuffer.ts","../src/helpers/clampNumber.ts","../src/helpers/initialViewportState.ts","../src/helpers/rectAndHitSlop.ts","../src/helpers/shouldRegister.ts","../src/helpers/shouldUpdateSetting.ts","../src/helpers/lineSigmentIntersectsRect.ts","../src/helpers/predictNextMousePosition.ts","../src/foresight/BasePredictor.ts","../src/foresight/MousePredictor.ts","../src/helpers/getScrollDirection.ts","../src/helpers/predictNextScrollPosition.ts","../src/foresight/ScrollPredictor.ts","../src/foresight/TabPredictor.ts","../src/helpers/getFocusedElementIndex.ts"],"sourcesContent":["import { PositionObserver, PositionObserverEntry } from \"position-observer\"\r\nimport {\r\n DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n DEFAULT_ENABLE_TAB_PREDICTION,\r\n DEFAULT_HITSLOP,\r\n DEFAULT_POSITION_HISTORY_SIZE,\r\n DEFAULT_SCROLL_MARGIN,\r\n DEFAULT_TAB_OFFSET,\r\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n MAX_POSITION_HISTORY_SIZE,\r\n MAX_SCROLL_MARGIN,\r\n MAX_TAB_OFFSET,\r\n MAX_TRAJECTORY_PREDICTION_TIME,\r\n MIN_POSITION_HISTORY_SIZE,\r\n MIN_SCROLL_MARGIN,\r\n MIN_TAB_OFFSET,\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n} from \"../constants\"\r\nimport { CircularBuffer } from \"../helpers/CircularBuffer\"\r\nimport { clampNumber } from \"../helpers/clampNumber\"\r\nimport { initialViewportState } from \"../helpers/initialViewportState\"\r\nimport {\r\n areRectsEqual,\r\n getExpandedRect,\r\n isPointInRectangle,\r\n normalizeHitSlop,\r\n} from \"../helpers/rectAndHitSlop\"\r\nimport { evaluateRegistrationConditions } from \"../helpers/shouldRegister\"\r\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\r\nimport type {\r\n CallbackHits,\r\n CallbackHitType,\r\n ElementUnregisteredReason,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ForesightEventListener,\r\n ForesightEventMap,\r\n ForesightManagerData,\r\n ForesightManagerSettings,\r\n ForesightRegisterOptions,\r\n ForesightRegisterResult,\r\n ManagerBooleanSettingKeys,\r\n NumericSettingKeys,\r\n TrajectoryPositions,\r\n UpdatedDataPropertyNames,\r\n UpdatedManagerSetting,\r\n UpdateForsightManagerSettings,\r\n} from \"../types/types\"\r\nimport type { PredictorDependencies } from \"./BasePredictor\"\r\nimport { MousePredictor } from \"./MousePredictor\"\r\nimport { ScrollPredictor } from \"./ScrollPredictor\"\r\nimport { TabPredictor } from \"./TabPredictor\"\r\n\r\n/**\r\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\r\n *\r\n * ForesightManager is a singleton class responsible for:\r\n * - Registering HTML elements to monitor.\r\n * - Tracking mouse movements and predicting future cursor positions.\r\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\r\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\r\n * - Optionally unregistering elements after their callback is triggered.\r\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\r\n * - Automatically updating element bounds on resize using {@link ResizeObserver}.\r\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\r\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\r\n *\r\n * It should be initialized once using {@link ForesightManager.initialize} and then\r\n * accessed via the static getter {@link ForesightManager.instance}.\r\n */\r\n\r\nexport class ForesightManager {\r\n private static manager: ForesightManager\r\n private elements: Map<ForesightElement, ForesightElementData> = new Map()\r\n private trajectoryPositions: TrajectoryPositions = {\r\n positions: new CircularBuffer(DEFAULT_POSITION_HISTORY_SIZE),\r\n currentPoint: { x: 0, y: 0 },\r\n predictedPoint: { x: 0, y: 0 },\r\n }\r\n private isSetup: boolean = false\r\n private _globalCallbackHits: CallbackHits = {\r\n mouse: {\r\n hover: 0,\r\n trajectory: 0,\r\n },\r\n tab: {\r\n forwards: 0,\r\n reverse: 0,\r\n },\r\n scroll: {\r\n down: 0,\r\n left: 0,\r\n right: 0,\r\n up: 0,\r\n },\r\n total: 0,\r\n }\r\n private _globalSettings: ForesightManagerSettings = {\r\n debug: false,\r\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\r\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\r\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\r\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\r\n scrollMargin: DEFAULT_SCROLL_MARGIN,\r\n defaultHitSlop: {\r\n top: DEFAULT_HITSLOP,\r\n left: DEFAULT_HITSLOP,\r\n right: DEFAULT_HITSLOP,\r\n bottom: DEFAULT_HITSLOP,\r\n },\r\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\r\n tabOffset: DEFAULT_TAB_OFFSET,\r\n }\r\n\r\n private domObserver: MutationObserver | null = null\r\n private positionObserver: PositionObserver | null = null\r\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\r\n private mousePredictor: MousePredictor | null = null\r\n private tabPredictor: TabPredictor | null = null\r\n private scrollPredictor: ScrollPredictor | null = null\r\n private constructor() {}\r\n\r\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\r\n if (!this.isInitiated) {\r\n ForesightManager.manager = new ForesightManager()\r\n }\r\n if (props !== undefined) {\r\n ForesightManager.manager.alterGlobalSettings(props)\r\n }\r\n\r\n return ForesightManager.manager\r\n }\r\n\r\n public addEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>,\r\n options?: { signal?: AbortSignal }\r\n ) {\r\n if (options?.signal?.aborted) {\r\n return () => {}\r\n }\r\n const listeners = this.eventListeners.get(eventType) ?? []\r\n listeners.push(listener as ForesightEventListener)\r\n this.eventListeners.set(eventType, listeners)\r\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\r\n }\r\n\r\n public removeEventListener<K extends ForesightEvent>(\r\n eventType: K,\r\n listener: ForesightEventListener<K>\r\n ): void {\r\n const listeners = this.eventListeners.get(eventType)\r\n if (!listeners) {\r\n return\r\n }\r\n const index = listeners.indexOf(listener as ForesightEventListener)\r\n if (index > -1) {\r\n listeners.splice(index, 1)\r\n }\r\n }\r\n\r\n private emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\r\n const listeners = this.eventListeners.get(event.type)\r\n if (!listeners) {\r\n return\r\n }\r\n\r\n listeners.forEach((listener, index) => {\r\n try {\r\n listener(event)\r\n } catch (error) {\r\n console.error(`Error in ForesightManager event listener ${index} for ${event.type}:`, error)\r\n }\r\n })\r\n }\r\n\r\n public get getManagerData(): Readonly<ForesightManagerData> {\r\n return {\r\n registeredElements: this.elements,\r\n globalSettings: this._globalSettings,\r\n globalCallbackHits: this._globalCallbackHits,\r\n eventListeners: this.eventListeners,\r\n historyPositions: this.trajectoryPositions,\r\n }\r\n }\r\n\r\n public static get isInitiated(): Readonly<boolean> {\r\n return !!ForesightManager.manager\r\n }\r\n\r\n public static get instance(): ForesightManager {\r\n return this.initialize()\r\n }\r\n\r\n public get registeredElements(): ReadonlyMap<ForesightElement, ForesightElementData> {\r\n return this.elements\r\n }\r\n\r\n public register({\r\n element,\r\n callback,\r\n hitSlop,\r\n name,\r\n }: ForesightRegisterOptions): ForesightRegisterResult {\r\n const { shouldRegister, isTouchDevice, isLimitedConnection } = evaluateRegistrationConditions()\r\n if (!shouldRegister) {\r\n return {\r\n isLimitedConnection,\r\n isTouchDevice,\r\n isRegistered: false,\r\n unregister: () => {},\r\n }\r\n }\r\n\r\n // Setup global listeners on every first element added to the manager. It gets removed again when the map is emptied\r\n if (!this.isSetup) {\r\n this.initializeGlobalListeners()\r\n }\r\n\r\n const initialRect = element.getBoundingClientRect()\r\n\r\n const normalizedHitSlop = hitSlop\r\n ? normalizeHitSlop(hitSlop)\r\n : this._globalSettings.defaultHitSlop\r\n\r\n const elementData: ForesightElementData = {\r\n element: element,\r\n callback,\r\n elementBounds: {\r\n originalRect: initialRect,\r\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\r\n hitSlop: normalizedHitSlop,\r\n },\r\n isHovering: false,\r\n trajectoryHitData: {\r\n isTrajectoryHit: false,\r\n trajectoryHitTime: 0,\r\n trajectoryHitExpirationTimeoutId: undefined,\r\n },\r\n name: name || element.id || \"unnamed\",\r\n isIntersectingWithViewport: initialViewportState(initialRect),\r\n isRunningCallback: false,\r\n registerCount: (this.registeredElements.get(element)?.registerCount ?? 0) + 1,\r\n }\r\n\r\n this.elements.set(element, elementData)\r\n\r\n this.positionObserver?.observe(element)\r\n\r\n this.emit({\r\n type: \"elementRegistered\",\r\n timestamp: Date.now(),\r\n elementData,\r\n })\r\n\r\n return {\r\n isTouchDevice,\r\n isLimitedConnection,\r\n isRegistered: true,\r\n unregister: () => this.unregister(element, \"apiCall\"),\r\n }\r\n }\r\n\r\n private unregister(element: ForesightElement, unregisterReason: ElementUnregisteredReason) {\r\n if (!this.elements.has(element)) {\r\n return\r\n }\r\n\r\n const elementData = this.elements.get(element)\r\n\r\n // Clear any pending trajectory expiration timeout\r\n if (elementData?.trajectoryHitData.trajectoryHitExpirationTimeoutId) {\r\n clearTimeout(elementData.trajectoryHitData.trajectoryHitExpirationTimeoutId)\r\n }\r\n\r\n this.positionObserver?.unobserve(element)\r\n this.elements.delete(element)\r\n\r\n const wasLastElement = this.elements.size === 0 && this.isSetup\r\n\r\n if (wasLastElement) {\r\n this.removeGlobalListeners()\r\n }\r\n\r\n if (elementData) {\r\n this.emit({\r\n type: \"elementUnregistered\",\r\n elementData: elementData,\r\n timestamp: Date.now(),\r\n unregisterReason: unregisterReason,\r\n wasLastElement: wasLastElement,\r\n })\r\n }\r\n }\r\n\r\n private updateNumericSettings(\r\n newValue: number | undefined,\r\n setting: NumericSettingKeys,\r\n min: number,\r\n max: number\r\n ) {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n\r\n this._globalSettings[setting] = clampNumber(newValue, min, max, setting)\r\n\r\n return true\r\n }\r\n\r\n private updateBooleanSetting(\r\n newValue: boolean | undefined,\r\n setting: ManagerBooleanSettingKeys\r\n ): boolean {\r\n if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {\r\n return false\r\n }\r\n this._globalSettings[setting] = newValue\r\n return true\r\n }\r\n\r\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\r\n const changedSettings: UpdatedManagerSetting[] = []\r\n\r\n const oldTrajectoryPredictionTime = this._globalSettings.trajectoryPredictionTime\r\n const trajectoryPredictionTimeChanged = this.updateNumericSettings(\r\n props?.trajectoryPredictionTime,\r\n \"trajectoryPredictionTime\",\r\n MIN_TRAJECTORY_PREDICTION_TIME,\r\n MAX_TRAJECTORY_PREDICTION_TIME\r\n )\r\n if (trajectoryPredictionTimeChanged) {\r\n changedSettings.push({\r\n setting: \"trajectoryPredictionTime\",\r\n oldValue: oldTrajectoryPredictionTime,\r\n newValue: this._globalSettings.trajectoryPredictionTime,\r\n })\r\n }\r\n\r\n const oldPositionHistorySize = this._globalSettings.positionHistorySize\r\n const positionHistorySizeChanged = this.updateNumericSettings(\r\n props?.positionHistorySize,\r\n \"positionHistorySize\",\r\n MIN_POSITION_HISTORY_SIZE,\r\n MAX_POSITION_HISTORY_SIZE\r\n )\r\n if (positionHistorySizeChanged) {\r\n changedSettings.push({\r\n setting: \"positionHistorySize\",\r\n oldValue: oldPositionHistorySize,\r\n newValue: this._globalSettings.positionHistorySize,\r\n })\r\n this.trajectoryPositions.positions.resize(this._globalSettings.positionHistorySize)\r\n }\r\n\r\n const oldScrollMargin = this._globalSettings.scrollMargin\r\n const scrollMarginChanged = this.updateNumericSettings(\r\n props?.scrollMargin,\r\n \"scrollMargin\",\r\n MIN_SCROLL_MARGIN,\r\n MAX_SCROLL_MARGIN\r\n )\r\n if (scrollMarginChanged) {\r\n const newValue = this._globalSettings.scrollMargin\r\n if (this.scrollPredictor) {\r\n this.scrollPredictor.scrollMargin = newValue\r\n }\r\n changedSettings.push({\r\n setting: \"scrollMargin\",\r\n oldValue: oldScrollMargin,\r\n newValue: newValue,\r\n })\r\n }\r\n\r\n const oldTabOffset = this._globalSettings.tabOffset\r\n const tabOffsetChanged = this.updateNumericSettings(\r\n props?.tabOffset,\r\n \"tabOffset\",\r\n MIN_TAB_OFFSET,\r\n MAX_TAB_OFFSET\r\n )\r\n if (tabOffsetChanged) {\r\n if (this.tabPredictor) {\r\n this.tabPredictor.tabOffset = this._globalSettings.tabOffset\r\n }\r\n changedSettings.push({\r\n setting: \"tabOffset\",\r\n oldValue: oldTabOffset,\r\n newValue: this._globalSettings.tabOffset,\r\n })\r\n }\r\n\r\n const oldEnableMousePrediction = this._globalSettings.enableMousePrediction\r\n const enableMousePredictionChanged = this.updateBooleanSetting(\r\n props?.enableMousePrediction,\r\n \"enableMousePrediction\"\r\n )\r\n if (enableMousePredictionChanged) {\r\n changedSettings.push({\r\n setting: \"enableMousePrediction\",\r\n oldValue: oldEnableMousePrediction,\r\n newValue: this._globalSettings.enableMousePrediction,\r\n })\r\n }\r\n\r\n const oldEnableScrollPrediction = this._globalSettings.enableScrollPrediction\r\n const enableScrollPredictionChanged = this.updateBooleanSetting(\r\n props?.enableScrollPrediction,\r\n \"enableScrollPrediction\"\r\n )\r\n if (enableScrollPredictionChanged) {\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor = new ScrollPredictor({\r\n dependencies: {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n },\r\n settings: {\r\n scrollMargin: this._globalSettings.scrollMargin,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n } else {\r\n this.scrollPredictor?.cleanup()\r\n this.scrollPredictor = null\r\n }\r\n changedSettings.push({\r\n setting: \"enableScrollPrediction\",\r\n oldValue: oldEnableScrollPrediction,\r\n newValue: this._globalSettings.enableScrollPrediction,\r\n })\r\n }\r\n\r\n const oldEnableTabPrediction = this._globalSettings.enableTabPrediction\r\n const enableTabPredictionChanged = this.updateBooleanSetting(\r\n props?.enableTabPrediction,\r\n \"enableTabPrediction\"\r\n )\r\n if (enableTabPredictionChanged) {\r\n changedSettings.push({\r\n setting: \"enableTabPrediction\",\r\n oldValue: oldEnableTabPrediction,\r\n newValue: this._globalSettings.enableTabPrediction,\r\n })\r\n if (this._globalSettings.enableTabPrediction) {\r\n this.tabPredictor = new TabPredictor({\r\n dependencies: {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n },\r\n settings: {\r\n tabOffset: this._globalSettings.tabOffset,\r\n },\r\n })\r\n } else {\r\n this.tabPredictor?.cleanup()\r\n this.tabPredictor = null\r\n }\r\n }\r\n\r\n if (props?.defaultHitSlop !== undefined) {\r\n const oldHitSlop = this._globalSettings.defaultHitSlop\r\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\r\n\r\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\r\n this._globalSettings.defaultHitSlop = normalizedNewHitSlop\r\n changedSettings.push({\r\n setting: \"defaultHitSlop\",\r\n oldValue: oldHitSlop,\r\n newValue: normalizedNewHitSlop,\r\n })\r\n this.forceUpdateAllElementBounds()\r\n }\r\n }\r\n\r\n if (changedSettings.length > 0) {\r\n this.emit({\r\n type: \"managerSettingsChanged\",\r\n timestamp: Date.now(),\r\n managerData: this.getManagerData,\r\n updatedSettings: changedSettings,\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.\r\n *\r\n * @param mutationsList - Array of MutationRecord objects describing the DOM changes\r\n *\r\n */\r\n private handleDomMutations = (mutationsList: MutationRecord[]) => {\r\n // Invalidate tabbale elements cache\r\n if (mutationsList.length) {\r\n this.tabPredictor?.invalidateCache()\r\n }\r\n for (const mutation of mutationsList) {\r\n if (mutation.type === \"childList\" && mutation.removedNodes.length > 0) {\r\n for (const element of this.elements.keys()) {\r\n if (!element.isConnected) {\r\n this.unregister(element, \"disconnected\")\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n private updateHitCounters(callbackHitType: CallbackHitType) {\r\n switch (callbackHitType.kind) {\r\n case \"mouse\":\r\n this._globalCallbackHits.mouse[callbackHitType.subType]++\r\n break\r\n case \"tab\":\r\n this._globalCallbackHits.tab[callbackHitType.subType]++\r\n break\r\n case \"scroll\":\r\n this._globalCallbackHits.scroll[callbackHitType.subType]++\r\n break\r\n default:\r\n callbackHitType satisfies never\r\n }\r\n this._globalCallbackHits.total++\r\n }\r\n\r\n private callCallback(elementData: ForesightElementData, callbackHitType: CallbackHitType) {\r\n if (elementData.isRunningCallback) {\r\n return\r\n }\r\n // We have this async wrapper so we can time exactly how long the callback takes\r\n const asyncCallbackWrapper = async () => {\r\n this.updateHitCounters(callbackHitType)\r\n this.emit({\r\n type: \"callbackInvoked\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n })\r\n const start = performance.now()\r\n try {\r\n await elementData.callback()\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"success\",\r\n })\r\n } catch (error) {\r\n const errorMessage = error instanceof Error ? error.message : String(error)\r\n console.error(\r\n `Error in callback for element ${elementData.name} (${elementData.element.tagName}):`,\r\n error\r\n )\r\n this.emit({\r\n type: \"callbackCompleted\",\r\n timestamp: Date.now(),\r\n elementData,\r\n hitType: callbackHitType,\r\n elapsed: performance.now() - start,\r\n status: \"error\",\r\n errorMessage,\r\n })\r\n }\r\n this.unregister(elementData.element, \"callbackHit\")\r\n }\r\n elementData.isRunningCallback = true\r\n asyncCallbackWrapper()\r\n }\r\n\r\n private handlePositionChange = (entries: PositionObserverEntry[]) => {\r\n for (const entry of entries) {\r\n const elementData = this.elements.get(entry.target)\r\n if (!elementData) {\r\n continue\r\n }\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor?.handleScrollPrefetch(elementData, entry.boundingClientRect)\r\n } else {\r\n // If we dont check for scroll prediction, check if the user is hovering over the element during a scroll instead\r\n if (\r\n isPointInRectangle(\r\n this.trajectoryPositions.currentPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"mouse\",\r\n subType: \"hover\",\r\n })\r\n }\r\n }\r\n // Always call handlePositionChangeDataUpdates AFTER handleScrollPrefetch since handlePositionChangeDataUpdates alters the elementData\r\n this.handlePositionChangeDataUpdates(elementData, entry)\r\n }\r\n\r\n // End batch processing for scroll prediction\r\n if (this._globalSettings.enableScrollPrediction) {\r\n this.scrollPredictor?.resetScrollProps()\r\n }\r\n }\r\n\r\n private handlePositionChangeDataUpdates = (\r\n elementData: ForesightElementData,\r\n entry: PositionObserverEntry\r\n ) => {\r\n const updatedProps: UpdatedDataPropertyNames[] = []\r\n const isNowIntersecting = entry.isIntersecting\r\n\r\n // Track visibility changes\r\n if (elementData.isIntersectingWithViewport !== isNowIntersecting) {\r\n updatedProps.push(\"visibility\")\r\n elementData.isIntersectingWithViewport = isNowIntersecting\r\n }\r\n\r\n // Handle bounds updates for intersecting elements\r\n if (isNowIntersecting) {\r\n updatedProps.push(\"bounds\")\r\n elementData.elementBounds = {\r\n hitSlop: elementData.elementBounds.hitSlop,\r\n originalRect: entry.boundingClientRect,\r\n expandedRect: getExpandedRect(entry.boundingClientRect, elementData.elementBounds.hitSlop),\r\n }\r\n }\r\n if (updatedProps.length) {\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: elementData,\r\n updatedProps,\r\n })\r\n }\r\n }\r\n\r\n private initializeGlobalListeners() {\r\n if (this.isSetup) {\r\n return\r\n }\r\n // To avoid setting up listeners while ssr\r\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\r\n return\r\n }\r\n\r\n const settings = this._globalSettings\r\n const dependencies: PredictorDependencies = {\r\n callCallback: this.callCallback.bind(this),\r\n emit: this.emit.bind(this),\r\n elements: this.elements,\r\n }\r\n\r\n if (settings.enableTabPrediction) {\r\n this.tabPredictor = new TabPredictor({\r\n dependencies,\r\n settings: {\r\n tabOffset: settings.tabOffset,\r\n },\r\n })\r\n }\r\n\r\n if (settings.enableScrollPrediction) {\r\n this.scrollPredictor = new ScrollPredictor({\r\n dependencies,\r\n settings: {\r\n scrollMargin: settings.scrollMargin,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n }\r\n\r\n // Always initialize mouse since if its not enabled we still check for hover and need it for scroll\r\n this.mousePredictor = new MousePredictor({\r\n dependencies,\r\n settings: {\r\n enableMousePrediction: settings.enableMousePrediction,\r\n trajectoryPredictionTime: settings.trajectoryPredictionTime,\r\n positionHistorySize: settings.positionHistorySize,\r\n },\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n\r\n //Mutation observer is to automatically unregister elements when they leave the DOM. Its a fail-safe for if the user forgets to do it.\r\n this.domObserver = new MutationObserver(this.handleDomMutations)\r\n this.domObserver.observe(document.documentElement, {\r\n childList: true,\r\n subtree: true,\r\n attributes: false,\r\n })\r\n\r\n // Handles all position based changes and update the rects of the elements. completely async to avoid dirtying the main thread.\r\n // Handles resize of elements\r\n // Handles resize of viewport\r\n // Handles scrolling\r\n this.positionObserver = new PositionObserver(this.handlePositionChange)\r\n\r\n this.isSetup = true\r\n }\r\n\r\n private removeGlobalListeners() {\r\n this.isSetup = false\r\n this.domObserver?.disconnect()\r\n this.domObserver = null\r\n this.positionObserver?.disconnect()\r\n this.positionObserver = null\r\n this.mousePredictor?.cleanup()\r\n this.tabPredictor?.cleanup()\r\n this.scrollPredictor?.cleanup()\r\n }\r\n\r\n private forceUpdateAllElementBounds() {\r\n for (const [, elementData] of this.elements) {\r\n if (elementData.isIntersectingWithViewport) {\r\n this.forceUpdateElementBounds(elementData)\r\n }\r\n }\r\n }\r\n /**\r\n * ONLY use this function when you want to change the rect bounds via code, if the rects are changing because of updates in the DOM do not use this function.\r\n * We need an observer for that\r\n */\r\n private forceUpdateElementBounds(elementData: ForesightElementData) {\r\n const newOriginalRect = elementData.element.getBoundingClientRect()\r\n const expandedRect = getExpandedRect(newOriginalRect, elementData.elementBounds.hitSlop)\r\n\r\n if (!areRectsEqual(expandedRect, elementData.elementBounds.expandedRect)) {\r\n const updatedElementData = {\r\n ...elementData,\r\n elementBounds: {\r\n ...elementData.elementBounds,\r\n originalRect: newOriginalRect,\r\n expandedRect,\r\n },\r\n }\r\n\r\n this.elements.set(elementData.element, updatedElementData)\r\n\r\n this.emit({\r\n type: \"elementDataUpdated\",\r\n elementData: updatedElementData,\r\n updatedProps: [\"bounds\"],\r\n })\r\n }\r\n }\r\n}\r\n","export class CircularBuffer<T> {\r\n private buffer: T[]\r\n private head: number = 0\r\n private count: number = 0\r\n private capacity: number\r\n\r\n constructor(capacity: number) {\r\n if (capacity <= 0) {\r\n throw new Error('CircularBuffer capacity must be greater than 0')\r\n }\r\n this.capacity = capacity\r\n this.buffer = new Array(capacity)\r\n }\r\n\r\n add(item: T): void {\r\n this.buffer[this.head] = item\r\n this.head = (this.head + 1) % this.capacity\r\n \r\n if (this.count < this.capacity) {\r\n this.count++\r\n }\r\n }\r\n\r\n getFirst(): T | undefined {\r\n if (this.count === 0) {\r\n return undefined\r\n }\r\n\r\n if (this.count < this.capacity) {\r\n return this.buffer[0]\r\n } else {\r\n return this.buffer[this.head]\r\n }\r\n }\r\n\r\n getLast(): T | undefined {\r\n if (this.count === 0) {\r\n return undefined\r\n }\r\n\r\n if (this.count < this.capacity) {\r\n return this.buffer[this.count - 1]\r\n } else {\r\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity\r\n return this.buffer[lastIndex]\r\n }\r\n }\r\n\r\n getFirstLast(): [T | undefined, T | undefined] {\r\n if (this.count === 0) {\r\n return [undefined, undefined]\r\n }\r\n\r\n if (this.count === 1) {\r\n const item = this.count < this.capacity ? this.buffer[0] : this.buffer[this.head]\r\n return [item, item]\r\n }\r\n\r\n const first = this.getFirst()\r\n const last = this.getLast()\r\n return [first, last]\r\n }\r\n\r\n resize(newCapacity: number): void {\r\n if (newCapacity <= 0) {\r\n throw new Error('CircularBuffer capacity must be greater than 0')\r\n }\r\n\r\n if (newCapacity === this.capacity) {\r\n return\r\n }\r\n\r\n const currentItems = this.getAllItems()\r\n this.capacity = newCapacity\r\n this.buffer = new Array(newCapacity)\r\n this.head = 0\r\n this.count = 0\r\n\r\n if (currentItems.length > newCapacity) {\r\n const itemsToKeep = currentItems.slice(-newCapacity)\r\n for (const item of itemsToKeep) {\r\n this.add(item)\r\n }\r\n } else {\r\n for (const item of currentItems) {\r\n this.add(item)\r\n }\r\n }\r\n }\r\n\r\n private getAllItems(): T[] {\r\n if (this.count === 0) {\r\n return []\r\n }\r\n\r\n const result: T[] = new Array(this.count)\r\n \r\n if (this.count < this.capacity) {\r\n for (let i = 0; i < this.count; i++) {\r\n result[i] = this.buffer[i]\r\n }\r\n } else {\r\n const startIndex = this.head\r\n for (let i = 0; i < this.capacity; i++) {\r\n const bufferIndex = (startIndex + i) % this.capacity\r\n result[i] = this.buffer[bufferIndex]\r\n }\r\n }\r\n \r\n return result\r\n }\r\n\r\n clear(): void {\r\n this.head = 0\r\n this.count = 0\r\n }\r\n\r\n get length(): number {\r\n return this.count\r\n }\r\n\r\n get size(): number {\r\n return this.capacity\r\n }\r\n\r\n get isFull(): boolean {\r\n return this.count === this.capacity\r\n }\r\n\r\n get isEmpty(): boolean {\r\n return this.count === 0\r\n }\r\n}","export function clampNumber(\n number: number,\n lowerBound: number,\n upperBound: number,\n settingName: string\n) {\n if (number < lowerBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\n )\n } else if (number > upperBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\n )\n }\n\n return Math.min(Math.max(number, lowerBound), upperBound)\n}\n","export function initialViewportState(rect: DOMRect) {\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight\n\n return rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0\n}\n","import type { HitSlop, Point, Rect } from \"../types/types\"\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\nimport { clampNumber } from \"./clampNumber\"\n\n/**\n * Normalizes a `hitSlop` value into a {@link Rect} object.\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\n * If `hitSlop` is already a `Rect` object, it's returned as is.\n *\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\n */\nexport function normalizeHitSlop(hitSlop: HitSlop): Rect {\n if (typeof hitSlop === \"number\") {\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\n return {\n top: clampedValue,\n left: clampedValue,\n right: clampedValue,\n bottom: clampedValue,\n }\n }\n\n return {\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\n }\n}\n\n/**\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\n *\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\n * @returns A new {@link Rect} object representing the expanded area.\n */\nexport function getExpandedRect(baseRect: Rect | DOMRect, hitSlop: Rect): Rect {\n return {\n left: baseRect.left - hitSlop.left,\n right: baseRect.right + hitSlop.right,\n top: baseRect.top - hitSlop.top,\n bottom: baseRect.bottom + hitSlop.bottom,\n }\n}\n\n/**\n * Checks if two rectangle objects are equal by comparing their respective\n * `top`, `left`, `right`, and `bottom` properties.\n * Handles cases where one or both rects might be null or undefined.\n *\n * @param rect1 - The first {@link Rect} object to compare.\n * @param rect2 - The second {@link Rect} object to compare.\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\n * `false` otherwise.\n */\nexport function areRectsEqual(rect1: Rect, rect2: Rect): boolean {\n if (!rect1 || !rect2) return rect1 === rect2\n return (\n rect1.left === rect2.left &&\n rect1.right === rect2.right &&\n rect1.top === rect2.top &&\n rect1.bottom === rect2.bottom\n )\n}\n\nexport function isPointInRectangle(point: Point, rect: Rect): boolean {\n return (\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\n )\n}\n","type ShouldRegister = {\n shouldRegister: boolean\n isTouchDevice: boolean\n isLimitedConnection: boolean\n}\n\nexport function evaluateRegistrationConditions(): ShouldRegister {\n const isTouchDevice = userUsesTouchDevice()\n const isLimitedConnection = hasConnectionLimitations()\n const shouldRegister = !isTouchDevice && !isLimitedConnection\n return { isTouchDevice, isLimitedConnection, shouldRegister }\n}\n\n/**\n * Detects if the current device is likely a touch-enabled device.\n * It checks for coarse pointer media query and the presence of touch points.\n *\n * @returns `true` if the device is likely touch-enabled, `false` otherwise.\n */\nfunction userUsesTouchDevice(): boolean {\n return window.matchMedia(\"(pointer: coarse)\").matches && navigator.maxTouchPoints > 0\n}\n\n/**\n * Checks if the user has connection limitations (slow network or data saver enabled).\n *\n * @returns {boolean} True if connection is limited, false if safe to prefetch\n * @example\n * if (!hasConnectionLimitations()) {\n * prefetchResource('/api/data');\n * }\n */\nfunction hasConnectionLimitations(): boolean {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connection = (navigator as any).connection\n if (!connection) return false\n\n return /2g/.test(connection.effectiveType) || connection.saveData\n}\n","/**\n * Checks if a setting should be updated.\n * Returns true if the newValue is defined and different from the currentValue.\n * Uses a type predicate to narrow the type of newValue in the calling scope.\n *\n * @param newValue The potentially new value for the setting (can be undefined).\n * @param currentValue The current value of the setting.\n * @returns True if the setting should be updated, false otherwise.\n */\nexport function shouldUpdateSetting<T>(\n newValue: T | undefined,\n currentValue: T\n): newValue is NonNullable<T> {\n // NonNullable<T> ensures that if T itself could be undefined (e.g. T = number | undefined),\n // the predicate narrows to the non-undefined part (e.g. number).\n // If T is already non-nullable (e.g. T = number), it remains T (e.g. number).\n return newValue !== undefined && currentValue !== newValue\n}\n","import type { Point, Rect } from \"../types/types\"\n\n/**\n * Determines if a line segment intersects with a given rectangle.\n * This function implements the Liang-Barsky line clipping algorithm.\n *\n * @param p1 - The starting {@link Point} of the line segment.\n * @param p2 - The ending {@link Point} of the line segment.\n * @param rect - The {@link Rect} to check for intersection.\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\n */\nexport function lineSegmentIntersectsRect(p1: Point, p2: Point, rect: Rect): boolean {\n let t0 = 0.0\n let t1 = 1.0\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n\n const clipTest = (p: number, q: number): boolean => {\n if (p === 0) {\n // Line is parallel to this clipping edge\n if (q < 0) return false // Line is outside the clipping edge\n } else {\n const r = q / p\n if (p < 0) {\n // Line proceeds from outside to inside\n if (r > t1) return false // Line segment ends before crossing edge\n if (r > t0) t0 = r // Update entry point\n } else {\n // Line proceeds from inside to outside\n if (r < t0) return false // Line segment starts after crossing edge\n if (r < t1) t1 = r // Update exit point\n }\n }\n return true\n }\n\n // Clip against all four edges of the rectangle\n if (!clipTest(-dx, p1.x - rect.left)) return false // Left edge\n if (!clipTest(dx, rect.right - p1.x)) return false // Right edge\n if (!clipTest(-dy, p1.y - rect.top)) return false // Top edge\n if (!clipTest(dy, rect.bottom - p1.y)) return false // Bottom edge\n\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\n return t0 <= t1\n}\n","import type { MousePosition, Point } from \"../types/types\"\nimport type { CircularBuffer } from \"./CircularBuffer\"\n\n/**\n * Predicts the next mouse position based on a history of recent movements.\n * It calculates velocity from the historical data and extrapolates a future point.\n * The `buffer` is mutated by this function: the new `currentPoint` is added,\n * automatically overwriting the oldest entry when the buffer is full.\n *\n * @param currentPoint - The current actual mouse coordinates.\n * @param buffer - A circular buffer of previous mouse positions with timestamps.\n * This buffer will be modified by this function.\n * @param trajectoryPredictionTimeInMs - How far into the future (in milliseconds)\n * to predict the mouse position.\n * @returns The predicted {@link Point} (x, y coordinates). If history is insufficient\n * (less than 2 points) or time delta is zero, returns the `currentPoint`.\n */\nexport function predictNextMousePosition(\n currentPoint: Point,\n buffer: CircularBuffer<MousePosition>,\n trajectoryPredictionTimeInMs: number\n): Point {\n const now = performance.now()\n const currentPosition: MousePosition = { point: currentPoint, time: now }\n const { x, y } = currentPoint\n\n buffer.add(currentPosition)\n\n if (buffer.length < 2) {\n return { x, y }\n }\n\n const [first, last] = buffer.getFirstLast()\n if (!first || !last) {\n return { x, y }\n }\n const dt = (last.time - first.time) * 0.001\n\n if (dt === 0) {\n return { x, y }\n }\n\n const dx = last.point.x - first.point.x\n const dy = last.point.y - first.point.y\n const vx = dx / dt\n const vy = dy / dt\n\n const trajectoryPredictionTimeInSeconds = trajectoryPredictionTimeInMs * 0.001\n const predictedX = x + vx * trajectoryPredictionTimeInSeconds\n const predictedY = y + vy * trajectoryPredictionTimeInSeconds\n\n return { x: predictedX, y: predictedY }\n}\n","import type {\r\n CallbackHitType,\r\n ForesightElement,\r\n ForesightElementData,\r\n ForesightEvent,\r\n ForesightEventMap,\r\n} from \"../types/types\"\r\n\r\nexport type CallCallbackFunction = (\r\n elementData: ForesightElementData,\r\n callbackHitType: CallbackHitType\r\n) => void\r\n\r\nexport type EmitFunction = <K extends ForesightEvent>(event: ForesightEventMap[K]) => void\r\n\r\nexport type PredictorDependencies = {\r\n elements: ReadonlyMap<ForesightElement, ForesightElementData>\r\n callCallback: CallCallbackFunction\r\n emit: EmitFunction\r\n}\r\n\r\nexport interface BasePredictorConfig {\r\n dependencies: PredictorDependencies\r\n}\r\n\r\nexport type PredictorProps = PredictorDependencies\r\n\r\nexport abstract class BasePredictor {\r\n protected abortController: AbortController\r\n protected elements: ReadonlyMap<ForesightElement, ForesightElementData>\r\n protected callCallback: CallCallbackFunction\r\n protected emit: EmitFunction\r\n \r\n constructor(config: BasePredictorConfig) {\r\n this.elements = config.dependencies.elements\r\n this.callCallback = config.dependencies.callCallback\r\n this.emit = config.dependencies.emit\r\n this.abortController = new AbortController()\r\n }\r\n \r\n protected abort(): void {\r\n this.abortController.abort()\r\n }\r\n\r\n public abstract cleanup(): void\r\n protected abstract initializeListeners(): void\r\n \r\n protected handleError(error: unknown, context: string): void {\r\n console.error(`${this.constructor.name} error in ${context}:`, error)\r\n }\r\n}\r\n","import { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\nimport { predictNextMousePosition } from \"../helpers/predictNextMousePosition\"\r\nimport { isPointInRectangle } from \"../helpers/rectAndHitSlop\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\nimport type { TrajectoryPositions } from \"../types/types\"\r\n\r\nexport interface MousePredictorSettings {\r\n enableMousePrediction: boolean\r\n trajectoryPredictionTime: number\r\n positionHistorySize: number\r\n}\r\n\r\nexport interface MousePredictorConfig extends BasePredictorConfig {\r\n settings: MousePredictorSettings\r\n trajectoryPositions: TrajectoryPositions\r\n}\r\n\r\nexport class MousePredictor extends BasePredictor {\r\n private pendingMouseEvent: MouseEvent | null = null\r\n private rafId: number | null = null\r\n private enableMousePrediction: boolean\r\n public trajectoryPredictionTime: number\r\n public positionHistorySize: number\r\n private trajectoryPositions: TrajectoryPositions\r\n\r\n constructor(config: MousePredictorConfig) {\r\n super(config)\r\n this.enableMousePrediction = config.settings.enableMousePrediction\r\n this.trajectoryPredictionTime = config.settings.trajectoryPredictionTime\r\n this.positionHistorySize = config.settings.positionHistorySize\r\n this.trajectoryPositions = config.trajectoryPositions\r\n this.initializeListeners()\r\n }\r\n protected initializeListeners() {\r\n const { signal } = this.abortController\r\n document.addEventListener(\"mousemove\", this.handleMouseMove, { signal })\r\n }\r\n\r\n private handleMouseMove = (e: MouseEvent) => {\r\n this.pendingMouseEvent = e\r\n if (this.rafId) return\r\n\r\n this.rafId = requestAnimationFrame(() => {\r\n if (this.pendingMouseEvent) {\r\n this.processMouseMovement(this.pendingMouseEvent)\r\n }\r\n this.rafId = null\r\n })\r\n }\r\n private updatePointerState(e: MouseEvent): void {\r\n const currentPoint = { x: e.clientX, y: e.clientY }\r\n this.trajectoryPositions.currentPoint = currentPoint\r\n if (this.enableMousePrediction) {\r\n this.trajectoryPositions.predictedPoint = predictNextMousePosition(\r\n currentPoint,\r\n this.trajectoryPositions.positions,\r\n this.trajectoryPredictionTime\r\n )\r\n } else {\r\n this.trajectoryPositions.predictedPoint = currentPoint\r\n }\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n if (this.rafId) {\r\n cancelAnimationFrame(this.rafId)\r\n this.rafId = null\r\n }\r\n this.pendingMouseEvent = null\r\n }\r\n\r\n private processMouseMovement(e: MouseEvent): void {\r\n try {\r\n this.updatePointerState(e)\r\n\r\n // Use for...of instead of forEach for better performance in hot code path\r\n // Avoids function call overhead and iterator creation on every mouse move\r\n for (const currentData of this.elements.values()) {\r\n if (!currentData.isIntersectingWithViewport) {\r\n continue\r\n }\r\n const expandedRect = currentData.elementBounds.expandedRect\r\n\r\n if (!this.enableMousePrediction) {\r\n if (isPointInRectangle(this.trajectoryPositions.currentPoint, expandedRect)) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"hover\" })\r\n return\r\n }\r\n // when enable mouse prediction is off, we only check if the mouse is physically hovering over the element\r\n } else if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.trajectoryPositions.predictedPoint,\r\n expandedRect\r\n )\r\n ) {\r\n this.callCallback(currentData, { kind: \"mouse\", subType: \"trajectory\" })\r\n }\r\n }\r\n\r\n this.emit({\r\n type: \"mouseTrajectoryUpdate\",\r\n predictionEnabled: this.enableMousePrediction,\r\n trajectoryPositions: this.trajectoryPositions,\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"processMouseMovement\")\r\n }\r\n }\r\n}\r\n","import type { Rect, ScrollDirection } from \"../types/types\"\n\nexport function getScrollDirection(oldRect: Rect, newRect: Rect): ScrollDirection {\n const scrollThreshold = 1\n const deltaY = newRect.top - oldRect.top\n const deltaX = newRect.left - oldRect.left\n\n // Check vertical scroll first (most common)\n if (deltaY < -scrollThreshold) {\n return \"down\" // Element moved up in viewport = scrolled down\n } else if (deltaY > scrollThreshold) {\n return \"up\" // Element moved down in viewport = scrolled up\n }\n\n // Check horizontal scroll\n if (deltaX < -scrollThreshold) {\n return \"right\" // Element moved left in viewport = scrolled right\n } else if (deltaX > scrollThreshold) {\n return \"left\" // Element moved right in viewport = scrolled left\n }\n\n return \"none\" // No significant movement detected\n}\n","import type { Point, ScrollDirection } from \"../types/types\"\n\nexport function predictNextScrollPosition(\n currentPoint: Point,\n direction: ScrollDirection,\n scrollMargin: number\n) {\n const { x, y } = currentPoint\n const predictedPoint = { x, y }\n\n switch (direction) {\n case \"down\":\n predictedPoint.y += scrollMargin\n break\n case \"up\":\n predictedPoint.y -= scrollMargin\n break\n case \"left\":\n predictedPoint.x -= scrollMargin\n break\n case \"right\":\n predictedPoint.x += scrollMargin\n break\n case \"none\":\n break\n default:\n direction satisfies never\n }\n return predictedPoint\n}\n","import type {\r\n ForesightElementData,\r\n Point,\r\n ScrollDirection,\r\n TrajectoryPositions,\r\n} from \"../types/types\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\nimport { getScrollDirection } from \"../helpers/getScrollDirection\"\r\nimport { predictNextScrollPosition } from \"../helpers/predictNextScrollPosition\"\r\nimport { lineSegmentIntersectsRect } from \"../helpers/lineSigmentIntersectsRect\"\r\n\r\nexport interface ScrollPredictorSettings {\r\n scrollMargin: number\r\n}\r\n\r\nexport interface ScrollPredictorConfig extends BasePredictorConfig {\r\n settings: ScrollPredictorSettings\r\n trajectoryPositions: Readonly<TrajectoryPositions>\r\n}\r\n\r\nexport class ScrollPredictor extends BasePredictor {\r\n protected initializeListeners(): void {\r\n // ScrollPredictor doesn't need direct event listeners\r\n // as it's called by the ForesightManager during position changes\r\n }\r\n\r\n private predictedScrollPoint: Point | null = null\r\n private scrollDirection: ScrollDirection | null = null\r\n public scrollMargin: number\r\n private trajectoryPositions: Readonly<TrajectoryPositions>\r\n\r\n constructor(config: ScrollPredictorConfig) {\r\n super(config)\r\n this.scrollMargin = config.settings.scrollMargin\r\n this.trajectoryPositions = config.trajectoryPositions\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n this.resetScrollProps()\r\n }\r\n\r\n public resetScrollProps(): void {\r\n this.scrollDirection = null\r\n this.predictedScrollPoint = null\r\n }\r\n\r\n public handleScrollPrefetch(elementData: ForesightElementData, newRect: DOMRect): void {\r\n if (!elementData.isIntersectingWithViewport) {\r\n return\r\n }\r\n\r\n try {\r\n // ONCE per handlePositionChange batch we decide what the scroll direction is\r\n this.scrollDirection =\r\n this.scrollDirection ?? getScrollDirection(elementData.elementBounds.originalRect, newRect)\r\n\r\n if (this.scrollDirection === \"none\") {\r\n return\r\n }\r\n\r\n // ONCE per handlePositionChange batch we decide the predicted scroll point\r\n this.predictedScrollPoint =\r\n this.predictedScrollPoint ??\r\n predictNextScrollPosition(\r\n this.trajectoryPositions.currentPoint,\r\n this.scrollDirection,\r\n this.scrollMargin\r\n )\r\n\r\n // Check if the scroll is going to intersect with an registered element\r\n if (\r\n lineSegmentIntersectsRect(\r\n this.trajectoryPositions.currentPoint,\r\n this.predictedScrollPoint,\r\n elementData.elementBounds.expandedRect\r\n )\r\n ) {\r\n this.callCallback(elementData, {\r\n kind: \"scroll\",\r\n subType: this.scrollDirection,\r\n })\r\n }\r\n\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.predictedScrollPoint,\r\n scrollDirection: this.scrollDirection,\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"handleScrollPrefetch\")\r\n }\r\n }\r\n\r\n protected handleError(error: unknown, context: string): void {\r\n super.handleError(error, context)\r\n\r\n // Emit fallback event on error\r\n this.emit({\r\n type: \"scrollTrajectoryUpdate\",\r\n currentPoint: this.trajectoryPositions.currentPoint,\r\n predictedPoint: this.trajectoryPositions.currentPoint,\r\n scrollDirection: \"none\",\r\n })\r\n }\r\n}\r\n","import { tabbable, type FocusableElement } from \"tabbable\"\r\nimport { getFocusedElementIndex } from \"../helpers/getFocusedElementIndex\"\r\nimport type { ForesightElement } from \"../types/types\"\r\nimport { BasePredictor, type BasePredictorConfig } from \"./BasePredictor\"\r\n\r\nexport interface TabPredictorSettings {\r\n tabOffset: number\r\n}\r\n\r\nexport interface TabPredictorConfig extends BasePredictorConfig {\r\n settings: TabPredictorSettings\r\n}\r\n\r\n/**\r\n * Manages the prediction of user intent based on Tab key navigation.\r\n *\r\n * This class is a specialist module controlled by the ForesightManager.\r\n * Its responsibilities are:\r\n * - Listening for `keydown` and `focusin` events to detect tabbing.\r\n * - Caching the list of tabbable elements on the page for performance.\r\n * - Invalidating the cache when the DOM changes.\r\n * - Predicting which registered elements the user is about to focus.\r\n * - Calling a provided callback when a prediction is made.\r\n */\r\nexport class TabPredictor extends BasePredictor {\r\n // Internal state for tab prediction\r\n private lastKeyDown: KeyboardEvent | null = null\r\n private tabbableElementsCache: FocusableElement[] = []\r\n private lastFocusedIndex: number | null = null\r\n public tabOffset: number\r\n\r\n constructor(config: TabPredictorConfig) {\r\n super(config)\r\n this.tabOffset = config.settings.tabOffset\r\n this.initializeListeners()\r\n }\r\n\r\n protected initializeListeners(): void {\r\n const { signal } = this.abortController\r\n document.addEventListener(\"keydown\", this.handleKeyDown, { signal })\r\n document.addEventListener(\"focusin\", this.handleFocusIn, { signal })\r\n }\r\n\r\n public invalidateCache() {\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n }\r\n\r\n public cleanup(): void {\r\n this.abort()\r\n this.tabbableElementsCache = []\r\n this.lastFocusedIndex = null\r\n this.lastKeyDown = null\r\n }\r\n\r\n // We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.\r\n // We dont use handleKeyDown for the full event because of 2 main reasons:\r\n // 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)\r\n // 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)\r\n private handleKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Tab\") {\r\n this.lastKeyDown = e\r\n }\r\n }\r\n\r\n private handleFocusIn = (e: FocusEvent) => {\r\n try {\r\n if (!this.lastKeyDown) {\r\n return\r\n }\r\n const targetElement = e.target\r\n if (!(targetElement instanceof HTMLElement)) {\r\n return\r\n }\r\n\r\n // tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values\r\n if (!this.tabbableElementsCache.length || this.lastFocusedIndex === -1) {\r\n this.tabbableElementsCache = tabbable(document.documentElement)\r\n }\r\n\r\n const isReversed = this.lastKeyDown.shiftKey\r\n\r\n const currentIndex: number = getFocusedElementIndex(\r\n isReversed,\r\n this.lastFocusedIndex,\r\n this.tabbableElementsCache,\r\n targetElement\r\n )\r\n\r\n this.lastFocusedIndex = currentIndex\r\n\r\n this.lastKeyDown = null\r\n const elementsToPredict: ForesightElement[] = []\r\n for (let i = 0; i <= this.tabOffset; i++) {\r\n const elementIndex = isReversed ? currentIndex - i : currentIndex + i\r\n const element = this.tabbableElementsCache[elementIndex]\r\n\r\n // Type guard: ensure element exists and is a valid ForesightElement\r\n if (element && element instanceof Element && this.elements.has(element)) {\r\n elementsToPredict.push(element)\r\n }\r\n }\r\n\r\n elementsToPredict.forEach(element => {\r\n const elementData = this.elements.get(element)\r\n if (elementData) {\r\n this.callCallback(elementData, {\r\n kind: \"tab\",\r\n subType: isReversed ? \"reverse\" : \"forwards\",\r\n })\r\n }\r\n })\r\n } catch (error) {\r\n this.handleError(error, \"handleFocusIn\")\r\n }\r\n }\r\n}\r\n","import type { FocusableElement } from \"tabbable\"\r\n\r\n/**\r\n * Finds the index of a focused element within a cache of tabbable elements.\r\n * It uses a predictive search for O(1) performance in the common case of\r\n * sequential tabbing, and falls back to a linear search O(n) if the\r\n * prediction fails.\r\n *\r\n * @param isReversed - True if the user is tabbing backward (Shift+Tab).\r\n * @param lastFocusedIndex - The index of the previously focused element, or null if none.\r\n * @param tabbableElementsCache - The array of all tabbable elements.\r\n * @param targetElement - The new HTML element that has received focus.\r\n * @returns The index of the targetElement in the cache, or -1 if not found.\r\n */\r\nexport function getFocusedElementIndex(\r\n isReversed: boolean,\r\n lastFocusedIndex: number | null,\r\n tabbableElementsCache: FocusableElement[],\r\n targetElement: HTMLElement\r\n): number {\r\n // First, try to predict the next index based on the last known position.\r\n if (lastFocusedIndex !== null && lastFocusedIndex > -1) {\r\n const predictedIndex = isReversed ? lastFocusedIndex - 1 : lastFocusedIndex + 1\r\n\r\n // Check if the prediction is valid and correct.\r\n if (\r\n predictedIndex >= 0 &&\r\n predictedIndex < tabbableElementsCache.length &&\r\n tabbableElementsCache[predictedIndex] === targetElement\r\n ) {\r\n return predictedIndex\r\n }\r\n }\r\n\r\n return tabbableElementsCache.findIndex(element => element === targetElement)\r\n}\r\n"],"mappings":"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","names":["PositionObserver","CircularBuffer","capacity","item","lastIndex","first","last","newCapacity","currentItems","itemsToKeep","result","i","startIndex","bufferIndex","clampNumber","number","lowerBound","upperBound","settingName","initialViewportState","rect","viewportWidth","viewportHeight","normalizeHitSlop","hitSlop","clampedValue","clampNumber","getExpandedRect","baseRect","areRectsEqual","rect1","rect2","isPointInRectangle","point","rect","evaluateRegistrationConditions","isTouchDevice","userUsesTouchDevice","isLimitedConnection","hasConnectionLimitations","connection","shouldUpdateSetting","newValue","currentValue","lineSegmentIntersectsRect","p1","p2","rect","t0","t1","dx","dy","clipTest","p","q","r","predictNextMousePosition","currentPoint","buffer","trajectoryPredictionTimeInMs","now","currentPosition","x","y","first","last","dt","dx","dy","vx","vy","trajectoryPredictionTimeInSeconds","predictedX","predictedY","BasePredictor","config","error","context","MousePredictor","BasePredictor","config","signal","currentPoint","predictNextMousePosition","currentData","expandedRect","lineSegmentIntersectsRect","isPointInRectangle","error","getScrollDirection","oldRect","newRect","deltaY","deltaX","predictNextScrollPosition","currentPoint","direction","scrollMargin","x","y","predictedPoint","ScrollPredictor","BasePredictor","config","elementData","newRect","getScrollDirection","predictNextScrollPosition","lineSegmentIntersectsRect","error","context","tabbable","getFocusedElementIndex","isReversed","lastFocusedIndex","tabbableElementsCache","targetElement","predictedIndex","element","TabPredictor","BasePredictor","config","targetElement","tabbable","isReversed","currentIndex","getFocusedElementIndex","elementsToPredict","i","elementIndex","element","elementData","error","signal","ForesightManager","_ForesightManager","CircularBuffer","mutationsList","mutation","element","entries","entry","elementData","isPointInRectangle","updatedProps","isNowIntersecting","getExpandedRect","props","eventType","listener","options","listeners","index","event","error","callback","hitSlop","name","shouldRegister","isTouchDevice","isLimitedConnection","evaluateRegistrationConditions","initialRect","normalizedHitSlop","normalizeHitSlop","initialViewportState","unregisterReason","wasLastElement","newValue","setting","min","max","shouldUpdateSetting","clampNumber","changedSettings","oldTrajectoryPredictionTime","oldPositionHistorySize","oldScrollMargin","oldTabOffset","oldEnableMousePrediction","oldEnableScrollPrediction","ScrollPredictor","oldEnableTabPrediction","TabPredictor","oldHitSlop","normalizedNewHitSlop","areRectsEqual","callbackHitType","asyncCallbackWrapper","start","errorMessage","settings","dependencies","MousePredictor","PositionObserver","newOriginalRect","expandedRect","updatedElementData"]}
|
package/package.json
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js.foresight",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Predicts mouse trajectory to trigger actions as users approach elements, enabling anticipatory UI updates or pre-loading. Made with vanilla javascript and usable in every framework.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
5
9
|
"exports": {
|
|
6
10
|
".": {
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
"default": "./dist/index.mjs"
|
|
10
|
-
},
|
|
11
|
-
"require": {
|
|
12
|
-
"types": "./dist/index.d.ts",
|
|
13
|
-
"default": "./dist/index.js"
|
|
14
|
-
}
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
15
13
|
}
|
|
16
14
|
},
|
|
17
15
|
"homepage": "https://foresightjs.com/",
|
|
@@ -50,7 +48,7 @@
|
|
|
50
48
|
"happy-dom": "^18.0.1",
|
|
51
49
|
"jsdom": "^26.1.0",
|
|
52
50
|
"tslib": "^2.8.1",
|
|
53
|
-
"tsup": "^8.
|
|
51
|
+
"tsup": "^8.5.0",
|
|
54
52
|
"typescript": "^5.8.3",
|
|
55
53
|
"vitest": "^3.2.4"
|
|
56
54
|
},
|