js.foresight
Version:
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.
639 lines • 31.4 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { tabbable } from "tabbable";
import { evaluateRegistrationConditions } from "../helpers/shouldRegister";
import { DEFAULT_ENABLE_MOUSE_PREDICTION, DEFAULT_ENABLE_SCROLL_PREDICTION, DEFAULT_ENABLE_TAB_PREDICTION, DEFAULT_HITSLOP, DEFAULT_POSITION_HISTORY_SIZE, DEFAULT_SCROLL_MARGIN, DEFAULT_TAB_OFFSET, DEFAULT_TRAJECTORY_PREDICTION_TIME, MAX_POSITION_HISTORY_SIZE, MAX_SCROLL_MARGIN, MAX_TAB_OFFSET, MAX_TRAJECTORY_PREDICTION_TIME, MIN_POSITION_HISTORY_SIZE, MIN_SCROLL_MARGIN, MIN_TAB_OFFSET, MIN_TRAJECTORY_PREDICTION_TIME, } from "./constants";
import { clampNumber } from "./helpers/clampNumber";
import { lineSegmentIntersectsRect } from "./helpers/lineSigmentIntersectsRect";
import { predictNextMousePosition } from "./helpers/predictNextMousePosition";
import { areRectsEqual, getExpandedRect, isPointInRectangle, normalizeHitSlop, } from "./helpers/rectAndHitSlop";
import { shouldUpdateSetting } from "./helpers/shouldUpdateSetting";
import { getFocusedElementIndex } from "./helpers/getFocusedElementIndex";
import { getScrollDirection } from "./helpers/getScrollDirection";
import { predictNextScrollPosition } from "./helpers/predictNextScrollPosition";
import { PositionObserver } from "position-observer";
/**
* Manages the prediction of user intent based on mouse trajectory and element interactions.
*
* ForesightManager is a singleton class responsible for:
* - Registering HTML elements to monitor.
* - Tracking mouse movements and predicting future cursor positions.
* - Detecting when a predicted trajectory intersects with a registered element's bounds.
* - Invoking callbacks associated with elements upon predicted or actual interaction.
* - Optionally unregistering elements after their callback is triggered.
* - Handling global settings for prediction behavior (e.g., history size, prediction time).
* - Automatically updating element bounds on resize using {@link ResizeObserver}.
* - Automatically unregistering elements removed from the DOM using {@link MutationObserver}.
* - Detecting broader layout shifts via {@link MutationObserver} to update element positions.
*
* It should be initialized once using {@link ForesightManager.initialize} and then
* accessed via the static getter {@link ForesightManager.instance}.
*/
var ForesightManager = /** @class */ (function () {
// Never put something in the constructor, use initialize instead
function ForesightManager() {
var _this = this;
this.elements = new Map();
this.isSetup = false;
this._globalCallbackHits = {
mouse: {
hover: 0,
trajectory: 0,
},
tab: {
forwards: 0,
reverse: 0,
},
scroll: {
down: 0,
left: 0,
right: 0,
up: 0,
},
total: 0,
};
this._globalSettings = {
debug: false,
enableMousePrediction: DEFAULT_ENABLE_MOUSE_PREDICTION,
enableScrollPrediction: DEFAULT_ENABLE_SCROLL_PREDICTION,
positionHistorySize: DEFAULT_POSITION_HISTORY_SIZE,
trajectoryPredictionTime: DEFAULT_TRAJECTORY_PREDICTION_TIME,
scrollMargin: DEFAULT_SCROLL_MARGIN,
defaultHitSlop: {
top: DEFAULT_HITSLOP,
left: DEFAULT_HITSLOP,
right: DEFAULT_HITSLOP,
bottom: DEFAULT_HITSLOP,
},
enableTabPrediction: DEFAULT_ENABLE_TAB_PREDICTION,
tabOffset: DEFAULT_TAB_OFFSET,
onAnyCallbackFired: function (_elementData, _managerData) { },
};
this.trajectoryPositions = {
positions: [],
currentPoint: { x: 0, y: 0 },
predictedPoint: { x: 0, y: 0 },
};
this.tabbableElementsCache = [];
this.lastFocusedIndex = null;
this.predictedScrollPoint = null;
this.scrollDirection = null;
this.domObserver = null;
this.positionObserver = null;
// Track the last keydown event to determine if focus change was due to Tab
this.lastKeyDown = null;
// AbortController for managing global event listeners
this.globalListenersController = null;
this.eventListeners = new Map();
this.handleMouseMove = function (e) {
_this.updatePointerState(e);
_this.elements.forEach(function (currentData) {
if (!currentData.isIntersectingWithViewport) {
return;
}
_this.handleCallbackInteraction(currentData);
});
_this.emit({
type: "mouseTrajectoryUpdate",
predictionEnabled: _this._globalSettings.enableMousePrediction,
timestamp: Date.now(),
trajectoryPositions: _this.trajectoryPositions,
});
};
/**
* Detects when registered elements are removed from the DOM and automatically unregisters them to prevent stale references.
*
* @param mutationsList - Array of MutationRecord objects describing the DOM changes
*
*/
this.handleDomMutations = function (mutationsList) {
// Invalidate tabbale elements cache
if (mutationsList.length) {
_this.tabbableElementsCache = [];
_this.lastFocusedIndex = null;
}
for (var _i = 0, mutationsList_1 = mutationsList; _i < mutationsList_1.length; _i++) {
var mutation = mutationsList_1[_i];
if (mutation.type === "childList" && mutation.removedNodes.length > 0) {
for (var _a = 0, _b = Array.from(_this.elements.keys()); _a < _b.length; _a++) {
var element = _b[_a];
if (!element.isConnected) {
_this.unregister(element, "disconnected");
}
}
}
}
};
// We store the last key for the FocusIn event, meaning we know if the user is tabbing around the page.
// We dont use handleKeyDown for the full event because of 2 main reasons:
// 1: handleKeyDown e.target returns the target on which the keydown is pressed (meaning we dont know which target got the focus)
// 2: handleKeyUp does return the correct e.target however when holding tab the event doesnt repeat (handleKeyDown does)
this.handleKeyDown = function (e) {
if (e.key === "Tab") {
_this.lastKeyDown = e;
}
};
this.handleFocusIn = function (e) {
if (!_this.lastKeyDown || !_this._globalSettings.enableTabPrediction) {
return;
}
var targetElement = e.target;
if (!(targetElement instanceof HTMLElement)) {
return;
}
// tabbable uses element.GetBoundingClientRect under the hood, to avoid alot of computations we cache its values
if (!_this.tabbableElementsCache.length) {
_this.tabbableElementsCache = tabbable(document.documentElement);
}
// Determine the range of elements to check based on the tab direction and offset
var isReversed = _this.lastKeyDown.shiftKey;
var currentIndex = getFocusedElementIndex(isReversed, _this.lastFocusedIndex, _this.tabbableElementsCache, targetElement);
_this.lastFocusedIndex = currentIndex;
_this.lastKeyDown = null;
var elementsToPredict = [];
for (var i = 0; i <= _this._globalSettings.tabOffset; i++) {
if (isReversed) {
var element = _this.tabbableElementsCache[currentIndex - i];
if (_this.elements.has(element)) {
elementsToPredict.push(element);
}
}
else {
var element = _this.tabbableElementsCache[currentIndex + i];
if (_this.elements.has(element)) {
elementsToPredict.push(element);
}
}
}
elementsToPredict.forEach(function (element) {
_this.callCallback(_this.elements.get(element), {
kind: "tab",
subType: isReversed ? "reverse" : "forwards",
});
});
};
this.handlePositionChange = function (entries) {
for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) {
var entry = entries_1[_i];
var elementData = _this.elements.get(entry.target);
if (!elementData)
continue;
var wasPreviouslyIntersecting = elementData.isIntersectingWithViewport;
var isNowIntersecting = entry.isIntersecting;
elementData.isIntersectingWithViewport = isNowIntersecting;
if (wasPreviouslyIntersecting !== isNowIntersecting) {
// TODO check if visibility status is changing
_this.emit({
type: "elementDataUpdated",
elementData: elementData,
timestamp: Date.now(),
updatedProp: "visibility",
});
}
if (isNowIntersecting) {
_this.updateElementBounds(entry.boundingClientRect, elementData);
_this.handleScrollPrefetch(elementData, entry.boundingClientRect);
}
}
_this.scrollDirection = null;
_this.predictedScrollPoint = null;
};
}
ForesightManager.initialize = function (props) {
if (!this.isInitiated) {
ForesightManager.manager = new ForesightManager();
}
if (props !== undefined) {
ForesightManager.manager.alterGlobalSettings(props);
}
return ForesightManager.manager;
};
ForesightManager.prototype.addEventListener = function (eventType, listener, options) {
var _this = this;
var _a, _b;
if ((_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
return function () { };
}
if (!this.eventListeners.has(eventType)) {
this.eventListeners.set(eventType, []);
}
this.eventListeners.get(eventType).push(listener);
(_b = options === null || options === void 0 ? void 0 : options.signal) === null || _b === void 0 ? void 0 : _b.addEventListener("abort", function () { return _this.removeEventListener(eventType, listener); });
};
ForesightManager.prototype.removeEventListener = function (eventType, listener) {
var listeners = this.eventListeners.get(eventType);
if (listeners) {
var index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
}
};
// Used for debugging only
ForesightManager.prototype.logSubscribers = function () {
var _this = this;
console.log("%c[ForesightManager] Current Subscribers:", "font-weight: bold; color: #3b82f6;");
var eventTypes = Array.from(this.eventListeners.keys());
if (eventTypes.length === 0) {
console.log(" No active subscribers.");
return;
}
eventTypes.forEach(function (eventType) {
var listeners = _this.eventListeners.get(eventType);
if (listeners && listeners.length > 0) {
// Use groupCollapsed so the log isn't too noisy by default.
// The user can expand the events they are interested in.
console.groupCollapsed("Event: %c".concat(eventType), "font-weight: bold;", "(".concat(listeners.length, " listener").concat(listeners.length > 1 ? "s" : "", ")"));
listeners.forEach(function (listener, index) {
console.log("[".concat(index, "]:"), listener);
});
console.groupEnd();
}
});
};
ForesightManager.prototype.emit = function (event) {
var listeners = this.eventListeners.get(event.type);
if (listeners) {
listeners.forEach(function (listener) {
try {
listener(event);
}
catch (error) {
console.error("Error in ForesightManager event listener for ".concat(event.type, ":"), error);
}
});
}
};
Object.defineProperty(ForesightManager.prototype, "getManagerData", {
get: function () {
return {
registeredElements: this.elements,
globalSettings: this._globalSettings,
globalCallbackHits: this._globalCallbackHits,
};
},
enumerable: false,
configurable: true
});
Object.defineProperty(ForesightManager, "isInitiated", {
get: function () {
return !!ForesightManager.manager;
},
enumerable: false,
configurable: true
});
Object.defineProperty(ForesightManager, "instance", {
get: function () {
return this.initialize();
},
enumerable: false,
configurable: true
});
Object.defineProperty(ForesightManager.prototype, "registeredElements", {
get: function () {
return this.elements;
},
enumerable: false,
configurable: true
});
ForesightManager.prototype.register = function (_a) {
var _this = this;
var _b, _c;
var element = _a.element, callback = _a.callback, hitSlop = _a.hitSlop, name = _a.name;
var _d = evaluateRegistrationConditions(), shouldRegister = _d.shouldRegister, isTouchDevice = _d.isTouchDevice, isLimitedConnection = _d.isLimitedConnection;
if (!shouldRegister) {
return {
isLimitedConnection: isLimitedConnection,
isTouchDevice: isTouchDevice,
isRegistered: false,
unregister: function () { },
};
}
// Setup global listeners on every first element added to the manager. It gets removed again when the map is emptied
if (!this.isSetup) {
this.initializeGlobalListeners();
}
var normalizedHitSlop = hitSlop
? normalizeHitSlop(hitSlop)
: this._globalSettings.defaultHitSlop;
// const elementRect = element.getBoundingClientRect()
var elementData = {
element: element,
callback: callback,
callbackHits: {
mouse: {
hover: 0,
trajectory: 0,
},
tab: {
forwards: 0,
reverse: 0,
},
scroll: {
down: 0,
left: 0,
right: 0,
up: 0,
},
total: 0,
},
elementBounds: {
originalRect: undefined,
expandedRect: { top: 0, left: 0, right: 0, bottom: 0 },
hitSlop: normalizedHitSlop,
},
isHovering: false,
trajectoryHitData: {
isTrajectoryHit: false,
trajectoryHitTime: 0,
trajectoryHitExpirationTimeoutId: undefined,
},
name: (_b = name !== null && name !== void 0 ? name : element.id) !== null && _b !== void 0 ? _b : "",
isIntersectingWithViewport: true,
};
this.elements.set(element, elementData);
(_c = this.positionObserver) === null || _c === void 0 ? void 0 : _c.observe(element);
this.emit({
type: "elementRegistered",
timestamp: Date.now(),
elementData: elementData,
});
return {
isTouchDevice: isTouchDevice,
isLimitedConnection: isLimitedConnection,
isRegistered: true,
unregister: function () { return _this.unregister(element, "apiCall"); },
};
};
ForesightManager.prototype.unregister = function (element, unregisterReason) {
var _a;
if (!this.elements.has(element)) {
return;
}
var foresightElementData = this.elements.get(element);
if (foresightElementData) {
this.emit({
type: "elementUnregistered",
elementData: foresightElementData,
timestamp: Date.now(),
unregisterReason: unregisterReason,
});
}
// Clear any pending trajectory expiration timeout
if (foresightElementData === null || foresightElementData === void 0 ? void 0 : foresightElementData.trajectoryHitData.trajectoryHitExpirationTimeoutId) {
clearTimeout(foresightElementData.trajectoryHitData.trajectoryHitExpirationTimeoutId);
}
(_a = this.positionObserver) === null || _a === void 0 ? void 0 : _a.unobserve(element);
this.elements.delete(element);
if (this.elements.size === 0 && this.isSetup) {
this.removeGlobalListeners();
}
};
ForesightManager.prototype.updateNumericSettings = function (newValue, setting, min, max) {
if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {
return false;
}
this._globalSettings[setting] = clampNumber(newValue, min, max, setting);
return true;
};
ForesightManager.prototype.updateBooleanSetting = function (newValue, setting) {
if (!shouldUpdateSetting(newValue, this._globalSettings[setting])) {
return false;
}
this._globalSettings[setting] = newValue;
return true;
};
ForesightManager.prototype.alterGlobalSettings = function (props) {
// Call each update function and store whether it made a change.
// This ensures every update function is executed.
var oldPositionHistorySize = this._globalSettings.positionHistorySize;
var positionHistoryChanged = this.updateNumericSettings(props === null || props === void 0 ? void 0 : props.positionHistorySize, "positionHistorySize", MIN_POSITION_HISTORY_SIZE, MAX_POSITION_HISTORY_SIZE);
if (positionHistoryChanged &&
this._globalSettings.positionHistorySize < oldPositionHistorySize) {
if (this.trajectoryPositions.positions.length > this._globalSettings.positionHistorySize) {
this.trajectoryPositions.positions = this.trajectoryPositions.positions.slice(this.trajectoryPositions.positions.length - this._globalSettings.positionHistorySize);
}
}
var trajectoryTimeChanged = this.updateNumericSettings(props === null || props === void 0 ? void 0 : props.trajectoryPredictionTime, "trajectoryPredictionTime", MIN_TRAJECTORY_PREDICTION_TIME, MAX_TRAJECTORY_PREDICTION_TIME);
var scrollMarginChanged = this.updateNumericSettings(props === null || props === void 0 ? void 0 : props.scrollMargin, "scrollMargin", MIN_SCROLL_MARGIN, MAX_SCROLL_MARGIN);
var tabOffsetChanged = this.updateNumericSettings(props === null || props === void 0 ? void 0 : props.tabOffset, "tabOffset", MIN_TAB_OFFSET, MAX_TAB_OFFSET);
var mousePredictionChanged = this.updateBooleanSetting(props === null || props === void 0 ? void 0 : props.enableMousePrediction, "enableMousePrediction");
var scrollPredictionChanged = this.updateBooleanSetting(props === null || props === void 0 ? void 0 : props.enableScrollPrediction, "enableScrollPrediction");
var tabPredictionChanged = this.updateBooleanSetting(props === null || props === void 0 ? void 0 : props.enableTabPrediction, "enableTabPrediction");
if ((props === null || props === void 0 ? void 0 : props.onAnyCallbackFired) !== undefined) {
this._globalSettings.onAnyCallbackFired = props.onAnyCallbackFired;
}
var hitSlopChanged = false;
if ((props === null || props === void 0 ? void 0 : props.defaultHitSlop) !== undefined) {
var normalizedNewHitSlop = normalizeHitSlop(props.defaultHitSlop);
if (!areRectsEqual(this._globalSettings.defaultHitSlop, normalizedNewHitSlop)) {
this._globalSettings.defaultHitSlop = normalizedNewHitSlop;
hitSlopChanged = true;
this.forceUpdateAllElementBounds();
}
}
var settingsActuallyChanged = positionHistoryChanged ||
trajectoryTimeChanged ||
tabOffsetChanged ||
mousePredictionChanged ||
tabPredictionChanged ||
scrollPredictionChanged ||
hitSlopChanged ||
scrollMarginChanged;
if (settingsActuallyChanged) {
this.emit({
type: "managerSettingsChanged",
timestamp: Date.now(),
newSettings: this._globalSettings,
});
}
};
ForesightManager.prototype.forceUpdateAllElementBounds = function () {
var _this = this;
this.elements.forEach(function (_, element) {
var elementData = _this.elements.get(element);
// For performance only update rects that are currently intersecting with the viewport
if (elementData && elementData.isIntersectingWithViewport) {
_this.forceUpdateElementBounds(elementData);
}
});
};
ForesightManager.prototype.updatePointerState = function (e) {
this.trajectoryPositions.currentPoint = { x: e.clientX, y: e.clientY };
this.trajectoryPositions.predictedPoint = this._globalSettings.enableMousePrediction
? predictNextMousePosition(this.trajectoryPositions.currentPoint, this.trajectoryPositions.positions, // History before the currentPoint was added
this._globalSettings.positionHistorySize, this._globalSettings.trajectoryPredictionTime)
: __assign({}, this.trajectoryPositions.currentPoint);
};
/**
* Processes elements that unregister after a single callback.
*
* This is a "fire-and-forget" handler. Its only goal is to trigger the
* callback once. It does so if the mouse trajectory is predicted to hit the
* element (if prediction is on) OR if the mouse physically hovers over it.
* It does not track state, as the element is immediately unregistered.
*
* @param elementData - The data object for the foresight element.
* @param element - The HTML element being interacted with.
*/
ForesightManager.prototype.handleCallbackInteraction = function (elementData) {
var expandedRect = elementData.elementBounds.expandedRect;
// when enable mouse prediction is off, we only check if the mouse is physically hovering over the element
if (!this._globalSettings.enableMousePrediction) {
if (isPointInRectangle(this.trajectoryPositions.currentPoint, expandedRect)) {
this.callCallback(elementData, { kind: "mouse", subType: "hover" });
return;
}
}
else if (lineSegmentIntersectsRect(this.trajectoryPositions.currentPoint, this.trajectoryPositions.predictedPoint, expandedRect)) {
this.callCallback(elementData, { kind: "mouse", subType: "trajectory" });
}
};
ForesightManager.prototype.updateHitCounters = function (elementData, hitType) {
switch (hitType.kind) {
case "mouse":
elementData.callbackHits.mouse[hitType.subType]++;
this._globalCallbackHits.mouse[hitType.subType]++;
break;
case "tab":
elementData.callbackHits.tab[hitType.subType]++;
this._globalCallbackHits.tab[hitType.subType]++;
break;
case "scroll":
elementData.callbackHits.scroll[hitType.subType]++;
this._globalCallbackHits.scroll[hitType.subType]++;
break;
}
elementData.callbackHits.total++;
this._globalCallbackHits.total++;
};
ForesightManager.prototype.callCallback = function (elementData, hitType) {
if (elementData) {
this.updateHitCounters(elementData, hitType);
elementData.callback();
this._globalSettings.onAnyCallbackFired(elementData, this.getManagerData);
this.emit({
type: "callbackFired",
timestamp: Date.now(),
elementData: elementData,
hitType: hitType,
});
this.unregister(elementData.element, "callbackHit");
}
};
/**
* 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.
* We need an observer for that
*/
ForesightManager.prototype.forceUpdateElementBounds = function (elementData) {
var newOriginalRect = elementData.element.getBoundingClientRect();
var expandedRect = getExpandedRect(newOriginalRect, elementData.elementBounds.hitSlop);
if (!areRectsEqual(expandedRect, elementData.elementBounds.expandedRect)) {
var updatedElementData = __assign(__assign({}, elementData), { elementBounds: __assign(__assign({}, elementData.elementBounds), { originalRect: newOriginalRect, expandedRect: expandedRect }) });
this.elements.set(elementData.element, updatedElementData);
this.emit({
type: "elementDataUpdated",
timestamp: Date.now(),
elementData: updatedElementData,
updatedProp: "bounds",
});
}
};
ForesightManager.prototype.updateElementBounds = function (newRect, elementData) {
var updatedElementData = __assign(__assign({}, elementData), { elementBounds: __assign(__assign({}, elementData.elementBounds), { originalRect: newRect, expandedRect: getExpandedRect(newRect, elementData.elementBounds.hitSlop) }) });
this.elements.set(elementData.element, updatedElementData);
this.emit({
type: "elementDataUpdated",
timestamp: Date.now(),
elementData: updatedElementData,
updatedProp: "bounds",
});
};
ForesightManager.prototype.handleScrollPrefetch = function (elementData, newRect) {
var _a, _b;
if (this._globalSettings.enableScrollPrediction) {
// This means the foresightmanager is initializing registered elements, we dont want to calc the scroll direction here
if (!elementData.elementBounds.originalRect) {
return;
}
// ONCE per animation frame we decide what the scroll direction is
this.scrollDirection =
(_a = this.scrollDirection) !== null && _a !== void 0 ? _a : getScrollDirection(elementData.elementBounds.originalRect, newRect);
if (this.scrollDirection === "none") {
return;
}
// ONCE per animation frame we decide the predicted scroll point
this.predictedScrollPoint =
(_b = this.predictedScrollPoint) !== null && _b !== void 0 ? _b : predictNextScrollPosition(this.trajectoryPositions.currentPoint, this.scrollDirection, this._globalSettings.scrollMargin);
if (lineSegmentIntersectsRect(this.trajectoryPositions.currentPoint, this.predictedScrollPoint, elementData === null || elementData === void 0 ? void 0 : elementData.elementBounds.expandedRect)) {
this.callCallback(elementData, {
kind: "scroll",
subType: this.scrollDirection,
});
}
this.emit({
type: "scrollTrajectoryUpdate",
timestamp: Date.now(),
currentPoint: this.trajectoryPositions.currentPoint,
predictedPoint: this.predictedScrollPoint,
});
}
else {
if (isPointInRectangle(this.trajectoryPositions.currentPoint, elementData.elementBounds.expandedRect)) {
this.callCallback(elementData, {
kind: "mouse",
subType: "hover",
});
}
}
};
ForesightManager.prototype.initializeGlobalListeners = function () {
if (this.isSetup) {
return;
}
// To avoid setting up listeners while ssr
if (typeof window === "undefined" || typeof document === "undefined") {
return;
}
this.globalListenersController = new AbortController();
var signal = this.globalListenersController.signal;
document.addEventListener("mousemove", this.handleMouseMove); // Dont add signal we still need to emit events even without elements
document.addEventListener("keydown", this.handleKeyDown, { signal: signal });
document.addEventListener("focusin", this.handleFocusIn, { signal: signal });
//Mutation observer is to automatically unregister elements when they leave the DOM. Its a fail-safe for if the user forgets to do it.
this.domObserver = new MutationObserver(this.handleDomMutations);
this.domObserver.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: false,
});
// Handles all position based changes and update the rects of the elements. completely async to avoid dirtying the main thread.
// Handles resize of elements
// Handles resize of viewport
// Handles scrolling
this.positionObserver = new PositionObserver(this.handlePositionChange);
this.isSetup = true;
};
ForesightManager.prototype.removeGlobalListeners = function () {
var _a, _b, _c;
this.isSetup = false;
(_a = this.globalListenersController) === null || _a === void 0 ? void 0 : _a.abort(); // Remove all event listeners only in non debug mode
this.globalListenersController = null;
(_b = this.domObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
this.domObserver = null;
(_c = this.positionObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
this.positionObserver = null;
};
return ForesightManager;
}());
export { ForesightManager };
//# sourceMappingURL=ForesightManager.js.map