js.foresight 3.5.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -46
  3. package/dist/BaseForesightModule-CJzWVlF2.mjs +2 -0
  4. package/dist/BaseForesightModule-CJzWVlF2.mjs.map +1 -0
  5. package/dist/DesktopHandler-CR9nLuzu.mjs +2 -0
  6. package/dist/DesktopHandler-CR9nLuzu.mjs.map +1 -0
  7. package/dist/ElementObservingModule-QE8ZBcnz.mjs +2 -0
  8. package/dist/ElementObservingModule-QE8ZBcnz.mjs.map +1 -0
  9. package/dist/ScrollPredictor-ZHRqX0lh.mjs +2 -0
  10. package/dist/ScrollPredictor-ZHRqX0lh.mjs.map +1 -0
  11. package/dist/TabPredictor-C2zyaEQZ.mjs +2 -0
  12. package/dist/TabPredictor-C2zyaEQZ.mjs.map +1 -0
  13. package/dist/TouchDeviceHandler-BPzPdr-z.mjs +2 -0
  14. package/dist/TouchDeviceHandler-BPzPdr-z.mjs.map +1 -0
  15. package/dist/TouchStartPredictor-r7R_E74t.mjs +2 -0
  16. package/dist/TouchStartPredictor-r7R_E74t.mjs.map +1 -0
  17. package/dist/ViewportPredictor-aTE2EmU2.mjs +2 -0
  18. package/dist/ViewportPredictor-aTE2EmU2.mjs.map +1 -0
  19. package/dist/index.d.mts +594 -0
  20. package/dist/index.d.mts.map +1 -0
  21. package/dist/index.mjs +2 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/lineSegmentIntersectsRect-x9nicliA.mjs +2 -0
  24. package/dist/lineSegmentIntersectsRect-x9nicliA.mjs.map +1 -0
  25. package/dist/rectAndHitSlop-T7Z3PZlb.mjs +2 -0
  26. package/dist/rectAndHitSlop-T7Z3PZlb.mjs.map +1 -0
  27. package/package.json +27 -28
  28. package/dist/DesktopHandler-BOXAW4XX.js +0 -1
  29. package/dist/ScrollPredictor-Y7NELMBI.js +0 -1
  30. package/dist/TabPredictor-HA2SV3CY.js +0 -1
  31. package/dist/TouchDeviceHandler-JWBQ2YOV.js +0 -1
  32. package/dist/TouchStartPredictor-ZH3KJG2C.js +0 -1
  33. package/dist/ViewportPredictor-H3GLDETY.js +0 -1
  34. package/dist/chunk-44N4MCQB.js +0 -1
  35. package/dist/chunk-AODZNE3S.js +0 -1
  36. package/dist/chunk-PAYO6NXN.js +0 -1
  37. package/dist/index.d.ts +0 -531
  38. package/dist/index.js +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/helpers/DerivedMapView.ts","../src/helpers/shouldRegister.ts","../src/helpers/initialViewportState.ts","../src/helpers/createInitialState.ts","../src/core/ForesightEventEmitter.ts","../src/helpers/shouldUpdateSetting.ts","../src/managers/SettingsManager.ts","../src/managers/ForesightManager.ts"],"sourcesContent":["/**\n * Read-only map that exposes external state derived from internal state,\n * so internals are never leaked to consumers.\n */\nexport class DerivedMapView<K, TSource, TDerived> implements ReadonlyMap<K, TDerived> {\n constructor(\n private source: Map<K, TSource>,\n private derive: (value: TSource) => TDerived\n ) {}\n\n get size() {\n return this.source.size\n }\n\n get(key: K): TDerived | undefined {\n const entry = this.source.get(key)\n\n return entry === undefined ? undefined : this.derive(entry)\n }\n\n has(key: K): boolean {\n return this.source.has(key)\n }\n\n forEach(cb: (value: TDerived, key: K, map: ReadonlyMap<K, TDerived>) => void): void {\n this.source.forEach((entry, key) => cb(this.derive(entry), key, this))\n }\n\n *entries(): MapIterator<[K, TDerived]> {\n for (const [key, entry] of this.source) {\n yield [key, this.derive(entry)]\n }\n }\n\n *values(): MapIterator<TDerived> {\n for (const entry of this.source.values()) {\n yield this.derive(entry)\n }\n }\n\n keys(): MapIterator<K> {\n return this.source.keys()\n }\n\n [Symbol.iterator](): MapIterator<[K, TDerived]> {\n return this.entries()\n }\n\n get [Symbol.toStringTag](): string {\n return \"DerivedMapView\"\n }\n}\n","import { MinimumConnectionType } from \"../types/types\"\n\ntype ShouldRegister = {\n shouldRegister: boolean\n isTouchDevice: boolean\n isLimitedConnection: boolean\n}\n\nexport const evaluateRegistrationConditions = (\n minimumConnectionType: MinimumConnectionType\n): ShouldRegister => {\n const isTouchDevice = userUsesTouchDevice()\n const isLimitedConnection = hasConnectionLimitations(minimumConnectionType)\n const shouldRegister = !isLimitedConnection\n\n return { isTouchDevice, isLimitedConnection, shouldRegister }\n}\n\n/**\n * Detects if the current device is likely a touch-enabled device.\n * It checks for coarse pointer media query and the presence of touch points.\n *\n * @returns `true` if the device is likely touch-enabled, `false` otherwise.\n */\nexport const userUsesTouchDevice = (): boolean => {\n if (typeof window === \"undefined\" || typeof navigator === \"undefined\") {\n return false\n }\n\n return window.matchMedia(\"(pointer: coarse)\").matches && navigator.maxTouchPoints > 0\n}\n\n/**\n * Checks if the user has connection limitations (slow network or data saver enabled).\n *\n * @returns {boolean} True if connection is limited, false if safe to prefetch\n * @example\n * if (!hasConnectionLimitations()) {\n * prefetchResource('/api/data');\n * }\n */\nconst hasConnectionLimitations = (minimumConnectionType: MinimumConnectionType): boolean => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const connection = (navigator as any).connection\n if (!connection) {\n return false\n }\n\n // Define array of connection types from slowest to fastest\n const connectionTypes: MinimumConnectionType[] = [\"slow-2g\", \"2g\", \"3g\", \"4g\"]\n // Get index of user's current connection speed in the array (e.g. \"4g\" would be index 3)\n const currentConnectionIndex = connectionTypes.indexOf(\n connection.effectiveType as MinimumConnectionType\n )\n // Get index of the minimum connection speed required in settings (e.g. \"3g\" would be index 2)\n const minimumConnectionIndex = connectionTypes.indexOf(minimumConnectionType)\n\n // If user's connection is slower than the minimum required, or data saver is enabled, return true\n return currentConnectionIndex < minimumConnectionIndex || connection.saveData\n}\n","export const initialViewportState = (rect: DOMRect) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return false\n }\n\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight\n\n return rect.top < viewportHeight && rect.bottom > 0 && rect.left < viewportWidth && rect.right > 0\n}\n","import {\n DEFAULT_ENABLE_MOUSE_PREDICTION,\n DEFAULT_ENABLE_SCROLL_PREDICTION,\n DEFAULT_ENABLE_TAB_PREDICTION,\n DEFAULT_HITSLOP,\n DEFAULT_POSITION_HISTORY_SIZE,\n DEFAULT_SCROLL_MARGIN,\n DEFAULT_STALE_TIME,\n DEFAULT_TAB_OFFSET,\n DEFAULT_TRAJECTORY_PREDICTION_TIME,\n} from \"../constants\"\nimport type {\n CallbackHits,\n ForesightElementInternal,\n ForesightElementState,\n ForesightManagerSettings,\n ForesightRegisterOptions,\n HitSlop,\n} from \"../types/types\"\nimport { getExpandedRect, normalizeHitSlop } from \"./rectAndHitSlop\"\nimport { initialViewportState } from \"./initialViewportState\"\n\nexport const createInitialCallbackHits = (): CallbackHits => {\n return {\n mouse: {\n hover: 0,\n trajectory: 0,\n },\n tab: {\n forwards: 0,\n reverse: 0,\n },\n scroll: {\n down: 0,\n left: 0,\n right: 0,\n up: 0,\n },\n touch: 0,\n viewport: 0,\n total: 0,\n }\n}\n\nexport const createDefaultManagerSettings = (): ForesightManagerSettings => {\n return {\n enableManagerLogging: false,\n enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,\n enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,\n positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,\n trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,\n scrollMargin: DEFAULT_SCROLL_MARGIN,\n defaultHitSlop: {\n top: DEFAULT_HITSLOP,\n left: DEFAULT_HITSLOP,\n right: DEFAULT_HITSLOP,\n bottom: DEFAULT_HITSLOP,\n },\n enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,\n tabOffset: DEFAULT_TAB_OFFSET,\n touchDeviceStrategy: \"onTouchStart\",\n minimumConnectionType: \"3g\",\n }\n}\n\n/**\n * Creates the internal record for a newly registered element, including the\n * initial immutable state snapshot.\n */\nexport const createElementInternal = (\n options: ForesightRegisterOptions,\n id: string,\n defaultHitSlop: Exclude<HitSlop, number>,\n isLimitedConnection: boolean\n): ForesightElementInternal => {\n const { element, callback, hitSlop, name, meta, reactivateAfter, enabled } = options\n\n const initialRect = element.getBoundingClientRect()\n const normalizedHitSlop = hitSlop !== undefined ? normalizeHitSlop(hitSlop) : defaultHitSlop\n const isEnabled = enabled !== false\n\n const state: ForesightElementState = {\n id,\n name: name || element.id || \"unnamed\",\n meta: meta ?? {},\n hitSlop: normalizedHitSlop,\n isLimitedConnection,\n isIntersectingWithViewport: initialViewportState(initialRect),\n isRegistered: true,\n isActive: isEnabled && !isLimitedConnection,\n isParked: false,\n isEnabled,\n isPredicted: false,\n isCallbackRunning: false,\n hitCount: 0,\n registerCount: 1,\n durationMs: undefined,\n status: undefined,\n error: null,\n reactivateAfter: reactivateAfter ?? DEFAULT_STALE_TIME,\n }\n\n return {\n state,\n bounds: {\n originalRect: initialRect,\n expandedRect: getExpandedRect(initialRect, normalizedHitSlop),\n },\n invokedAt: undefined,\n completedAt: undefined,\n element,\n callback,\n reactivateTimeoutId: undefined,\n subscribers: new Set(),\n boundsSubscribers: new Set(),\n }\n}\n\n/**\n * Snapshot of the flat state shape for an element that is not (yet) tracked by the\n * manager. Reuses the same fields as a registered element so consumers don't need\n * to special-case `null`.\n *\n * Used in two situations:\n * 1. The manager refuses to register the element (touch device, limited connection,\n * etc.) - pass `isLimitedConnection` to reflect that.\n * 2. Framework wrappers (React, Vue) need an initial snapshot before `register()`\n * has run. `register()` requires a real DOM element, which only exists after\n * the consumer's first render commits, so the wrapper returns this snapshot\n * during that brief window. Pass `isLimitedConnection: false` for this case.\n */\nexport const createUnregisteredSnapshot = (isLimitedConnection: boolean): ForesightElementState => {\n return {\n id: \"\",\n name: \"\",\n meta: {},\n // Fresh object per call - Vue wraps the snapshot reactively.\n hitSlop: { top: 0, left: 0, right: 0, bottom: 0 },\n isLimitedConnection,\n isIntersectingWithViewport: false,\n isRegistered: false,\n isActive: false,\n isParked: false,\n isEnabled: false,\n isPredicted: false,\n isCallbackRunning: false,\n hitCount: 0,\n registerCount: 0,\n durationMs: undefined,\n status: undefined,\n error: null,\n reactivateAfter: DEFAULT_STALE_TIME,\n }\n}\n","import type { ForesightEvent, ForesightEventListener, ForesightEventMap } from \"../types/types\"\n\n/**\n * Generic event emitter for ForesightJS events.\n * Handles event registration, removal, and emission with error handling.\n */\nexport class ForesightEventEmitter {\n private eventListeners: Map<ForesightEvent, ForesightEventListener[]> = new Map()\n\n public addEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>,\n options?: { signal?: AbortSignal }\n ): void {\n if (options?.signal?.aborted) {\n return\n }\n\n const listeners = this.eventListeners.get(eventType) ?? []\n listeners.push(listener as ForesightEventListener)\n this.eventListeners.set(eventType, listeners)\n\n options?.signal?.addEventListener(\"abort\", () => this.removeEventListener(eventType, listener))\n }\n\n public removeEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>\n ): void {\n const listeners = this.eventListeners.get(eventType)\n\n if (!listeners) {\n return\n }\n\n const index = listeners.indexOf(listener as ForesightEventListener)\n if (index > -1) {\n listeners.splice(index, 1)\n }\n }\n\n /**\n * Emit an event to all registered listeners.\n * Errors in listeners are caught and logged, not propagated.\n */\n public emit<K extends ForesightEvent>(event: ForesightEventMap[K]): void {\n const listeners = this.eventListeners.get(event.type)\n\n if (!listeners || listeners.length === 0) {\n return\n }\n\n for (let i = 0; i < listeners.length; i++) {\n try {\n const listener = listeners[i]\n if (listener) {\n listener(event)\n }\n } catch (error) {\n console.error(`Error in ForesightManager event listener ${i} for ${event.type}:`, error)\n }\n }\n }\n\n /**\n * Check if there are any listeners registered for a specific event type.\n * Useful for avoiding expensive event object creation when no one is listening.\n */\n public hasListeners<K extends ForesightEvent>(eventType: K): boolean {\n const listeners = this.eventListeners.get(eventType)\n\n return listeners !== undefined && listeners.length > 0\n }\n\n public getEventListeners(): ReadonlyMap<ForesightEvent, ForesightEventListener[]> {\n return this.eventListeners\n }\n}\n","/**\n * Checks if a setting should be updated.\n * Returns true if the newValue is defined and different from the currentValue.\n * Uses a type predicate to narrow the type of newValue in the calling scope.\n *\n * @param newValue The potentially new value for the setting (can be undefined).\n * @param currentValue The current value of the setting.\n * @returns True if the setting should be updated, false otherwise.\n */\nexport const shouldUpdateSetting = <T>(\n newValue: T | undefined,\n currentValue: T\n): newValue is NonNullable<T> => {\n // NonNullable<T> ensures that if T itself could be undefined (e.g. T = number | undefined),\n // the predicate narrows to the non-undefined part (e.g. number).\n // If T is already non-nullable (e.g. T = number), it remains T (e.g. number).\n return newValue !== undefined && currentValue !== newValue\n}\n","import {\n MAX_POSITION_HISTORY_SIZE,\n MAX_SCROLL_MARGIN,\n MAX_TAB_OFFSET,\n MAX_TRAJECTORY_PREDICTION_TIME,\n MIN_POSITION_HISTORY_SIZE,\n MIN_SCROLL_MARGIN,\n MIN_TAB_OFFSET,\n MIN_TRAJECTORY_PREDICTION_TIME,\n} from \"../constants\"\nimport { clampNumber } from \"../helpers/clampNumber\"\nimport { areRectsEqual, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { shouldUpdateSetting } from \"../helpers/shouldUpdateSetting\"\nimport type {\n ForesightManagerSettings,\n ManagerBooleanSettingKeys,\n NumericSettingKeys,\n UpdatedManagerSetting,\n UpdateForsightManagerSettings,\n} from \"../types/types\"\n\n/** Configuration for numeric settings with their min/max constraints */\nconst NUMERIC_SETTING_CONFIGS: Record<NumericSettingKeys, { min: number; max: number }> = {\n trajectoryPredictionTime: {\n min: MIN_TRAJECTORY_PREDICTION_TIME,\n max: MAX_TRAJECTORY_PREDICTION_TIME,\n },\n positionHistorySize: {\n min: MIN_POSITION_HISTORY_SIZE,\n max: MAX_POSITION_HISTORY_SIZE,\n },\n scrollMargin: {\n min: MIN_SCROLL_MARGIN,\n max: MAX_SCROLL_MARGIN,\n },\n tabOffset: {\n min: MIN_TAB_OFFSET,\n max: MAX_TAB_OFFSET,\n },\n}\n\n/**\n * Updates a numeric setting with clamping.\n * Returns true if the setting was changed.\n */\nconst updateNumericSetting = (\n settings: ForesightManagerSettings,\n key: NumericSettingKeys,\n newValue: number | undefined\n): boolean => {\n if (!shouldUpdateSetting(newValue, settings[key])) {\n return false\n }\n\n const { min, max } = NUMERIC_SETTING_CONFIGS[key]\n settings[key] = clampNumber(newValue, min, max, key)\n\n return true\n}\n\n/**\n * Updates a boolean setting.\n * Returns true if the setting was changed.\n */\nconst updateBooleanSetting = (\n settings: ForesightManagerSettings,\n key: ManagerBooleanSettingKeys,\n newValue: boolean | undefined\n): boolean => {\n if (!shouldUpdateSetting(newValue, settings[key])) {\n return false\n }\n\n settings[key] = newValue as boolean\n\n return true\n}\n\n/**\n * Apply initial settings during construction (no event emission, no side effects).\n * Mutates the settings object directly.\n */\nexport const initializeSettings = (\n settings: ForesightManagerSettings,\n props: Partial<UpdateForsightManagerSettings>\n): void => {\n // Numeric settings\n updateNumericSetting(settings, \"trajectoryPredictionTime\", props.trajectoryPredictionTime)\n updateNumericSetting(settings, \"positionHistorySize\", props.positionHistorySize)\n updateNumericSetting(settings, \"scrollMargin\", props.scrollMargin)\n updateNumericSetting(settings, \"tabOffset\", props.tabOffset)\n\n // Boolean settings\n updateBooleanSetting(settings, \"enableMousePrediction\", props.enableMousePrediction)\n updateBooleanSetting(settings, \"enableScrollPrediction\", props.enableScrollPrediction)\n updateBooleanSetting(settings, \"enableTabPrediction\", props.enableTabPrediction)\n updateBooleanSetting(settings, \"enableManagerLogging\", props.enableManagerLogging)\n\n // Object/special settings\n if (props.defaultHitSlop !== undefined) {\n settings.defaultHitSlop = normalizeHitSlop(props.defaultHitSlop)\n }\n\n if (props.touchDeviceStrategy !== undefined) {\n settings.touchDeviceStrategy = props.touchDeviceStrategy\n }\n\n if (props.minimumConnectionType !== undefined) {\n settings.minimumConnectionType = props.minimumConnectionType\n }\n}\n\n/**\n * Result of applying settings changes at runtime.\n * Contains the list of changed settings for event emission.\n */\ninterface SettingsChangeResult {\n changedSettings: UpdatedManagerSetting[]\n positionHistorySizeChanged: boolean\n scrollPredictionChanged: boolean\n tabPredictionChanged: boolean\n hitSlopChanged: boolean\n touchStrategyChanged: boolean\n}\n\n/**\n * Apply settings changes at runtime.\n * Returns information about what changed for the caller to handle side effects.\n */\nexport const applySettingsChanges = (\n settings: ForesightManagerSettings,\n props: Partial<UpdateForsightManagerSettings> | undefined\n): SettingsChangeResult => {\n const changedSettings: UpdatedManagerSetting[] = []\n let positionHistorySizeChanged = false\n let scrollPredictionChanged = false\n let tabPredictionChanged = false\n let hitSlopChanged = false\n let touchStrategyChanged = false\n\n if (!props) {\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n }\n }\n\n // Numeric settings\n const numericKeys: NumericSettingKeys[] = [\n \"trajectoryPredictionTime\",\n \"positionHistorySize\",\n \"scrollMargin\",\n \"tabOffset\",\n ]\n\n for (const key of numericKeys) {\n const oldValue = settings[key]\n if (updateNumericSetting(settings, key, props[key])) {\n changedSettings.push({\n setting: key,\n oldValue,\n newValue: settings[key],\n } as UpdatedManagerSetting)\n\n if (key === \"positionHistorySize\") {\n positionHistorySizeChanged = true\n }\n }\n }\n\n // Boolean settings\n const booleanKeys: ManagerBooleanSettingKeys[] = [\n \"enableMousePrediction\",\n \"enableScrollPrediction\",\n \"enableTabPrediction\",\n ]\n\n for (const key of booleanKeys) {\n const oldValue = settings[key]\n if (updateBooleanSetting(settings, key, props[key])) {\n changedSettings.push({\n setting: key,\n oldValue,\n newValue: settings[key],\n } as UpdatedManagerSetting)\n\n if (key === \"enableScrollPrediction\") {\n scrollPredictionChanged = true\n }\n\n if (key === \"enableTabPrediction\") {\n tabPredictionChanged = true\n }\n }\n }\n\n // HitSlop\n if (props.defaultHitSlop !== undefined) {\n const oldHitSlop = settings.defaultHitSlop\n const normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop)\n\n if (!areRectsEqual(oldHitSlop, normalizedNewHitSlop)) {\n settings.defaultHitSlop = normalizedNewHitSlop\n changedSettings.push({\n setting: \"defaultHitSlop\",\n oldValue: oldHitSlop,\n newValue: normalizedNewHitSlop,\n })\n hitSlopChanged = true\n }\n }\n\n // Touch strategy\n if (props.touchDeviceStrategy !== undefined) {\n const oldValue = settings.touchDeviceStrategy\n settings.touchDeviceStrategy = props.touchDeviceStrategy\n changedSettings.push({\n setting: \"touchDeviceStrategy\",\n oldValue,\n newValue: props.touchDeviceStrategy,\n })\n touchStrategyChanged = true\n }\n\n // Minimum connection type\n if (props.minimumConnectionType !== undefined) {\n const oldValue = settings.minimumConnectionType\n settings.minimumConnectionType = props.minimumConnectionType\n changedSettings.push({\n setting: \"minimumConnectionType\",\n oldValue,\n newValue: props.minimumConnectionType,\n })\n }\n\n return {\n changedSettings,\n positionHistorySizeChanged,\n scrollPredictionChanged,\n tabPredictionChanged,\n hitSlopChanged,\n touchStrategyChanged,\n }\n}\n","import { DerivedMapView } from \"../helpers/DerivedMapView\"\nimport { areRectsEqual, getExpandedRect, normalizeHitSlop } from \"../helpers/rectAndHitSlop\"\nimport { evaluateRegistrationConditions, userUsesTouchDevice } from \"../helpers/shouldRegister\"\nimport {\n createDefaultManagerSettings,\n createElementInternal,\n createInitialCallbackHits,\n} from \"../helpers/createInitialState\"\nimport { ForesightEventEmitter } from \"../core/ForesightEventEmitter\"\nimport type { ForesightModuleDependencies } from \"../core/BaseForesightModule\"\nimport type { ElementObservingModule } from \"../core/ElementObservingModule\"\nimport { applySettingsChanges, initializeSettings } from \"./SettingsManager\"\nimport type {\n CallbackHits,\n CallbackHitType,\n callbackStatus,\n CurrentDeviceStrategy,\n ElementBounds,\n ElementUnregisteredReason,\n ForesightElement,\n ForesightElementInternal,\n ForesightElementState,\n ForesightEvent,\n ForesightEventListener,\n ForesightManagerData,\n ForesightManagerSettings,\n ForesightModules,\n ForesightRegisterNodeListOptions,\n ForesightRegisterOptions,\n ForesightRegisterOptionsWithoutElement,\n ForesightRegisterResult,\n UpdateForsightManagerSettings,\n} from \"../types/types\"\nimport type { DesktopHandler } from \"./DesktopHandler\"\nimport type { TouchDeviceHandler } from \"./TouchDeviceHandler\"\n\n/**\n * Manages the prediction of user intent based on mouse trajectory and element interactions.\n *\n * ForesightManager is a singleton class responsible for:\n * - Registering HTML elements to monitor.\n * - Tracking mouse movements and predicting future cursor positions.\n * - Detecting when a predicted trajectory intersects with a registered element's bounds.\n * - Invoking callbacks associated with elements upon predicted or actual interaction.\n * - Deactivating elements after their callback completes, with optional reactivation via `reactivateAfter`.\n * - Handling global settings for prediction behavior (e.g., history size, prediction time).\n * - Delegating element bounds observation to device handlers ({@link DesktopHandler}, {@link TouchDeviceHandler}).\n * - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.\n * - Detecting broader layout shifts via {@link MutationObserver} to update element positions.\n *\n * It should be initialized once using {@link ForesightManager.initialize} and then\n * accessed via the static getter {@link ForesightManager.instance}.\n */\nexport class ForesightManager {\n private static manager: ForesightManager\n\n /** Internal entries containing full element data, callbacks, and subscribers. */\n private elementEntries: Map<ForesightElement, ForesightElementInternal> = new Map()\n /** Public read-only view exposing only external state, derived from {@link elementEntries}. */\n public readonly registeredElements: ReadonlyMap<ForesightElement, ForesightElementState> =\n new DerivedMapView(this.elementEntries, (entry: ForesightElementInternal) => entry.state)\n\n private idCounter: number = 0\n private activeElementCount: number = 0\n private parkedElementCount: number = 0\n\n private desktopHandler: DesktopHandler | null = null\n private touchDeviceHandler: TouchDeviceHandler | null = null\n private currentlyActiveHandler: ElementObservingModule | null = null\n private handlerDependencies: ForesightModuleDependencies\n\n private isSetup: boolean = false\n private pendingPointerEvent: PointerEvent | null = null\n private rafId: number | null = null\n private domObserver: MutationObserver | null = null\n private currentDeviceStrategy: CurrentDeviceStrategy = userUsesTouchDevice() ? \"touch\" : \"mouse\"\n\n private eventEmitter = new ForesightEventEmitter()\n private _globalCallbackHits: CallbackHits = createInitialCallbackHits()\n private _globalSettings: ForesightManagerSettings = createDefaultManagerSettings()\n\n private constructor(initialSettings?: Partial<UpdateForsightManagerSettings>) {\n if (initialSettings !== undefined) {\n initializeSettings(this._globalSettings, initialSettings)\n }\n\n this.handlerDependencies = {\n elements: this.elementEntries,\n callCallback: this.callCallback.bind(this),\n emit: this.eventEmitter.emit.bind(this.eventEmitter),\n hasListeners: this.eventEmitter.hasListeners.bind(this.eventEmitter),\n updateElementState: this.updateElementState.bind(this),\n updateElementBounds: this.updateElementBounds.bind(this),\n settings: this._globalSettings,\n }\n\n // Handlers are created lazily when first needed as of 3.4.0\n this.devLog(`ForesightManager initialized with device strategy: ${this.currentDeviceStrategy}`)\n this.initializeGlobalListeners()\n }\n\n private async getOrCreateDesktopHandler(): Promise<DesktopHandler> {\n if (!this.desktopHandler) {\n const { DesktopHandler } = await import(\"./DesktopHandler\")\n this.desktopHandler = new DesktopHandler(this.handlerDependencies)\n this.devLog(\"DesktopHandler lazy loaded\")\n }\n\n return this.desktopHandler\n }\n\n private async getOrCreateTouchHandler(): Promise<TouchDeviceHandler> {\n if (!this.touchDeviceHandler) {\n const { TouchDeviceHandler } = await import(\"./TouchDeviceHandler\")\n this.touchDeviceHandler = new TouchDeviceHandler(this.handlerDependencies)\n this.devLog(\"TouchDeviceHandler lazy loaded\")\n }\n\n return this.touchDeviceHandler\n }\n\n public static initialize(props?: Partial<UpdateForsightManagerSettings>): ForesightManager {\n if (!this.isInitiated) {\n ForesightManager.manager = new ForesightManager(props)\n }\n\n return ForesightManager.manager\n }\n\n public static get isInitiated(): Readonly<boolean> {\n return !!ForesightManager.manager\n }\n\n public static get instance(): ForesightManager {\n return this.initialize()\n }\n\n private generateId(): string {\n return `foresight-${++this.idCounter}`\n }\n\n private get isUsingDesktopHandler(): boolean {\n return this.currentDeviceStrategy === \"mouse\" || this.currentDeviceStrategy === \"pen\"\n }\n\n public addEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>,\n options?: { signal?: AbortSignal }\n ): void {\n this.eventEmitter.addEventListener(eventType, listener, options)\n }\n\n public removeEventListener<K extends ForesightEvent>(\n eventType: K,\n listener: ForesightEventListener<K>\n ): void {\n this.eventEmitter.removeEventListener(eventType, listener)\n }\n\n public hasListeners<K extends ForesightEvent>(eventType: K): boolean {\n return this.eventEmitter.hasListeners(eventType)\n }\n\n /**\n * Subscribe to logical state changes for a specific element.\n * The listener is called (with no arguments) whenever the element's\n * immutable state snapshot is replaced. Never fires for geometry-only\n * changes (scroll/resize) - see {@link subscribeToElementBounds}.\n * Use {@link registeredElements} to read the latest state inside the listener.\n *\n * @returns An unsubscribe function, or `undefined` if the element is not registered.\n */\n public subscribeToElement(\n element: ForesightElement,\n listener: () => void\n ): (() => void) | undefined {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return undefined\n }\n\n return this.makeSubscribe(entry.subscribers)(listener)\n }\n\n /**\n * Subscribe to geometry changes for a specific element (position/size, fired\n * on every scroll/resize tick while visible). Use {@link getElementBounds}\n * to read the latest geometry inside the listener.\n *\n * @returns An unsubscribe function, or `undefined` if the element is not registered.\n */\n public subscribeToElementBounds(\n element: ForesightElement,\n listener: () => void\n ): (() => void) | undefined {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return undefined\n }\n\n return this.makeSubscribe(entry.boundsSubscribers)(listener)\n }\n\n /**\n * Returns the current immutable geometry snapshot for a registered element,\n * or `undefined` if the element is not registered.\n */\n public getElementBounds(element: ForesightElement): ElementBounds | undefined {\n return this.elementEntries.get(element)?.bounds\n }\n\n public get getManagerData(): Readonly<ForesightManagerData> {\n return {\n registeredElements: this.registeredElements,\n globalSettings: this._globalSettings,\n globalCallbackHits: this._globalCallbackHits,\n eventListeners: this.eventEmitter.getEventListeners(),\n currentDeviceStrategy: this.currentDeviceStrategy,\n activeElementCount: this.activeElementCount,\n parkedElementCount: this.parkedElementCount,\n loadedModules: this.getLoadedModulesSnapshot(),\n }\n }\n\n private getLoadedModulesSnapshot(): ForesightModules {\n const desktopPredictors = this.desktopHandler?.loadedPredictors\n const touchPredictors = this.touchDeviceHandler?.loadedPredictors\n\n return {\n desktopHandler: this.desktopHandler !== null,\n touchHandler: this.touchDeviceHandler !== null,\n predictors: {\n mouse: desktopPredictors?.mouse ?? false,\n tab: desktopPredictors?.tab ?? false,\n scroll: desktopPredictors?.scroll ?? false,\n viewport: touchPredictors?.viewport ?? false,\n touchStart: touchPredictors?.touchStart ?? false,\n },\n }\n }\n\n public register(options: ForesightRegisterNodeListOptions): ForesightRegisterResult[]\n public register(options: ForesightRegisterOptions): ForesightRegisterResult\n public register(\n options: ForesightRegisterOptions | ForesightRegisterNodeListOptions\n ): ForesightRegisterResult | ForesightRegisterResult[] {\n const { element: elements, ...rest } = options\n\n if (elements instanceof NodeList) {\n return Array.from(elements, element => this.registerElement({ ...rest, element }))\n }\n\n return this.registerElement({ ...rest, element: elements })\n }\n\n private registerElement(options: ForesightRegisterOptions): ForesightRegisterResult {\n // On a limited connection (data saver / slow network) the element is\n // registered so it can be patched and tracked, but stays inactive (never\n // predicted, never fires its callback) to avoid consuming data.\n // See createElementInternal / setElementEnabled.\n const { isLimitedConnection } = evaluateRegistrationConditions(\n this._globalSettings.minimumConnectionType\n )\n\n const previousEntry = this.elementEntries.get(options.element)\n\n if (previousEntry) {\n this.updateElementOptions(options.element, options)\n this.updateElementState(previousEntry, {\n registerCount: previousEntry.state.registerCount + 1,\n })\n\n return {\n ...previousEntry.state,\n unregister: () => {\n this.unregister(options.element)\n },\n subscribe: this.makeSubscribe(previousEntry.subscribers),\n getSnapshot: () => previousEntry.state,\n subscribeToBounds: this.makeSubscribe(previousEntry.boundsSubscribers),\n getBounds: () => previousEntry.bounds,\n }\n }\n\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n const entry = createElementInternal(\n options,\n this.generateId(),\n this._globalSettings.defaultHitSlop,\n isLimitedConnection\n )\n\n this.elementEntries.set(options.element, entry)\n\n // Inactive elements (disabled or limited connection) are not observed or\n // counted as active until they are (re)activated.\n if (entry.state.isActive) {\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(options.element)\n }\n\n this.eventEmitter.emit({\n type: \"elementRegistered\",\n timestamp: Date.now(),\n element: options.element,\n state: entry.state,\n })\n\n return {\n ...entry.state,\n unregister: () => {\n this.unregister(options.element)\n },\n subscribe: this.makeSubscribe(entry.subscribers),\n getSnapshot: () => entry.state,\n subscribeToBounds: this.makeSubscribe(entry.boundsSubscribers),\n getBounds: () => entry.bounds,\n }\n }\n\n /**\n * Updates the options of an already-registered element.\n * Only the provided fields are updated; omitted fields keep their current values.\n * If a reactivation timeout is pending and reactivateAfter changed, the timeout is rescheduled.\n *\n * @throws Error if the element is not registered.\n */\n public updateElementOptions(\n element: ForesightElement,\n options: Partial<ForesightRegisterOptionsWithoutElement>\n ): ForesightElementState {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n throw new Error(\"Cannot update options: element is not registered.\")\n }\n\n if (options.callback) {\n entry.callback = options.callback\n }\n\n if (options.enabled !== undefined) {\n this.setElementEnabled(entry, element, options.enabled !== false)\n }\n\n // Keep the current hitSlop reference when it is omitted or content-equal,\n // so updateElementState sees no change; otherwise remeasure and re-expand.\n // Bounds are updated BEFORE state so state subscribers read fresh geometry.\n let hitSlop = entry.state.hitSlop\n if (options.hitSlop !== undefined) {\n const normalized = normalizeHitSlop(options.hitSlop)\n if (!areRectsEqual(normalized, hitSlop)) {\n hitSlop = normalized\n const originalRect = element.getBoundingClientRect()\n this.updateElementBounds(entry, {\n originalRect,\n expandedRect: getExpandedRect(originalRect, hitSlop),\n })\n }\n }\n\n const prevReactivateAfter = entry.state.reactivateAfter\n const reactivateAfter = options.reactivateAfter ?? prevReactivateAfter\n const next = this.updateElementState(entry, {\n name: options.name || entry.state.name,\n meta: options.meta ?? entry.state.meta,\n reactivateAfter,\n hitSlop,\n })\n\n // Only clear and reschedule the reactivation timeout if reactivateAfter actually changed\n if (reactivateAfter !== prevReactivateAfter) {\n if (entry.reactivateTimeoutId !== undefined) {\n this.clearReactivateTimeout(entry)\n }\n\n if (reactivateAfter !== Infinity && next.isPredicted) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(element)\n }, reactivateAfter)\n }\n }\n\n return next\n }\n\n /**\n * Create a subscribe function for a listener set (state or bounds subscribers).\n * Returns an unsubscribe callback when called.\n */\n private makeSubscribe(subscribers: Set<() => void>) {\n return (listener: () => void): (() => void) => {\n subscribers.add(listener)\n\n return () => {\n subscribers.delete(listener)\n }\n }\n }\n\n /**\n * Replace the immutable state ref for an element and notify subscribers.\n * No-op when every patch value already matches current state - preserves the\n * stable-reference contract relied on by useSyncExternalStore and shallowRef.\n */\n private updateElementState(\n entry: ForesightElementInternal,\n patch: Partial<ForesightElementState>\n ): ForesightElementState {\n const current = entry.state\n let changed = false\n for (const key in patch) {\n if (\n patch[key as keyof ForesightElementState] !== current[key as keyof ForesightElementState]\n ) {\n changed = true\n break\n }\n }\n if (!changed) {\n return current\n }\n\n const next = { ...current, ...patch }\n entry.state = next\n for (const listener of entry.subscribers) {\n try {\n listener()\n } catch (error) {\n console.error(`Error in element subscriber for ${next.name}:`, error)\n }\n }\n\n return next\n }\n\n /**\n * Replace the immutable geometry ref for an element and notify bounds\n * subscribers. No-op when both rects are content-equal. Preserves the\n * stable-reference contract, mirroring {@link updateElementState}.\n *\n * When a single trigger changes both geometry and logical state (position\n * change, hitSlop update), bounds must be updated BEFORE the state patch so\n * state subscribers always read fresh geometry.\n */\n private updateElementBounds(entry: ForesightElementInternal, next: ElementBounds): ElementBounds {\n const current = entry.bounds\n if (\n areRectsEqual(next.originalRect, current.originalRect) &&\n areRectsEqual(next.expandedRect, current.expandedRect)\n ) {\n return current\n }\n\n entry.bounds = next\n for (const listener of entry.boundsSubscribers) {\n try {\n listener()\n } catch (error) {\n console.error(`Error in element bounds subscriber for ${entry.state.name}:`, error)\n }\n }\n\n return next\n }\n\n public unregister(\n element: ForesightElement | NodeListOf<ForesightElement>,\n unregisterReason?: ElementUnregisteredReason\n ): void {\n if (element instanceof NodeList) {\n element.forEach(el => this.unregisterElement(el, unregisterReason))\n } else {\n this.unregisterElement(element, unregisterReason)\n }\n }\n\n private unregisterElement(\n element: ForesightElement,\n unregisterReason?: ElementUnregisteredReason\n ): void {\n const entry = this.elementEntries.get(element)\n if (!entry) {\n return\n }\n\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\n\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n if (entry.state.isParked) {\n this.parkedElementCount--\n }\n\n const finalState = this.updateElementState(entry, {\n isRegistered: false,\n isActive: false,\n isParked: false,\n isPredicted: false,\n isCallbackRunning: false,\n })\n\n this.elementEntries.delete(element)\n entry.subscribers.clear()\n entry.boundsSubscribers.clear()\n\n const wasLastRegisteredElement = this.elementEntries.size === 0 && this.isSetup\n if (wasLastRegisteredElement) {\n this.devLog(\"All elements unregistered, removing global listeners\")\n this.removeGlobalListeners()\n }\n\n this.eventEmitter.emit({\n type: \"elementUnregistered\",\n element: element,\n state: finalState,\n timestamp: Date.now(),\n unregisterReason: unregisterReason ?? \"by user\",\n wasLastRegisteredElement,\n })\n }\n\n public reactivate(element: ForesightElement | NodeListOf<ForesightElement>): void {\n if (element instanceof NodeList) {\n element.forEach(el => this.reactivateElement(el))\n } else {\n this.reactivateElement(element)\n }\n }\n\n private reactivateElement(element: ForesightElement): void {\n const entry = this.elementEntries.get(element)\n if (!entry || !entry.state.isEnabled) {\n return\n }\n\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.clearReactivateTimeout(entry)\n\n if (entry.state.isCallbackRunning || entry.state.isActive) {\n return\n }\n\n this.updateElementState(entry, { isActive: true, isPredicted: false })\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(element)\n }\n\n /**\n * Toggle prediction for a registered element without unregistering it.\n * Disabling deactivates and stops observing; enabling reactivates it.\n */\n private setElementEnabled(\n entry: ForesightElementInternal,\n element: ForesightElement,\n enabled: boolean\n ): void {\n if (entry.state.isEnabled === enabled) {\n return\n }\n\n // A limited connection keeps the element inactive even when enabled (a data\n // saver never starts firing just because enabled flipped); a parked element\n // (detached from the DOM) stays inactive until it reconnects.\n const isActive = enabled && !entry.state.isLimitedConnection && !entry.state.isParked\n\n if (isActive) {\n // Global listeners may have been torn down when the active count last hit\n // zero; re-arm them so prediction actually resumes.\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(element)\n } else {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(element)\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n }\n\n this.updateElementState(entry, {\n isEnabled: enabled,\n isActive,\n isPredicted: false,\n isCallbackRunning: false,\n })\n\n // Disabling the last active element leaves nothing to predict on.\n this.removeGlobalListenersIfIdle()\n }\n\n private clearReactivateTimeout(entry: ForesightElementInternal): void {\n clearTimeout(entry.reactivateTimeoutId)\n entry.reactivateTimeoutId = undefined\n }\n\n private callCallback(entry: ForesightElementInternal, callbackHitType: CallbackHitType): void {\n if (entry.state.isPredicted || !entry.state.isActive) {\n return\n }\n\n this.markElementAsRunning(entry)\n this.executeCallbackAsync(entry, callbackHitType)\n }\n\n private markElementAsRunning(entry: ForesightElementInternal): void {\n this.clearReactivateTimeout(entry)\n\n entry.invokedAt = Date.now()\n\n this.updateElementState(entry, {\n isPredicted: true,\n isCallbackRunning: true,\n hitCount: entry.state.hitCount + 1,\n })\n }\n\n private async executeCallbackAsync(\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType\n ): Promise<void> {\n this.updateHitCounters(callbackHitType)\n\n this.eventEmitter.emit({\n type: \"callbackInvoked\",\n timestamp: Date.now(),\n element: entry.element,\n state: entry.state,\n hitType: callbackHitType,\n })\n\n const start = performance.now()\n let errorMessage: string | null = null\n\n try {\n await entry.callback(entry.state)\n } catch (error) {\n errorMessage = error instanceof Error ? error.message : String(error)\n console.error(`Error in callback for element ${entry.state.name}:`, error)\n }\n\n const status: callbackStatus = errorMessage !== null ? \"error\" : \"success\"\n this.finalizeCallback(entry, callbackHitType, start, status, errorMessage)\n }\n\n private finalizeCallback(\n entry: ForesightElementInternal,\n callbackHitType: CallbackHitType,\n startTime: number,\n status: callbackStatus,\n errorMessage: string | null\n ): void {\n const elapsed = performance.now() - startTime\n\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n this.currentlyActiveHandler?.unobserveElement(entry.element)\n\n entry.completedAt = Date.now()\n const next = this.updateElementState(entry, {\n isCallbackRunning: false,\n isActive: false,\n durationMs: elapsed,\n status,\n error: errorMessage,\n })\n\n if (next.reactivateAfter !== Infinity) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(entry.element)\n }, next.reactivateAfter)\n }\n\n const isLastActiveElement = this.activeElementCount === 0\n this.removeGlobalListenersIfIdle()\n\n this.eventEmitter.emit({\n type: \"callbackCompleted\",\n timestamp: Date.now(),\n element: entry.element,\n state: next,\n hitType: callbackHitType,\n elapsed,\n status,\n errorMessage,\n wasLastActiveElement: isLastActiveElement,\n })\n }\n\n private updateHitCounters(callbackHitType: CallbackHitType): void {\n switch (callbackHitType.kind) {\n case \"mouse\":\n this._globalCallbackHits.mouse[callbackHitType.subType]++\n break\n case \"tab\":\n this._globalCallbackHits.tab[callbackHitType.subType]++\n break\n case \"scroll\":\n this._globalCallbackHits.scroll[callbackHitType.subType]++\n break\n case \"touch\":\n this._globalCallbackHits.touch++\n break\n case \"viewport\":\n this._globalCallbackHits.viewport++\n break\n default:\n callbackHitType satisfies never\n }\n this._globalCallbackHits.total++\n }\n\n private async setDeviceStrategy(strategy: CurrentDeviceStrategy): Promise<void> {\n const previousStrategy = this.currentDeviceStrategy\n\n if (previousStrategy !== strategy) {\n this.devLog(`Switching device strategy from ${previousStrategy} to ${strategy}`)\n }\n\n this.currentlyActiveHandler?.disconnect()\n\n // Lazy load the handler\n this.currentlyActiveHandler =\n strategy === \"mouse\" || strategy === \"pen\"\n ? await this.getOrCreateDesktopHandler()\n : await this.getOrCreateTouchHandler()\n\n this.currentlyActiveHandler.connect()\n }\n\n private handlePointerMove = (e: PointerEvent): void => {\n this.pendingPointerEvent = e\n\n if (e.pointerType !== this.currentDeviceStrategy) {\n this.eventEmitter.emit({\n type: \"deviceStrategyChanged\",\n timestamp: Date.now(),\n newStrategy: e.pointerType as CurrentDeviceStrategy,\n oldStrategy: this.currentDeviceStrategy,\n })\n\n this.currentDeviceStrategy = e.pointerType as CurrentDeviceStrategy\n this.setDeviceStrategy(this.currentDeviceStrategy)\n }\n\n if (this.rafId) {\n return\n }\n\n this.rafId = requestAnimationFrame(() => {\n // Only process mouse movements for desktop handler (mouse/pen)\n if (!this.isUsingDesktopHandler) {\n this.rafId = null\n\n return\n }\n\n if (this.pendingPointerEvent) {\n this.desktopHandler?.processMouseMovement(this.pendingPointerEvent)\n }\n\n this.rafId = null\n })\n }\n\n private initializeGlobalListeners(): void {\n if (this.isSetup || typeof document === \"undefined\") {\n return\n }\n\n this.devLog(\"Initializing global listeners (pointermove, MutationObserver)\")\n this.setDeviceStrategy(this.currentDeviceStrategy)\n\n document.addEventListener(\"pointermove\", this.handlePointerMove)\n\n this.domObserver = new MutationObserver(this.handleDomMutations)\n this.domObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: false,\n })\n\n this.isSetup = true\n }\n\n private removeGlobalListeners(): void {\n if (typeof document === \"undefined\") {\n return\n }\n\n this.isSetup = false\n this.domObserver?.disconnect()\n this.domObserver = null\n\n document.removeEventListener(\"pointermove\", this.handlePointerMove)\n this.currentlyActiveHandler?.disconnect()\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n\n this.pendingPointerEvent = null\n }\n\n private handleDomMutations = (mutationsList: MutationRecord[]): void => {\n if (!mutationsList.length) {\n return\n }\n\n this.desktopHandler?.invalidateTabCache()\n\n let hasChildListChange = false\n for (let i = 0; i < mutationsList.length; i++) {\n const mutation = mutationsList[i]\n if (\n mutation &&\n mutation.type === \"childList\" &&\n (mutation.removedNodes.length > 0 || mutation.addedNodes.length > 0)\n ) {\n hasChildListChange = true\n break\n }\n }\n\n if (!hasChildListChange) {\n return\n }\n\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isParked) {\n if (entry.element.isConnected) {\n this.resumeReconnected(entry)\n }\n } else if (!entry.element.isConnected) {\n this.parkDisconnected(entry)\n }\n }\n }\n\n /**\n * Deactivate an element that was detached from the DOM. It is kept in\n * {@link elementEntries} (still registered) and flagged `isParked` so it can be\n * resumed when it reconnects.\n */\n private parkDisconnected(entry: ForesightElementInternal): void {\n this.clearReactivateTimeout(entry)\n this.currentlyActiveHandler?.unobserveElement(entry.element)\n if (entry.state.isActive) {\n this.activeElementCount--\n }\n\n this.parkedElementCount++\n // Preserve `isPredicted`: an element that already fired its callback must stay\n // \"fired\" so it is not treated as fresh (and reactivated) when it reconnects.\n this.updateElementState(entry, {\n isActive: false,\n isCallbackRunning: false,\n isParked: true,\n })\n }\n\n /**\n * Re-activate a previously parked element once it is back in the DOM. Mirrors\n * the activation rules used everywhere else: disabled / limited connections stay\n * inactive, and an element that already fired its callback stays inactive too\n * (it resumes the same state it had before it detached).\n */\n private resumeReconnected(entry: ForesightElementInternal): void {\n this.parkedElementCount--\n\n const eligible = entry.state.isEnabled && !entry.state.isLimitedConnection\n // Only resume as active if it was active before detaching. A fired element\n // (isPredicted) was already inactive, so it stays inactive on reconnect.\n const isActive = eligible && !entry.state.isPredicted\n if (isActive) {\n if (!this.isSetup) {\n this.initializeGlobalListeners()\n }\n\n this.activeElementCount++\n this.currentlyActiveHandler?.observeElement(entry.element)\n }\n\n this.updateElementState(entry, { isActive, isParked: false })\n\n // If it fired with a finite reactivateAfter, resume the reactivation timer that\n // was cleared when it parked, so the cooldown continues from reconnect.\n if (eligible && entry.state.isPredicted && entry.state.reactivateAfter !== Infinity) {\n entry.reactivateTimeoutId = setTimeout(() => {\n this.reactivate(entry.element)\n }, entry.state.reactivateAfter)\n }\n }\n\n /**\n * Tear down global listeners only when nothing needs them: no active elements\n * to predict on, and no parked elements waiting to reconnect (which need the\n * MutationObserver to detect their return).\n */\n private removeGlobalListenersIfIdle(): void {\n if (this.activeElementCount > 0 || this.parkedElementCount > 0) {\n return\n }\n\n this.removeGlobalListeners()\n }\n\n public alterGlobalSettings(props?: Partial<UpdateForsightManagerSettings>): void {\n const result = applySettingsChanges(this._globalSettings, props)\n\n if (result.positionHistorySizeChanged && this.desktopHandler) {\n this.desktopHandler.trajectoryPositions.positions.resize(\n this._globalSettings.positionHistorySize\n )\n }\n\n if (result.scrollPredictionChanged && this.isUsingDesktopHandler && this.desktopHandler) {\n if (this._globalSettings.enableScrollPrediction) {\n this.desktopHandler.connectScrollPredictor()\n } else {\n this.desktopHandler.disconnectScrollPredictor()\n }\n }\n\n if (result.tabPredictionChanged && this.isUsingDesktopHandler && this.desktopHandler) {\n if (this._globalSettings.enableTabPrediction) {\n this.desktopHandler.connectTabPredictor()\n } else {\n this.desktopHandler.disconnectTabPredictor()\n }\n }\n\n if (result.hitSlopChanged) {\n this.forceUpdateAllElementBounds()\n }\n\n if (result.touchStrategyChanged && !this.isUsingDesktopHandler && this.touchDeviceHandler) {\n this.touchDeviceHandler.setTouchPredictor()\n }\n\n if (result.changedSettings.length > 0) {\n this.eventEmitter.emit({\n type: \"managerSettingsChanged\",\n timestamp: Date.now(),\n managerData: this.getManagerData,\n updatedSettings: result.changedSettings,\n })\n }\n }\n\n private forceUpdateAllElementBounds(): void {\n for (const entry of this.elementEntries.values()) {\n if (entry.state.isIntersectingWithViewport) {\n this.forceUpdateElementBounds(entry)\n }\n }\n }\n\n /**\n * ONLY use this function when you want to change the rect bounds via code, if the rects are changing because of updates in the DOM do not use this function.\n * We need an observer for that\n */\n private forceUpdateElementBounds(entry: ForesightElementInternal): void {\n const newOriginalRect = entry.element.getBoundingClientRect()\n\n this.updateElementBounds(entry, {\n originalRect: newOriginalRect,\n expandedRect: getExpandedRect(newOriginalRect, entry.state.hitSlop),\n })\n }\n\n private devLog(message: string): void {\n if (this._globalSettings.enableManagerLogging) {\n console.log(`%c🛠️ ForesightManager: ${message}`, \"color: #16a34a; font-weight: bold;\")\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=(e,t,n)=>{let r=0,i=1,a=t.x-e.x,o=t.y-e.y,s=(e,t)=>{if(e===0){if(t<0)return!1}else{let n=t/e;if(e<0){if(n>i)return!1;n>r&&(r=n)}else{if(n<r)return!1;n<i&&(i=n)}}return!0};return!s(-a,e.x-n.left)||!s(a,n.right-e.x)||!s(-o,e.y-n.top)||!s(o,n.bottom-e.y)?!1:r<=i};export{e as t};
2
+ //# sourceMappingURL=lineSegmentIntersectsRect-x9nicliA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineSegmentIntersectsRect-x9nicliA.mjs","names":[],"sources":["../src/helpers/lineSegmentIntersectsRect.ts"],"sourcesContent":["import type { Point, Rect } from \"../types/types\"\n\n/**\n * Determines if a line segment intersects with a given rectangle.\n * This function implements the Liang-Barsky line clipping algorithm.\n *\n * @param p1 - The starting {@link Point} of the line segment.\n * @param p2 - The ending {@link Point} of the line segment.\n * @param rect - The {@link Rect} to check for intersection.\n * @returns `true` if the line segment intersects the rectangle, `false` otherwise.\n */\nexport const lineSegmentIntersectsRect = (p1: Point, p2: Point, rect: Rect): boolean => {\n let t0 = 0.0\n let t1 = 1.0\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n\n // Hot path; canonical Liang-Barsky form is fastest in V8.\n // fallow-ignore-next-line complexity\n const clipTest = (p: number, q: number): boolean => {\n if (p === 0) {\n // Line is parallel to this clipping edge\n if (q < 0) {\n return false\n } // Line is outside the clipping edge\n } else {\n const r = q / p\n if (p < 0) {\n // Line proceeds from outside to inside\n if (r > t1) {\n return false\n } // Line segment ends before crossing edge\n\n if (r > t0) {\n t0 = r\n } // Update entry point\n } else {\n // Line proceeds from inside to outside\n if (r < t0) {\n return false\n } // Line segment starts after crossing edge\n\n if (r < t1) {\n t1 = r\n } // Update exit point\n }\n }\n\n return true\n }\n\n // Clip against all four edges of the rectangle\n if (!clipTest(-dx, p1.x - rect.left)) {\n return false\n } // Left edge\n\n if (!clipTest(dx, rect.right - p1.x)) {\n return false\n } // Right edge\n\n if (!clipTest(-dy, p1.y - rect.top)) {\n return false\n } // Top edge\n\n if (!clipTest(dy, rect.bottom - p1.y)) {\n return false\n } // Bottom edge\n\n // If t0 <= t1, the line segment intersects the rectangle (or lies within it)\n return t0 <= t1\n}\n"],"mappings":"AAWA,MAAa,GAA6B,EAAW,EAAW,IAAwB,CACtF,IAAI,EAAK,EACL,EAAK,EACH,EAAK,EAAG,EAAI,EAAG,EACf,EAAK,EAAG,EAAI,EAAG,EAIf,GAAY,EAAW,IAAuB,CAClD,GAAI,IAAM,MAEJ,EAAI,EACN,MAAO,OAEJ,CACL,IAAM,EAAI,EAAI,EACd,GAAI,EAAI,EAAG,CAET,GAAI,EAAI,EACN,MAAO,GAGL,EAAI,IACN,EAAK,OAEF,CAEL,GAAI,EAAI,EACN,MAAO,GAGL,EAAI,IACN,EAAK,IAKX,MAAO,IAqBT,MAjBI,CAAC,EAAS,CAAC,EAAI,EAAG,EAAI,EAAK,KAAK,EAIhC,CAAC,EAAS,EAAI,EAAK,MAAQ,EAAG,EAAE,EAIhC,CAAC,EAAS,CAAC,EAAI,EAAG,EAAI,EAAK,IAAI,EAI/B,CAAC,EAAS,EAAI,EAAK,OAAS,EAAG,EAAE,CAC5B,GAIF,GAAM"}
@@ -0,0 +1,2 @@
1
+ const e=2e3,t=1/0,n=(e,t,n,r)=>(e<t?console.warn(`ForesightJS: "${r}" value ${e} is below minimum bound ${t}, clamping to ${t}`):e>n&&console.warn(`ForesightJS: "${r}" value ${e} is above maximum bound ${n}, clamping to ${n}`),Math.min(Math.max(e,t),n)),r=t=>{if(typeof t==`number`){let r=n(t,0,e,`hitslop`);return{top:r,left:r,right:r,bottom:r}}return{top:n(t.top,0,e,`hitslop - top`),left:n(t.left,0,e,`hitslop - left`),right:n(t.right,0,e,`hitslop - right`),bottom:n(t.bottom,0,e,`hitslop - bottom`)}},i=(e,t)=>({left:e.left-t.left,right:e.right+t.right,top:e.top-t.top,bottom:e.bottom+t.bottom}),a=(e,t)=>!e||!t?e===t:e.left===t.left&&e.right===t.right&&e.top===t.top&&e.bottom===t.bottom,o=(e,t)=>e.x>=t.left&&e.x<=t.right&&e.y>=t.top&&e.y<=t.bottom;export{n as a,r as i,i as n,t as o,o as r,a as t};
2
+ //# sourceMappingURL=rectAndHitSlop-T7Z3PZlb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rectAndHitSlop-T7Z3PZlb.mjs","names":[],"sources":["../src/constants.ts","../src/helpers/clampNumber.ts","../src/helpers/rectAndHitSlop.ts"],"sourcesContent":["//IMPORTANT: when altering these values change the type jsDocs/actual docs for BaseForesightManagerProps\nexport const MIN_TRAJECTORY_PREDICTION_TIME: number = 10\nexport const MAX_TRAJECTORY_PREDICTION_TIME: number = 200\nexport const DEFAULT_TRAJECTORY_PREDICTION_TIME: number = 120\n\nexport const MIN_POSITION_HISTORY_SIZE: number = 2\nexport const MAX_POSITION_HISTORY_SIZE: number = 30\nexport const DEFAULT_POSITION_HISTORY_SIZE: number = 8\n\nexport const MIN_TAB_OFFSET: number = 0\nexport const MAX_TAB_OFFSET: number = 20\nexport const DEFAULT_TAB_OFFSET: number = 2\n\nexport const MIN_HITSLOP: number = 0\nexport const MAX_HITSLOP: number = 2000\nexport const DEFAULT_HITSLOP: number = 0\n\nexport const DEFAULT_SCROLL_MARGIN: number = 150\nexport const MIN_SCROLL_MARGIN: number = 30\nexport const MAX_SCROLL_MARGIN: number = 300\n\nexport const DEFAULT_ENABLE_TAB_PREDICTION: boolean = true\nexport const DEFAULT_ENABLE_MOUSE_PREDICTION: boolean = true\nexport const DEFAULT_ENABLE_SCROLL_PREDICTION: boolean = true\n\nexport const DEFAULT_STALE_TIME: number = Infinity\n","export const clampNumber = (\n number: number,\n lowerBound: number,\n upperBound: number,\n settingName: string\n) => {\n if (number < lowerBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is below minimum bound ${lowerBound}, clamping to ${lowerBound}`\n )\n } else if (number > upperBound) {\n console.warn(\n `ForesightJS: \"${settingName}\" value ${number} is above maximum bound ${upperBound}, clamping to ${upperBound}`\n )\n }\n\n return Math.min(Math.max(number, lowerBound), upperBound)\n}\n","import type { HitSlop, Point, Rect } from \"../types/types\"\nimport { MAX_HITSLOP, MIN_HITSLOP } from \"../constants\"\nimport { clampNumber } from \"./clampNumber\"\n\n/**\n * Normalizes a `hitSlop` value into a {@link Rect} object.\n * If `hitSlop` is a number, it's applied uniformly to all sides (top, left, right, bottom).\n * If `hitSlop` is already a `Rect` object, it's returned as is.\n *\n * @param hitSlop - A number for uniform slop, or a {@link Rect} object for specific slop per side.\n * @returns A {@link Rect} object with `top`, `left`, `right`, and `bottom` properties.\n */\nexport const normalizeHitSlop = (hitSlop: HitSlop): Rect => {\n if (typeof hitSlop === \"number\") {\n const clampedValue = clampNumber(hitSlop, MIN_HITSLOP, MAX_HITSLOP, \"hitslop\")\n\n return {\n top: clampedValue,\n left: clampedValue,\n right: clampedValue,\n bottom: clampedValue,\n }\n }\n\n return {\n top: clampNumber(hitSlop.top, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - top\"),\n left: clampNumber(hitSlop.left, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - left\"),\n right: clampNumber(hitSlop.right, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - right\"),\n bottom: clampNumber(hitSlop.bottom, MIN_HITSLOP, MAX_HITSLOP, \"hitslop - bottom\"),\n }\n}\n\n/**\n * Calculates an expanded rectangle by applying a `hitSlop` to a base rectangle.\n * The `hitSlop` values define how much to extend each side of the `baseRect` outwards.\n *\n * @param baseRect - The original {@link Rect} or `DOMRect` to expand.\n * @param hitSlop - A {@link Rect} object defining how much to expand each side\n * (e.g., `hitSlop.left` expands the left boundary further to the left).\n * @returns A new {@link Rect} object representing the expanded area.\n */\nexport const getExpandedRect = (baseRect: Rect | DOMRect, hitSlop: Rect): Rect => {\n return {\n left: baseRect.left - hitSlop.left,\n right: baseRect.right + hitSlop.right,\n top: baseRect.top - hitSlop.top,\n bottom: baseRect.bottom + hitSlop.bottom,\n }\n}\n\n/**\n * Checks if two rectangle objects are equal by comparing their respective\n * `top`, `left`, `right`, and `bottom` properties.\n * Handles cases where one or both rects might be null or undefined.\n *\n * @param rect1 - The first {@link Rect} object to compare.\n * @param rect2 - The second {@link Rect} object to compare.\n * @returns `true` if the rectangles have identical dimensions or if both are null/undefined,\n * `false` otherwise.\n */\nexport const areRectsEqual = (rect1: Rect, rect2: Rect): boolean => {\n if (!rect1 || !rect2) {\n return rect1 === rect2\n }\n\n return (\n rect1.left === rect2.left &&\n rect1.right === rect2.right &&\n rect1.top === rect2.top &&\n rect1.bottom === rect2.bottom\n )\n}\n\nexport const isPointInRectangle = (point: Point, rect: Rect): boolean => {\n return (\n point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom\n )\n}\n"],"mappings":"AAcA,MAAa,EAAsB,IAWtB,EAA6B,ICzB7B,GACX,EACA,EACA,EACA,KAEI,EAAS,EACX,QAAQ,KACN,iBAAiB,EAAY,UAAU,EAAO,0BAA0B,EAAW,gBAAgB,IACpG,CACQ,EAAS,GAClB,QAAQ,KACN,iBAAiB,EAAY,UAAU,EAAO,0BAA0B,EAAW,gBAAgB,IACpG,CAGI,KAAK,IAAI,KAAK,IAAI,EAAQ,EAAW,CAAE,EAAW,ECJ9C,EAAoB,GAA2B,CAC1D,GAAI,OAAO,GAAY,SAAU,CAC/B,IAAM,EAAe,EAAY,EAAA,EAAsB,EAAa,UAAU,CAE9E,MAAO,CACL,IAAK,EACL,KAAM,EACN,MAAO,EACP,OAAQ,EACT,CAGH,MAAO,CACL,IAAK,EAAY,EAAQ,IAAA,EAAkB,EAAa,gBAAgB,CACxE,KAAM,EAAY,EAAQ,KAAA,EAAmB,EAAa,iBAAiB,CAC3E,MAAO,EAAY,EAAQ,MAAA,EAAoB,EAAa,kBAAkB,CAC9E,OAAQ,EAAY,EAAQ,OAAA,EAAqB,EAAa,mBAAmB,CAClF,EAYU,GAAmB,EAA0B,KACjD,CACL,KAAM,EAAS,KAAO,EAAQ,KAC9B,MAAO,EAAS,MAAQ,EAAQ,MAChC,IAAK,EAAS,IAAM,EAAQ,IAC5B,OAAQ,EAAS,OAAS,EAAQ,OACnC,EAaU,GAAiB,EAAa,IACrC,CAAC,GAAS,CAAC,EACN,IAAU,EAIjB,EAAM,OAAS,EAAM,MACrB,EAAM,QAAU,EAAM,OACtB,EAAM,MAAQ,EAAM,KACpB,EAAM,SAAW,EAAM,OAId,GAAsB,EAAc,IAE7C,EAAM,GAAK,EAAK,MAAQ,EAAM,GAAK,EAAK,OAAS,EAAM,GAAK,EAAK,KAAO,EAAM,GAAK,EAAK"}
package/package.json CHANGED
@@ -1,33 +1,23 @@
1
1
  {
2
2
  "name": "js.foresight",
3
- "version": "3.5.0",
3
+ "version": "4.1.0",
4
4
  "description": "Predicts where users will click based on mouse movement, keyboard navigation, and scroll behavior. Includes touch device support. Triggers callbacks before interactions happen to enable prefetching and faster UI responses. Works with any framework.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
- "scripts": {
8
- "build": "tsup --sourcemap",
9
- "build:prod": "tsup",
10
- "dev": "tsup --sourcemap --watch",
11
- "test": "vitest",
12
- "test:watch": "vitest --watch",
13
- "test:ui": "vitest --ui",
14
- "test:coverage": "vitest --coverage",
15
- "test:run": "vitest run",
16
- "prepublishOnly": "pnpm test:run && pnpm build:prod"
17
- },
18
- "main": "./dist/index.js",
19
- "module": "./dist/index.js",
20
- "types": "./dist/index.d.ts",
7
+ "main": "./dist/index.mjs",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.mts",
21
10
  "exports": {
22
11
  ".": {
23
- "types": "./dist/index.d.ts",
24
- "default": "./dist/index.js"
12
+ "types": "./dist/index.d.mts",
13
+ "default": "./dist/index.mjs"
25
14
  }
26
15
  },
27
16
  "homepage": "https://foresightjs.com/",
28
17
  "repository": {
29
18
  "type": "git",
30
- "url": "git+https://github.com/spaansba/ForesightJS"
19
+ "url": "git+https://github.com/spaansba/ForesightJS",
20
+ "directory": "packages/js.foresight"
31
21
  },
32
22
  "files": [
33
23
  "dist",
@@ -58,18 +48,27 @@
58
48
  "devDependencies": {
59
49
  "@testing-library/dom": "^10.4.1",
60
50
  "@testing-library/jest-dom": "^6.9.1",
61
- "@types/node": "^24.10.1",
62
- "@vitest/coverage-v8": "4.0.16",
63
- "@vitest/ui": "^4.0.9",
64
- "happy-dom": "^20.0.10",
65
- "jsdom": "^27.4.0",
66
- "tslib": "^2.8.1",
67
- "tsup": "^8.5.1",
68
- "typescript": "^5.9.3",
69
- "vitest": "^4.0.9"
51
+ "@types/node": "^25.5.2",
52
+ "@vitest/coverage-v8": "4.1.2",
53
+ "@vitest/ui": "^4.1.2",
54
+ "happy-dom": "^20.8.9",
55
+ "jsdom": "^29.0.1",
56
+ "tsdown": "^0.21.7",
57
+ "typescript": "^6.0.2",
58
+ "vitest": "^4.1.2"
70
59
  },
71
60
  "dependencies": {
72
61
  "position-observer": "^1.0.3",
73
62
  "tabbable": "^6.4.0"
63
+ },
64
+ "scripts": {
65
+ "build": "tsdown --sourcemap",
66
+ "build:prod": "tsdown",
67
+ "dev": "tsdown --sourcemap --watch",
68
+ "test": "vitest",
69
+ "test:watch": "vitest --watch",
70
+ "test:ui": "vitest --ui",
71
+ "test:coverage": "vitest --coverage",
72
+ "test:run": "vitest run"
74
73
  }
75
- }
74
+ }
@@ -1 +0,0 @@
1
- import{c as f,d as b,e as d}from"./chunk-PAYO6NXN.js";import{a as m}from"./chunk-44N4MCQB.js";import{a as l}from"./chunk-AODZNE3S.js";function y(n,i,t,e){let r=performance.now();i.add({point:{x:n.x,y:n.y},time:r});let{x:o,y:s}=n;if(i.length<2){e.x=o,e.y=s;return}let[c,a]=i.getFirstLast();if(!c||!a){e.x=o,e.y=s;return}let p=(a.time-c.time)*.001;if(p===0){e.x=o,e.y=s;return}let v=a.point.x-c.point.x,T=a.point.y-c.point.y,x=v/p,M=T/p,P=t*.001;e.x=o+x*P,e.y=s+M*P}var h=class extends l{constructor(t){super(t.dependencies);this.moduleName="MousePredictor";this.trajectoryPositions=t.trajectoryPositions}updatePointerState(t){let e=this.trajectoryPositions.currentPoint;e.x=t.clientX,e.y=t.clientY,this.settings.enableMousePrediction?y(e,this.trajectoryPositions.positions,this.settings.trajectoryPredictionTime,this.trajectoryPositions.predictedPoint):(this.trajectoryPositions.predictedPoint.x=e.x,this.trajectoryPositions.predictedPoint.y=e.y)}processMouseMovement(t){this.updatePointerState(t);let e=this.settings.enableMousePrediction,r=this.trajectoryPositions.currentPoint;for(let o of this.elements.values()){if(!o.isIntersectingWithViewport||!o.callbackInfo.isCallbackActive||o.callbackInfo.isRunningCallback)continue;let s=o.elementBounds.expandedRect;if(e)m(r,this.trajectoryPositions.predictedPoint,s)&&this.callCallback(o,{kind:"mouse",subType:"trajectory"});else if(d(r,s)){this.callCallback(o,{kind:"mouse",subType:"hover"});return}}this.hasListeners("mouseTrajectoryUpdate")&&this.emit({type:"mouseTrajectoryUpdate",predictionEnabled:e,trajectoryPositions:this.trajectoryPositions})}onDisconnect(){}onConnect(){}};import{PositionObserver as j}from"position-observer";var u=class{constructor(i){this.head=0;this.count=0;if(i<=0)throw new Error("CircularBuffer capacity must be greater than 0");this.capacity=i,this.buffer=new Array(i)}add(i){this.buffer[this.head]=i,this.head=(this.head+1)%this.capacity,this.count<this.capacity&&this.count++}getFirst(){if(this.count!==0)return this.count<this.capacity?this.buffer[0]:this.buffer[this.head]}getLast(){if(this.count!==0){if(this.count<this.capacity)return this.buffer[this.count-1];{let i=(this.head-1+this.capacity)%this.capacity;return this.buffer[i]}}}getFirstLast(){if(this.count===0)return[void 0,void 0];if(this.count===1){let e=this.count<this.capacity?this.buffer[0]:this.buffer[this.head];return[e,e]}let i=this.getFirst(),t=this.getLast();return[i,t]}resize(i){if(i<=0)throw new Error("CircularBuffer capacity must be greater than 0");if(i===this.capacity)return;let t=this.getAllItems();if(this.capacity=i,this.buffer=new Array(i),this.head=0,this.count=0,t.length>i){let e=t.slice(-i);for(let r of e)this.add(r)}else for(let e of t)this.add(e)}getAllItems(){if(this.count===0)return[];let i=new Array(this.count);if(this.count<this.capacity)for(let t=0;t<this.count;t++)i[t]=this.buffer[t];else{let t=this.head;for(let e=0;e<this.capacity;e++){let r=(t+e)%this.capacity;i[e]=this.buffer[r]}}return i}clear(){this.head=0,this.count=0}get length(){return this.count}get size(){return this.capacity}get isFull(){return this.count===this.capacity}get isEmpty(){return this.count===0}};var g=class extends l{constructor(t){super(t);this.moduleName="DesktopHandler";this.tabPredictor=null;this.scrollPredictor=null;this.positionObserver=null;this.trajectoryPositions={positions:new u(8),currentPoint:{x:0,y:0},predictedPoint:{x:0,y:0}};this.handlePositionChange=t=>{let e=this.settings.enableScrollPrediction;for(let r of t){let o=this.elements.get(r.target);o&&(e?this.scrollPredictor?.handleScrollPrefetch(o,r.boundingClientRect):this.checkForMouseHover(o),this.handlePositionChangeDataUpdates(o,r))}e&&this.scrollPredictor?.resetScrollProps()};this.checkForMouseHover=t=>{d(this.trajectoryPositions.currentPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"mouse",subType:"hover"})};this.handlePositionChangeDataUpdates=(t,e)=>{let r=[],o=e.isIntersecting;t.isIntersectingWithViewport!==o&&(r.push("visibility"),t.isIntersectingWithViewport=o),o&&!b(e.boundingClientRect,t.elementBounds.originalRect)&&(r.push("bounds"),t.elementBounds={hitSlop:t.elementBounds.hitSlop,originalRect:e.boundingClientRect,expandedRect:f(e.boundingClientRect,t.elementBounds.hitSlop)}),r.length&&this.hasListeners("elementDataUpdated")&&this.emit({type:"elementDataUpdated",elementData:t,updatedProps:r})};this.processMouseMovement=t=>this.mousePredictor.processMouseMovement(t);this.invalidateTabCache=()=>this.tabPredictor?.invalidateCache();this.observeElement=t=>this.positionObserver?.observe(t);this.unobserveElement=t=>this.positionObserver?.unobserve(t);this.connectTabPredictor=async()=>{if(!this.tabPredictor){let{TabPredictor:t}=await import("./TabPredictor-HA2SV3CY.js");this.tabPredictor=new t(this.storedDependencies),this.devLog("TabPredictor lazy loaded")}this.tabPredictor.connect()};this.connectScrollPredictor=async()=>{if(!this.scrollPredictor){let{ScrollPredictor:t}=await import("./ScrollPredictor-Y7NELMBI.js");this.scrollPredictor=new t({dependencies:this.storedDependencies,trajectoryPositions:this.trajectoryPositions}),this.devLog("ScrollPredictor lazy loaded")}this.scrollPredictor.connect()};this.connectMousePredictor=()=>this.mousePredictor.connect();this.disconnectTabPredictor=()=>this.tabPredictor?.disconnect();this.disconnectScrollPredictor=()=>this.scrollPredictor?.disconnect();this.disconnectMousePredictor=()=>this.mousePredictor.disconnect();this.storedDependencies=t,this.mousePredictor=new h({dependencies:t,trajectoryPositions:this.trajectoryPositions})}onConnect(){this.settings.enableTabPrediction&&this.connectTabPredictor(),this.settings.enableScrollPrediction&&this.connectScrollPredictor(),this.connectMousePredictor(),this.positionObserver=new j(this.handlePositionChange);let t=["mouse"];this.settings.enableTabPrediction&&t.push("tab (loading...)"),this.settings.enableScrollPrediction&&t.push("scroll (loading...)"),this.devLog(`Connected predictors: [${t.join(", ")}] and PositionObserver`);for(let e of this.elements.keys())this.positionObserver.observe(e)}onDisconnect(){this.disconnectMousePredictor(),this.disconnectTabPredictor(),this.disconnectScrollPredictor(),this.positionObserver?.disconnect(),this.positionObserver=null}get loadedPredictors(){return{mouse:this.mousePredictor!==null,tab:this.tabPredictor!==null,scroll:this.scrollPredictor!==null}}};export{g as DesktopHandler};
@@ -1 +0,0 @@
1
- import{a as s}from"./chunk-44N4MCQB.js";import{a as l}from"./chunk-AODZNE3S.js";function n(r,e){let o=e.top-r.top,c=e.left-r.left;return o<-1?"down":o>1?"up":c<-1?"right":c>1?"left":"none"}function p(r,e,t){let{x:o,y:c}=r,i={x:o,y:c};switch(e){case"down":i.y+=t;break;case"up":i.y-=t;break;case"left":i.x-=t;break;case"right":i.x+=t;break;case"none":break;default:}return i}var d=class extends l{constructor(t){super(t.dependencies);this.moduleName="ScrollPredictor";this.predictedScrollPoint=null;this.scrollDirection=null;this.onDisconnect=()=>this.resetScrollProps();this.trajectoryPositions=t.trajectoryPositions}resetScrollProps(){this.scrollDirection=null,this.predictedScrollPoint=null}handleScrollPrefetch(t,o){!t.isIntersectingWithViewport||t.callbackInfo.isRunningCallback||!t.callbackInfo.isCallbackActive||(this.scrollDirection=this.scrollDirection??n(t.elementBounds.originalRect,o),this.scrollDirection!=="none"&&(this.predictedScrollPoint=this.predictedScrollPoint??p(this.trajectoryPositions.currentPoint,this.scrollDirection,this.settings.scrollMargin),s(this.trajectoryPositions.currentPoint,this.predictedScrollPoint,t.elementBounds.expandedRect)&&this.callCallback(t,{kind:"scroll",subType:this.scrollDirection}),this.hasListeners("scrollTrajectoryUpdate")&&this.emit({type:"scrollTrajectoryUpdate",currentPoint:this.trajectoryPositions.currentPoint,predictedPoint:this.predictedScrollPoint,scrollDirection:this.scrollDirection})))}onConnect(){}};export{d as ScrollPredictor};
@@ -1 +0,0 @@
1
- import{a as b}from"./chunk-AODZNE3S.js";import{tabbable as f}from"tabbable";function u(r,l,e,o){if(l!==null&&l>-1){let t=r?l-1:l+1;if(t>=0&&t<e.length&&e[t]===o)return t}return e.findIndex(t=>t===o)}var h=class extends b{constructor(e){super(e);this.moduleName="TabPredictor";this.lastKeyDown=null;this.tabbableElementsCache=[];this.lastFocusedIndex=null;this.handleKeyDown=e=>{e.key==="Tab"&&(this.lastKeyDown=e)};this.handleFocusIn=e=>{if(!this.lastKeyDown)return;let o=e.target;if(!(o instanceof HTMLElement))return;(!this.tabbableElementsCache.length||this.lastFocusedIndex===-1)&&(this.devLog("Caching tabbable elements"),this.tabbableElementsCache=f(document.documentElement));let t=this.lastKeyDown.shiftKey,i=u(t,this.lastFocusedIndex,this.tabbableElementsCache,o);this.lastFocusedIndex=i,this.lastKeyDown=null;let c=[],m=this.settings.tabOffset,d=this.elements;for(let n=0;n<=m;n++){let s=t?i-n:i+n,a=this.tabbableElementsCache[s];a&&a instanceof Element&&d.has(a)&&c.push(a)}for(let n of c){let s=d.get(n);s&&!s.callbackInfo.isRunningCallback&&s.callbackInfo.isCallbackActive&&this.callCallback(s,{kind:"tab",subType:t?"reverse":"forwards"})}}}invalidateCache(){this.tabbableElementsCache.length&&this.devLog("Invalidating tabbable elements cache"),this.tabbableElementsCache=[],this.lastFocusedIndex=null}onConnect(){typeof document>"u"||(this.createAbortController(),document.addEventListener("keydown",this.handleKeyDown,{signal:this.abortController?.signal,passive:!0}),document.addEventListener("focusin",this.handleFocusIn,{signal:this.abortController?.signal,passive:!0}))}onDisconnect(){this.tabbableElementsCache=[],this.lastFocusedIndex=null,this.lastKeyDown=null}};export{h as TabPredictor};
@@ -1 +0,0 @@
1
- import{a as e}from"./chunk-AODZNE3S.js";var r=class extends e{constructor(t){super(t);this.moduleName="TouchDeviceHandler";this.viewportPredictor=null;this.touchStartPredictor=null;this.predictor=null;this.onDisconnect=()=>{this.devLog("Disconnecting touch predictor"),this.predictor?.disconnect()};this.onConnect=()=>this.setTouchPredictor();this.observeElement=t=>this.predictor?.observeElement(t);this.unobserveElement=t=>this.predictor?.unobserveElement(t);this.storedDependencies=t}async getOrCreateViewportPredictor(){if(!this.viewportPredictor){let{ViewportPredictor:t}=await import("./ViewportPredictor-H3GLDETY.js");this.viewportPredictor=new t(this.storedDependencies),this.devLog("ViewportPredictor lazy loaded")}return this.viewportPredictor}async getOrCreateTouchStartPredictor(){if(!this.touchStartPredictor){let{TouchStartPredictor:t}=await import("./TouchStartPredictor-ZH3KJG2C.js");this.touchStartPredictor=new t(this.storedDependencies),this.devLog("TouchStartPredictor lazy loaded")}return this.touchStartPredictor}async setTouchPredictor(){switch(this.predictor?.disconnect(),this.settings.touchDeviceStrategy){case"viewport":this.predictor=await this.getOrCreateViewportPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (ViewportPredictor)`);break;case"onTouchStart":this.predictor=await this.getOrCreateTouchStartPredictor(),this.devLog(`Connected touch strategy: ${this.settings.touchDeviceStrategy} (TouchStartPredictor)`);break;case"none":this.predictor=null,this.devLog('Touch strategy set to "none" - no predictor connected');return;default:this.settings.touchDeviceStrategy}this.predictor?.connect();for(let t of this.elements.keys())this.predictor?.observeElement(t)}get loadedPredictors(){return{viewport:this.viewportPredictor!==null,touchStart:this.touchStartPredictor!==null}}};export{r as TouchDeviceHandler};
@@ -1 +0,0 @@
1
- import{a as n}from"./chunk-AODZNE3S.js";var r=class extends n{constructor(e){super(e);this.moduleName="TouchStartPredictor";this.onConnect=()=>this.createAbortController();this.onDisconnect=()=>{};this.handleTouchStart=e=>{let t=e.currentTarget,o=this.elements.get(t);o&&(this.callCallback(o,{kind:"touch"}),this.unobserveElement(t))}}observeElement(e){e instanceof HTMLElement&&e.addEventListener("pointerdown",this.handleTouchStart,{signal:this.abortController?.signal})}unobserveElement(e){e instanceof HTMLElement&&e.removeEventListener("pointerdown",this.handleTouchStart)}};export{r as TouchStartPredictor};
@@ -1 +0,0 @@
1
- import{a as n}from"./chunk-AODZNE3S.js";var s=class extends n{constructor(e){super(e);this.moduleName="ViewportPredictor";this.intersectionObserver=null;this.onConnect=()=>this.intersectionObserver=new IntersectionObserver(this.handleViewportEnter);this.observeElement=e=>this.intersectionObserver?.observe(e);this.unobserveElement=e=>this.intersectionObserver?.unobserve(e);this.handleViewportEnter=e=>{for(let t of e){if(!t.isIntersecting)continue;let r=this.elements.get(t.target);r&&(this.callCallback(r,{kind:"viewport"}),this.unobserveElement(t.target))}}}onDisconnect(){this.intersectionObserver?.disconnect(),this.intersectionObserver=null}};export{s as ViewportPredictor};
@@ -1 +0,0 @@
1
- function y(e,s,n){let r=0,f=1,l=s.x-e.x,u=s.y-e.y,i=(o,a)=>{if(o===0){if(a<0)return!1}else{let t=a/o;if(o<0){if(t>f)return!1;t>r&&(r=t)}else{if(t<r)return!1;t<f&&(f=t)}}return!0};return!i(-l,e.x-n.left)||!i(l,n.right-e.x)||!i(-u,e.y-n.top)||!i(u,n.bottom-e.y)?!1:r<=f}export{y as a};
@@ -1 +0,0 @@
1
- var e=class{constructor(t){this._isConnected=!1;this._cachedLogStyle=null;this.elements=t.elements,this.callCallback=t.callCallback,this.emit=t.emit,this.hasListeners=t.hasListeners,this.settings=t.settings}get isConnected(){return this._isConnected}disconnect(){this.isConnected&&(this.devLog(`Disconnecting ${this.moduleName}...`),this.abortController?.abort(`${this.moduleName} module disconnected`),this.onDisconnect(),this._isConnected=!1)}connect(){this.devLog(`Connecting ${this.moduleName}...`),this.onConnect(),this._isConnected=!0}devLog(t){if(this.settings.enableManagerLogging){if(this._cachedLogStyle===null){let o=this.moduleName.includes("Predictor")?"#ea580c":"#2563eb";this._cachedLogStyle=`color: ${o}; font-weight: bold;`}console.log(`%c${this.moduleName}: ${t}`,this._cachedLogStyle)}}createAbortController(){this.abortController&&!this.abortController.signal.aborted||(this.abortController=new AbortController,this.devLog(`Created new AbortController for ${this.moduleName}`))}};export{e as a};
@@ -1 +0,0 @@
1
- function i(t,o,e,r){return t<o?console.warn(`ForesightJS: "${r}" value ${t} is below minimum bound ${o}, clamping to ${o}`):t>e&&console.warn(`ForesightJS: "${r}" value ${t} is above maximum bound ${e}, clamping to ${e}`),Math.min(Math.max(t,o),e)}function p(t){if(typeof t=="number"){let o=i(t,0,2e3,"hitslop");return{top:o,left:o,right:o,bottom:o}}return{top:i(t.top,0,2e3,"hitslop - top"),left:i(t.left,0,2e3,"hitslop - left"),right:i(t.right,0,2e3,"hitslop - right"),bottom:i(t.bottom,0,2e3,"hitslop - bottom")}}function a(t,o){return{left:t.left-o.left,right:t.right+o.right,top:t.top-o.top,bottom:t.bottom+o.bottom}}function g(t,o){return!t||!o?t===o:t.left===o.left&&t.right===o.right&&t.top===o.top&&t.bottom===o.bottom}function b(t,o){return t.x>=o.left&&t.x<=o.right&&t.y>=o.top&&t.y<=o.bottom}export{i as a,p as b,a as c,g as d,b as e};