js.foresight-devtools
Version:
Visual debugging tools for ForesightJS - mouse trajectory prediction and element interaction visualization
747 lines • 57.7 kB
JavaScript
var DEFAULT_IS_DEBUGGER_MINIMIZED = false;
var DEFAULT_SHOW_NAME_TAGS = true;
var MAX_POSITION_HISTORY_SIZE = 30;
var MAX_SCROLL_MARGIN = 300;
var MAX_TAB_OFFSET = 20;
var MAX_TRAJECTORY_PREDICTION_TIME = 200;
var MIN_POSITION_HISTORY_SIZE = 2;
var MIN_SCROLL_MARGIN = 30;
var MIN_TAB_OFFSET = 0;
var MIN_TRAJECTORY_PREDICTION_TIME = 10;
var POSITION_HISTORY_SIZE_UNIT = "points";
var SCROLL_MARGIN_UNIT = "px";
var TAB_OFFSET_UNIT = "tabs";
var TRAJECTORY_PREDICTION_TIME_UNIT = "ms";
import { objectToMethodCall } from "./helpers/objectToMethodCall";
import { createAndAppendStyle } from "./helpers/createAndAppend";
import { getIntersectingIcon } from "./helpers/getIntersectingIcon";
var COPY_SVG_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>";
var TICK_SVG_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>";
var SORT_SVG_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"></polygon></svg>";
var NO_ELEMENTS_STRING = "<em>No elements registered.</em>";
var DebuggerControlPanel = /** @class */ (function () {
function DebuggerControlPanel(foresightManager, debuggerInstance) {
this.elementListItemsContainer = null;
this.elementCountSpan = null;
this.callbackCountSpan = null;
this.elementListItems = new Map();
this.trajectoryEnabledCheckbox = null;
this.tabEnabledCheckbox = null;
this.scrollEnabledCheckbox = null;
this.historySizeSlider = null;
this.historyValueSpan = null;
this.predictionTimeSlider = null;
this.predictionValueSpan = null;
this.tabOffsetSlider = null;
this.tabOffsetValueSpan = null;
this.scrollMarginSlider = null;
this.scrollMarginValueSpan = null;
this.showNameTagsCheckbox = null;
this.sortOptionsPopup = null;
this.sortButton = null;
this.containerMinimizeButton = null;
this.allSettingsSectionsContainer = null;
this.debuggerElementsSection = null;
this.isContainerMinimized = false;
this.isMouseSettingsMinimized = true;
this.isKeyboardSettingsMinimized = true;
this.isScrollSettingsMinimized = true;
this.isGeneralSettingsMinimized = true;
this.SESSION_STORAGE_KEY = "jsforesightDebuggerSectionStates";
this.copySettingsButton = null;
this.minimizedElementCount = null;
this.copyTimeoutId = null;
this.closeSortDropdownHandler = null;
this.foresightManagerInstance = foresightManager;
this.debuggerInstance = debuggerInstance;
}
/**
* The initialize method now creates the instance if needed,
* then calls the setup method to ensure the UI is ready.
*/
DebuggerControlPanel.initialize = function (foresightManager, debuggerInstance, shadowRoot, debuggerSettings) {
if (!DebuggerControlPanel.isInitiated) {
DebuggerControlPanel.debuggerControlPanelInstance = new DebuggerControlPanel(foresightManager, debuggerInstance);
}
var instance = DebuggerControlPanel.debuggerControlPanelInstance;
// This will build the DOM on first run or rebuild it on subsequent runs after cleanup.
instance._setupDOMAndListeners(shadowRoot, debuggerSettings);
return instance;
};
/**
* All DOM creation and event listener setup logic is moved here.
* This method can be called to "revive" a cleaned-up instance.
*/
DebuggerControlPanel.prototype._setupDOMAndListeners = function (shadowRoot, debuggerSettings) {
// Guard clause to prevent re-running if the UI is already active.
if (this.controlsContainer) {
return;
}
this.shadowRoot = shadowRoot;
this.isContainerMinimized = debuggerSettings.isControlPanelDefaultMinimized;
this.controlsContainer = this.createControlContainer();
this.shadowRoot.appendChild(this.controlsContainer);
this.controlPanelStyleElement = createAndAppendStyle(this.getStyles(), this.shadowRoot, "debug-control-panel");
this.queryDOMElements();
this.originalSectionStates();
this.setupEventListeners();
this.updateContainerVisibilityState();
this.updateControlsState(this.foresightManagerInstance.getManagerData.globalSettings, debuggerSettings);
};
Object.defineProperty(DebuggerControlPanel, "isInitiated", {
get: function () {
return !!DebuggerControlPanel.debuggerControlPanelInstance;
},
enumerable: false,
configurable: true
});
DebuggerControlPanel.prototype.loadSectionStatesFromSessionStorage = function () {
var _a, _b, _c, _d;
var storedStatesRaw = sessionStorage.getItem(this.SESSION_STORAGE_KEY);
var loadedStates = {};
if (storedStatesRaw) {
loadedStates = JSON.parse(storedStatesRaw);
}
this.isMouseSettingsMinimized = (_a = loadedStates.mouse) !== null && _a !== void 0 ? _a : true;
this.isKeyboardSettingsMinimized = (_b = loadedStates.keyboard) !== null && _b !== void 0 ? _b : true;
this.isScrollSettingsMinimized = (_c = loadedStates.scroll) !== null && _c !== void 0 ? _c : true;
this.isGeneralSettingsMinimized = (_d = loadedStates.general) !== null && _d !== void 0 ? _d : true;
return loadedStates;
};
DebuggerControlPanel.prototype.saveSectionStatesToSessionStorage = function () {
var states = {
mouse: this.isMouseSettingsMinimized,
keyboard: this.isKeyboardSettingsMinimized,
scroll: this.isScrollSettingsMinimized,
general: this.isGeneralSettingsMinimized,
};
try {
sessionStorage.setItem(this.SESSION_STORAGE_KEY, JSON.stringify(states));
}
catch (e) {
console.error("Foresight Debugger: Could not save section states to session storage.", e);
}
};
DebuggerControlPanel.prototype.queryDOMElements = function () {
this.trajectoryEnabledCheckbox = this.controlsContainer.querySelector("#trajectory-enabled");
this.tabEnabledCheckbox = this.controlsContainer.querySelector("#tab-enabled");
this.scrollEnabledCheckbox = this.controlsContainer.querySelector("#scroll-enabled");
this.historySizeSlider = this.controlsContainer.querySelector("#history-size");
this.historyValueSpan = this.controlsContainer.querySelector("#history-value");
this.predictionTimeSlider = this.controlsContainer.querySelector("#prediction-time");
this.predictionValueSpan = this.controlsContainer.querySelector("#prediction-value");
this.tabOffsetSlider = this.controlsContainer.querySelector("#tab-offset");
this.tabOffsetValueSpan = this.controlsContainer.querySelector("#tab-offset-value");
this.scrollMarginSlider = this.controlsContainer.querySelector("#scroll-margin");
this.scrollMarginValueSpan = this.controlsContainer.querySelector("#scroll-margin-value");
this.elementListItemsContainer = this.controlsContainer.querySelector("#element-list-items-container");
this.showNameTagsCheckbox = this.controlsContainer.querySelector("#toggle-name-tags");
this.sortOptionsPopup = this.controlsContainer.querySelector("#sort-options-popup");
this.sortButton = this.controlsContainer.querySelector(".sort-button");
this.elementCountSpan = this.controlsContainer.querySelector("#element-count");
this.callbackCountSpan = this.controlsContainer.querySelector("#callback-count");
this.containerMinimizeButton = this.controlsContainer.querySelector(".minimize-button");
this.allSettingsSectionsContainer = this.controlsContainer.querySelector(".all-settings-sections-container");
this.debuggerElementsSection = this.controlsContainer.querySelector(".debugger-elements");
this.copySettingsButton = this.controlsContainer.querySelector(".copy-settings-button");
this.minimizedElementCount = this.controlsContainer.querySelector(".minimized-element-count");
};
DebuggerControlPanel.prototype.handleCopySettings = function () {
var _this = this;
if (!this.copySettingsButton)
return;
navigator.clipboard
.writeText(objectToMethodCall(this.foresightManagerInstance.getManagerData.globalSettings, "ForesightManager.initialize"))
.then(function () {
_this.copySettingsButton.innerHTML = TICK_SVG_ICON;
if (_this.copyTimeoutId) {
clearTimeout(_this.copyTimeoutId);
}
_this.copyTimeoutId = setTimeout(function () {
if (_this.copySettingsButton) {
_this.copySettingsButton.innerHTML = COPY_SVG_ICON;
}
_this.copyTimeoutId = null;
}, 3000);
})
.catch(function (err) {
console.error("Foresight Debugger: Could not copy settings to clipboard", err);
});
};
DebuggerControlPanel.prototype.createInputEventListener = function (element, spanElement, unit, setting) {
var _this = this;
if (!element || !spanElement) {
return;
}
element.addEventListener("input", function (e) {
var _a;
var value = parseInt(e.target.value, 10);
spanElement.textContent = "".concat(value, " ").concat(unit);
_this.foresightManagerInstance.alterGlobalSettings((_a = {},
_a[setting] = value,
_a));
});
};
DebuggerControlPanel.prototype.createChangeEventListener = function (element, setting) {
var _this = this;
if (!element) {
return;
}
// This is the crucial part. We get an object representing the debugger's
// settings so we can check against it at runtime.
// Replace `this.debuggerInstance.settings` with however you access
// the settings object on your instance.
var debuggerSettings = this.debuggerInstance.getDebuggerData.settings;
element.addEventListener("change", function (e) {
var _a, _b;
var isChecked = e.target.checked;
// The `in` operator checks if the key (e.g., "showOverlay") exists on the
// debuggerSettings object. This is a true runtime check.
if (setting in debuggerSettings) {
// Although we've confirmed the key belongs to the debugger, TypeScript's
// control flow analysis doesn't automatically narrow the type of the
// `setting` variable itself here.
// So, we use a type assertion to satisfy the compiler.
_this.debuggerInstance.alterDebuggerSettings((_a = {},
_a[setting] = isChecked,
_a));
}
else {
// If the key is not in debuggerSettings, it must be a manager setting.
_this.foresightManagerInstance.alterGlobalSettings((_b = {},
_b[setting] = isChecked,
_b));
}
});
};
DebuggerControlPanel.prototype.createSectionVisibilityToggleEventListener = function (section, isMinimizedFlagName) {
var _this = this;
var sectionHeader = section === null || section === void 0 ? void 0 : section.querySelector(".debugger-section-header");
sectionHeader === null || sectionHeader === void 0 ? void 0 : sectionHeader.addEventListener("click", function (e) {
e.stopPropagation();
_this.toggleMinimizeSection(section, (_this[isMinimizedFlagName] = !_this[isMinimizedFlagName]));
});
};
DebuggerControlPanel.prototype.setupEventListeners = function () {
var _this = this;
var _a, _b, _c, _d;
this.createChangeEventListener(this.trajectoryEnabledCheckbox, "enableMousePrediction");
this.createChangeEventListener(this.tabEnabledCheckbox, "enableTabPrediction");
this.createChangeEventListener(this.scrollEnabledCheckbox, "enableScrollPrediction");
this.createChangeEventListener(this.showNameTagsCheckbox, "showNameTags");
this.createInputEventListener(this.historySizeSlider, this.historyValueSpan, POSITION_HISTORY_SIZE_UNIT, "positionHistorySize");
this.createInputEventListener(this.predictionTimeSlider, this.predictionValueSpan, TRAJECTORY_PREDICTION_TIME_UNIT, "trajectoryPredictionTime");
this.createInputEventListener(this.tabOffsetSlider, this.tabOffsetValueSpan, TAB_OFFSET_UNIT, "tabOffset");
this.createInputEventListener(this.scrollMarginSlider, this.scrollMarginValueSpan, SCROLL_MARGIN_UNIT, "scrollMargin");
(_a = this.sortButton) === null || _a === void 0 ? void 0 : _a.addEventListener("click", function (e) {
var _a;
e.stopPropagation();
(_a = _this.sortOptionsPopup) === null || _a === void 0 ? void 0 : _a.classList.toggle("active");
});
(_b = this.sortOptionsPopup) === null || _b === void 0 ? void 0 : _b.addEventListener("click", function (e) {
var _a;
var target = e.target;
var sortButton = target.closest("[data-sort]");
if (!sortButton)
return;
var value = sortButton.dataset.sort;
_this.debuggerInstance.alterDebuggerSettings({
sortElementList: value,
});
_this.sortAndReorderElements();
_this.updateSortOptionUI(value);
(_a = _this.sortOptionsPopup) === null || _a === void 0 ? void 0 : _a.classList.remove("active");
});
this.closeSortDropdownHandler = function (e) {
var _a, _b;
if (((_a = _this.sortOptionsPopup) === null || _a === void 0 ? void 0 : _a.classList.contains("active")) &&
!((_b = _this.sortButton) === null || _b === void 0 ? void 0 : _b.contains(e.target))) {
_this.sortOptionsPopup.classList.remove("active");
}
};
document.addEventListener("click", this.closeSortDropdownHandler);
(_c = this.containerMinimizeButton) === null || _c === void 0 ? void 0 : _c.addEventListener("click", function () {
_this.isContainerMinimized = !_this.isContainerMinimized;
_this.updateContainerVisibilityState();
});
(_d = this.copySettingsButton) === null || _d === void 0 ? void 0 : _d.addEventListener("click", this.handleCopySettings.bind(this));
this.createSectionVisibilityToggleEventListener(this.controlsContainer.querySelector(".mouse-settings-section"), "isMouseSettingsMinimized");
this.createSectionVisibilityToggleEventListener(this.controlsContainer.querySelector(".keyboard-settings-section"), "isKeyboardSettingsMinimized");
this.createSectionVisibilityToggleEventListener(this.controlsContainer.querySelector(".scroll-settings-section"), "isScrollSettingsMinimized");
this.createSectionVisibilityToggleEventListener(this.controlsContainer.querySelector(".general-settings-section"), "isGeneralSettingsMinimized");
};
DebuggerControlPanel.prototype.toggleMinimizeSection = function (section, shouldMinimize) {
if (!section) {
return;
}
var sectionContent = section.querySelector(".debugger-section-content");
var minimizeButton = section.querySelector(".section-minimize-button");
if (sectionContent && minimizeButton) {
if (shouldMinimize) {
sectionContent.style.display = "none";
minimizeButton.textContent = "+";
}
else {
sectionContent.style.display = "flex";
minimizeButton.textContent = "-";
}
}
this.saveSectionStatesToSessionStorage();
};
DebuggerControlPanel.prototype.originalSectionStates = function () {
var _a, _b, _c, _d, _e;
var states = this.loadSectionStatesFromSessionStorage();
this.toggleMinimizeSection(this.controlsContainer.querySelector(".mouse-settings-section"), (_a = states.mouse) !== null && _a !== void 0 ? _a : true);
this.toggleMinimizeSection(this.controlsContainer.querySelector(".keyboard-settings-section"), (_b = states.keyboard) !== null && _b !== void 0 ? _b : true);
this.toggleMinimizeSection(this.controlsContainer.querySelector(".scroll-settings-section"), (_c = states.scroll) !== null && _c !== void 0 ? _c : true);
this.toggleMinimizeSection(this.controlsContainer.querySelector(".general-settings-section"), (_d = states.general) !== null && _d !== void 0 ? _d : true);
// Ensure the element list is always open by default
var elementListContent = (_e = this.debuggerElementsSection) === null || _e === void 0 ? void 0 : _e.querySelector(".debugger-section-content");
if (elementListContent) {
;
elementListContent.style.display = "flex";
}
};
DebuggerControlPanel.prototype.updateContainerVisibilityState = function () {
if (!this.containerMinimizeButton)
return;
if (this.isContainerMinimized) {
this.controlsContainer.classList.add("minimized");
this.containerMinimizeButton.textContent = "+";
if (this.allSettingsSectionsContainer)
this.allSettingsSectionsContainer.style.display = "none";
if (this.debuggerElementsSection)
this.debuggerElementsSection.style.display = "none";
if (this.copySettingsButton)
this.copySettingsButton.style.display = "none";
if (this.minimizedElementCount)
this.minimizedElementCount.style.display = "";
}
else {
this.controlsContainer.classList.remove("minimized");
this.containerMinimizeButton.textContent = "-";
if (this.allSettingsSectionsContainer)
this.allSettingsSectionsContainer.style.display = "";
if (this.debuggerElementsSection)
this.debuggerElementsSection.style.display = "";
if (this.copySettingsButton)
this.copySettingsButton.style.display = "";
if (this.minimizedElementCount)
this.minimizedElementCount.style.display = "none";
}
};
// Adds a tick before the choosen sort option
DebuggerControlPanel.prototype.updateSortOptionUI = function (currentSort) {
var _a;
(_a = this.sortOptionsPopup) === null || _a === void 0 ? void 0 : _a.querySelectorAll("[data-sort]").forEach(function (button) {
var btn = button;
if (btn.dataset.sort === currentSort) {
btn.classList.add("active-sort-option");
}
else {
btn.classList.remove("active-sort-option");
}
});
};
DebuggerControlPanel.prototype.updateControlsState = function (managerSettings, debuggerSettings) {
var _a;
if (this.trajectoryEnabledCheckbox) {
this.trajectoryEnabledCheckbox.checked = managerSettings.enableMousePrediction;
}
if (this.tabEnabledCheckbox) {
this.tabEnabledCheckbox.checked = managerSettings.enableTabPrediction;
}
if (this.scrollEnabledCheckbox) {
this.scrollEnabledCheckbox.checked = managerSettings.enableScrollPrediction;
}
if (this.showNameTagsCheckbox) {
this.showNameTagsCheckbox.checked = debuggerSettings.showNameTags;
}
this.updateSortOptionUI((_a = debuggerSettings.sortElementList) !== null && _a !== void 0 ? _a : "visibility");
if (this.historySizeSlider && this.historyValueSpan) {
this.historySizeSlider.value = managerSettings.positionHistorySize.toString();
this.historyValueSpan.textContent = "".concat(managerSettings.positionHistorySize, " ").concat(POSITION_HISTORY_SIZE_UNIT);
}
if (this.predictionTimeSlider && this.predictionValueSpan) {
this.predictionTimeSlider.value = managerSettings.trajectoryPredictionTime.toString();
this.predictionValueSpan.textContent = "".concat(managerSettings.trajectoryPredictionTime, " ").concat(TRAJECTORY_PREDICTION_TIME_UNIT);
}
if (this.tabOffsetSlider && this.tabOffsetValueSpan) {
this.tabOffsetSlider.value = managerSettings.tabOffset.toString();
this.tabOffsetValueSpan.textContent = "".concat(managerSettings.tabOffset, " ").concat(TAB_OFFSET_UNIT);
}
if (this.scrollMarginSlider && this.scrollMarginValueSpan) {
this.scrollMarginSlider.value = managerSettings.scrollMargin.toString();
this.scrollMarginValueSpan.textContent = "".concat(managerSettings.scrollMargin, " ").concat(SCROLL_MARGIN_UNIT);
}
};
DebuggerControlPanel.prototype.refreshRegisteredElementCountDisplay = function (elementsMap) {
if (!this.elementCountSpan || !this.callbackCountSpan) {
return;
}
var visibleElementCount = 0;
elementsMap.forEach(function (data) {
if (data.isIntersectingWithViewport) {
visibleElementCount++;
}
});
var totalElements = elementsMap.size;
var _a = this.foresightManagerInstance.getManagerData.globalCallbackHits, tab = _a.tab, mouse = _a.mouse, scroll = _a.scroll, total = _a.total;
var visibleTitle = [
"Element Visibility Status",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Visible in Viewport: ".concat(visibleElementCount),
"Not in Viewport: ".concat(totalElements - visibleElementCount),
"Total Registered Elements: ".concat(totalElements),
"",
"Note: Only elements visible in the viewport",
"are actively tracked by intersection observers.",
];
if (this.minimizedElementCount) {
this.minimizedElementCount.textContent = "".concat(visibleElementCount, "/").concat(totalElements);
this.minimizedElementCount.title = visibleTitle.join("\n");
}
this.elementCountSpan.textContent = "Visible: ".concat(visibleElementCount, "/").concat(totalElements, " ~ ");
this.elementCountSpan.title = visibleTitle.join("\n");
this.callbackCountSpan.textContent = "Mouse: ".concat(mouse.hover + mouse.trajectory, " Tab: ").concat(tab.forwards + tab.reverse, " Scroll: ").concat(scroll.down + scroll.left + scroll.right + scroll.up);
this.callbackCountSpan.title = [
"Callback Execution Stats",
"━━━━━━━━━━━━━━━━━━━━━━━━",
"Mouse Callbacks",
" \u2022 Trajectory: ".concat(mouse.trajectory),
" \u2022 Hover: ".concat(mouse.hover),
" \u2022 Subtotal: ".concat(mouse.hover + mouse.trajectory),
"",
"Keyboard Callbacks:",
" \u2022 Tab Forward: ".concat(tab.forwards),
" \u2022 Tab Reverse: ".concat(tab.reverse),
" \u2022 Subtotal: ".concat(tab.forwards + tab.reverse),
"",
"Scroll Callbacks:",
" \u2022 Up: ".concat(scroll.up, " | Down: ").concat(scroll.down),
" \u2022 Left: ".concat(scroll.left, " | Right: ").concat(scroll.right),
" \u2022 Subtotal: ".concat(scroll.up + scroll.down + scroll.left + scroll.right),
"",
"Total Callbacks: " + total,
].join("\n");
};
DebuggerControlPanel.prototype.removeElementFromList = function (elementData) {
if (!this.elementListItemsContainer)
return;
var listItem = this.elementListItems.get(elementData.element);
if (listItem) {
listItem.remove();
this.elementListItems.delete(elementData.element);
var elementsMap = this.foresightManagerInstance.registeredElements;
this.refreshRegisteredElementCountDisplay(elementsMap);
if (this.elementListItems.size === 0) {
this.elementListItemsContainer.innerHTML = NO_ELEMENTS_STRING;
}
}
};
DebuggerControlPanel.prototype.updateElementVisibilityStatus = function (elementData) {
if (!this.elementListItemsContainer)
return;
var listItem = this.elementListItems.get(elementData.element);
if (!listItem) {
this.addElementToList(elementData);
return;
}
listItem.classList.toggle("not-in-viewport", !elementData.isIntersectingWithViewport);
var intersectingElement = listItem.querySelector(".intersecting-indicator");
if (intersectingElement) {
var intersectingIcon = getIntersectingIcon(elementData.isIntersectingWithViewport);
intersectingElement.textContent = intersectingIcon;
}
this.refreshRegisteredElementCountDisplay(this.foresightManagerInstance.registeredElements);
this.sortAndReorderElements();
};
DebuggerControlPanel.prototype.sortAndReorderElements = function () {
var _this = this;
var _a;
if (!this.elementListItemsContainer)
return;
var sortOrder = (_a = this.debuggerInstance.getDebuggerData.settings.sortElementList) !== null && _a !== void 0 ? _a : "visibility";
var elementsData = Array.from(this.foresightManagerInstance.registeredElements.values());
if (sortOrder !== "insertionOrder") {
var sortByDocumentPosition_1 = function (a, b) {
var position = a.element.compareDocumentPosition(b.element);
if (position & Node.DOCUMENT_POSITION_FOLLOWING)
return -1;
if (position & Node.DOCUMENT_POSITION_PRECEDING)
return 1;
return 0;
};
if (sortOrder === "visibility") {
elementsData.sort(function (a, b) {
if (a.isIntersectingWithViewport !== b.isIntersectingWithViewport) {
return a.isIntersectingWithViewport ? -1 : 1;
}
return sortByDocumentPosition_1(a, b);
});
}
else if (sortOrder === "documentOrder") {
elementsData.sort(sortByDocumentPosition_1);
}
}
var fragment = document.createDocumentFragment();
if (elementsData.length) {
elementsData.forEach(function (elementData) {
var listItem = _this.elementListItems.get(elementData.element);
if (listItem) {
// Appending to the fragment is cheap (it's off-screen)
fragment.appendChild(listItem);
}
});
this.elementListItemsContainer.innerHTML = "";
this.elementListItemsContainer.appendChild(fragment);
}
};
DebuggerControlPanel.prototype.addElementToList = function (elementData, sort) {
if (sort === void 0) { sort = true; }
if (!this.elementListItemsContainer)
return;
if (this.elementListItemsContainer.innerHTML === NO_ELEMENTS_STRING) {
this.elementListItemsContainer.innerHTML = "";
}
if (this.elementListItems.has(elementData.element))
return;
var listItem = document.createElement("div");
listItem.className = "element-list-item";
this.updateListItemContent(listItem, elementData);
this.elementListItemsContainer.appendChild(listItem);
this.elementListItems.set(elementData.element, listItem);
this.refreshRegisteredElementCountDisplay(this.foresightManagerInstance.registeredElements);
if (sort) {
this.sortAndReorderElements();
}
};
DebuggerControlPanel.prototype.updateListItemContent = function (listItem, elementData) {
// Determine the viewport icon based on current visibility status
var intersectingIcon = getIntersectingIcon(elementData.isIntersectingWithViewport);
listItem.classList.toggle("not-in-viewport", !elementData.isIntersectingWithViewport);
var hitSlopText = "N/A";
if (elementData.elementBounds.hitSlop) {
var _a = elementData.elementBounds.hitSlop, top_1 = _a.top, right = _a.right, bottom = _a.bottom, left = _a.left;
hitSlopText = "T:".concat(top_1, " R:").concat(right, " B:").concat(bottom, " L:").concat(left);
}
// Create comprehensive title with all information
var comprehensiveTitle = [
"".concat(elementData.name || "Unnamed Element"),
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Viewport Status:",
elementData.isIntersectingWithViewport
? " ✓ In viewport - actively tracked by observers"
: " ✗ Not in viewport - not being tracked",
"",
"Hit Slop:",
elementData.elementBounds.hitSlop
? [
" Top: ".concat(elementData.elementBounds.hitSlop.top, "px, Bottom: ").concat(elementData.elementBounds.hitSlop.bottom, "px "),
" Right: ".concat(elementData.elementBounds.hitSlop.right, "px, Left: ").concat(elementData.elementBounds.hitSlop.left, "px"),
].join("\n")
: " • Not defined - using element's natural boundaries",
"",
].join("\n");
listItem.title = comprehensiveTitle;
listItem.innerHTML = "\n <span class=\"intersecting-indicator\">".concat(intersectingIcon, "</span>\n <span class=\"element-name\">").concat(elementData.name || "Unnamed Element", "</span>\n <span class=\"hit-slop\">").concat(hitSlopText, "</span>\n ");
};
/**
* The cleanup method is updated to be more thorough, nullifying all
* DOM-related properties to put the instance in a dormant state.
*/
DebuggerControlPanel.prototype.cleanup = function () {
var _a, _b;
(_a = this.controlsContainer) === null || _a === void 0 ? void 0 : _a.remove();
(_b = this.controlPanelStyleElement) === null || _b === void 0 ? void 0 : _b.remove();
if (this.copyTimeoutId) {
clearTimeout(this.copyTimeoutId);
this.copyTimeoutId = null;
}
if (this.closeSortDropdownHandler) {
document.removeEventListener("click", this.closeSortDropdownHandler);
this.closeSortDropdownHandler = null;
}
// Nullify all DOM-related properties to signal it's "cleaned up"
this.controlsContainer = null;
this.controlPanelStyleElement = null;
this.elementListItemsContainer = null;
this.elementCountSpan = null;
this.callbackCountSpan = null;
this.elementListItems.clear();
this.containerMinimizeButton = null;
this.allSettingsSectionsContainer = null;
this.debuggerElementsSection = null;
this.trajectoryEnabledCheckbox = null;
this.tabEnabledCheckbox = null;
this.scrollEnabledCheckbox = null;
this.historySizeSlider = null;
this.historyValueSpan = null;
this.predictionTimeSlider = null;
this.predictionValueSpan = null;
this.tabOffsetSlider = null;
this.tabOffsetValueSpan = null;
this.scrollMarginSlider = null;
this.scrollMarginValueSpan = null;
this.showNameTagsCheckbox = null;
this.sortOptionsPopup = null;
this.sortButton = null;
this.copySettingsButton = null;
};
DebuggerControlPanel.prototype.createControlContainer = function () {
var container = document.createElement("div");
container.id = "debug-controls";
container.innerHTML = /* html */ "\n <div class=\"debugger-title-container\">\n <button class=\"minimize-button\">-</button>\n <div class=\"title-group\">\n <h2>Foresight Debugger</h2>\n <span class=\"info-icon\" title=\"".concat([
"Foresight Debugger Information",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Session-Only Changes:",
"All adjustments made here apply only to the",
"current browser session and won't persist.",
"",
"Permanent Configuration:",
"To make lasting changes, update the initial",
"values in your ForesightManager.initialize().",
"",
"You can copy the current debugger settings",
"with the button on the right",
].join("\n"), "\">i</span>\n </div>\n <button class=\"copy-settings-button\" title=\"").concat([
"Copy Settings to Clipboard",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Copies the current configuration as a",
"formatted method call that you can paste",
"directly into your code.",
].join("\n"), "\">\n ").concat(COPY_SVG_ICON, "\n </button>\n <span class=\"minimized-element-count\">\n </span>\n </div>\n\n <div class=\"all-settings-sections-container\">\n <div class=\"debugger-section mouse-settings-section\">\n <div class=\"debugger-section-header collapsible\">\n <h3>Mouse Settings</h3>\n <button class=\"section-minimize-button\">-</button>\n </div>\n <div class=\"debugger-section-content mouse-settings-content\">\n <div class=\"control-row\">\n <label for=\"trajectory-enabled\">\n Enable Mouse Prediction\n <span class=\"info-icon\" title=\"").concat([
"Mouse Prediction Control",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"When enabled: Predicts mouse movement",
"trajectory and triggers callbacks before",
"the cursor reaches the target element.",
"",
"When disabled: Only direct hover events",
"trigger actions (next to tab/scroll).",
"",
"Property: enableMousePrediction",
].join("\n"), "\">i</span>\n </label>\n <input type=\"checkbox\" id=\"trajectory-enabled\">\n </div>\n <div class=\"control-row\">\n <label for=\"history-size\">\n History Size\n <span class=\"info-icon\" title=\"").concat([
"Position History",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Controls how many past mouse positions",
"are stored for velocity calculations.",
"",
"Higher values:",
" • More accurate trajectory predictions",
" • Smoother movement detection",
" • Slightly increased processing overhead",
"",
"Lower values:",
" • Faster response to direction changes",
" • Less memory usage",
" • May be less accurate for fast movements",
"",
"Property: positionHistorySize",
].join("\n"), "\">i</span>\n </label>\n <input type=\"range\" id=\"history-size\" min=\"").concat(MIN_POSITION_HISTORY_SIZE, "\" max=\"").concat(MAX_POSITION_HISTORY_SIZE, "\">\n <span id=\"history-value\"></span>\n </div>\n <div class=\"control-row\">\n <label for=\"prediction-time\">\n Prediction Time\n <span class=\"info-icon\" title=\"").concat([
"Trajectory Prediction Time",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"How far into the future (in ".concat(TRAJECTORY_PREDICTION_TIME_UNIT, ")"),
"to calculate the mouse trajectory path.",
"",
"Larger values:",
" • Elements are detected sooner",
" • More time for preloading/preparation",
" • May trigger false positives for curved paths",
"",
"Smaller values:",
" • More precise targeting",
" • Reduced false positive rate",
" • Less time for preparation",
"",
"Property: trajectoryPredictionTime",
].join("\n"), "\">i</span>\n </label>\n <input type=\"range\" id=\"prediction-time\" min=\"").concat(MIN_TRAJECTORY_PREDICTION_TIME, "\" max=\"").concat(MAX_TRAJECTORY_PREDICTION_TIME, "\" step=\"10\">\n <span id=\"prediction-value\"></span>\n </div>\n </div>\n </div>\n\n <div class=\"debugger-section keyboard-settings-section\">\n <div class=\"debugger-section-header collapsible\">\n <h3>Keyboard Settings</h3>\n <button class=\"section-minimize-button\">-</button>\n </div>\n <div class=\"debugger-section-content keyboard-settings-content\">\n <div class=\"control-row\">\n <label for=\"tab-enabled\">\n Enable Tab Prediction\n <span class=\"info-icon\" title=\"").concat([
"Tab Navigation Prediction",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"When enabled: Callbacks are executed when",
"the user is ".concat(this.foresightManagerInstance.getManagerData.globalSettings.tabOffset, " (tabOffset) ").concat(TAB_OFFSET_UNIT, " away from"),
"a registered element during tab navigation.",
"",
"(works with Shift+Tab too).",
"",
"Property: enableTabPrediction",
].join("\n"), "\">i</span>\n </label>\n <input type=\"checkbox\" id=\"tab-enabled\">\n </div>\n <div class=\"control-row\">\n <label for=\"tab-offset\">\n Tab Offset\n <span class=\"info-icon\" title=\"").concat([
"Tab Offset",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Number of tabbable elements to look ahead",
"when predicting tab navigation targets.",
"",
"How it works:",
" • Tracks the current focused element",
" • Looks ahead by the specified offset",
" • Triggers callbacks for registered elements",
" within that range",
"",
"Property: tabOffset",
].join("\n"), "\">i</span>\n </label>\n <input type=\"range\" id=\"tab-offset\" min=\"").concat(MIN_TAB_OFFSET, "\" max=\"").concat(MAX_TAB_OFFSET, "\" step=\"1\">\n <span id=\"tab-offset-value\"></span>\n </div>\n </div>\n </div>\n\n <div class=\"debugger-section scroll-settings-section\">\n <div class=\"debugger-section-header collapsible\">\n <h3>Scroll Settings</h3>\n <button class=\"section-minimize-button\">-</button>\n </div>\n <div class=\"debugger-section-content scroll-settings-content\">\n <div class=\"control-row\">\n <label for=\"scroll-enabled\">\n Enable Scroll Prediction\n <span class=\"info-icon\" title=\"").concat([
"Scroll Prediction",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Enables predictive scrolling based on mouse",
"position and scroll direction.",
"",
"When enabled, calculates scroll direction from",
"mouse movement and triggers callbacks for",
"elements that intersect the predicted path.",
"",
"Property: enableScrollPrediction",
].join("\n"), "\">i</span>\n </label>\n <input type=\"checkbox\" id=\"scroll-enabled\">\n </div>\n <div class=\"control-row\">\n <label for=\"scroll-margin\">\n Scroll Margin\n <span class=\"info-icon\" title=\"").concat([
"Scroll Margin",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Sets the pixel distance to check from the",
"mouse position in the scroll direction.",
"",
"Higher values check further ahead, allowing",
"earlier detection of elements that will come",
"into view during scrolling.",
"",
"Property: scrollMargin",
].join("\n"), "\">i</span>\n </label>\n <input type=\"range\" id=\"scroll-margin\" min=\"").concat(MIN_SCROLL_MARGIN, "\" max=\"").concat(MAX_SCROLL_MARGIN, "\" step=\"10\">\n <span id=\"scroll-margin-value\"></span>\n </div>\n </div>\n\n <div class=\"debugger-section general-settings-section\">\n <div class=\"debugger-section-header collapsible\">\n <h3>General Settings</h3>\n <button class=\"section-minimize-button\">-</button>\n </div>\n <div class=\"debugger-section-content general-settings-content\">\n <div class=\"control-row\">\n <label for=\"toggle-name-tags\">\n Show Name Tags\n <span class=\"info-icon\" title=\"").concat([
"Visual Debug Name Tags",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"When enabled: Displays name tags over",
"each registered element in debug mode.",
"",
"Property: debuggerSettings.showNameTags",
].join("\n"), "\">i</span>\n </label>\n <input type=\"checkbox\" id=\"toggle-name-tags\">\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"debugger-section debugger-elements\">\n <div class=\"debugger-section-header elements-list-header\">\n <h3>Elements <span id=\"element-count\"></span> <span id=\"callback-count\"></span></h3>\n <div class=\"header-controls\">\n <div class=\"sort-control-container\">\n <button class=\"sort-button\" title=\"Change element list sort order\">\n ").concat(SORT_SVG_ICON, "\n </button>\n <div id=\"sort-options-popup\">\n <button\n data-sort=\"visibility\"\n title=\"").concat([
"Sort by Visibility",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Sorts elements by their viewport visibility",
"(visible elements first), with a secondary",
"sort by their order in the document.",
"",
"Property: debuggerSettings.sortElementList",
"Value: 'visibility'",
].join("\n"), "\">\n Visibility\n </button>\n <button\n data-sort=\"documentOrder\"\n title=\"").concat([
"Sort by Document Order",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Sorts elements based on their order of",
"appearance in the document's structure",
"(matching the HTML source).",
"",
"Property: debuggerSettings.sortElementList",
"Value: 'documentOrder'",
].join("\n"), "\"\n >\n Document Order\n </button>\n <button\n data-sort=\"insertionOrder\"\n title=\"").concat([
"Sort by Insertion Order",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
"Sorts elements based on the order they",
"were registered with the ForesightManager.",
"",
"Property: debuggerSettings.sortElementList",
"Value: 'insertionOrder'",
].join("\n"), "\"\n >\n Insertion Order\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"debugger-section-content element-list\">\n <div id=\"element-list-items-container\">\n </div>\n </div>\n </div>\n ");
return container;
};
DebuggerControlPanel.prototype.getStyles = function () {
var elementItemHeight = 35; // px
var elementListGap = 3; // px
var elementListItemsContainerPadding = 6; // px
var numRowsToShow = 6;
var numItemsPerRow = 1;
var rowsContentHeight = elementItemHeight * numRowsToShow + elementListGap * (numRowsToShow - 1);
var elementListContainerHeight = rowsContentHeight + elementListItemsContainerPadding * 2;
return /* css */ "\n #debug-controls {\n position: fixed; bottom: 10px; right: 10px;\n background-color: rgba(0, 0, 0, 0.90); color: white; padding: 12px;\n border-radius: 5px; font-family: Arial, sans-serif; font-size: 13px;\n z-index: 10001; pointer-events: auto; display: flex; flex-direction: column; gap: 8px;\n width: 400px;\n transition: width 0.3s ease, height 0.3s ease;\n }\n #debug-controls.minimized {\n width: 250px;\n overflow: hidden;\n padding: 12px 0; \n }\n #debug-controls.minimized .debugger-title-container {\n padding-left: 10px; \n padding-right: 10px;\n gap: 10px; \n }\n #debug-controls.minimized .debugger-title-container h2 {\n display: inline;\n font-size: 14px;\n margin: 0;\n white-space: nowrap;\n }\n #debug-controls.minimized .info-icon {\n display: none;\n }\n\n #element-count,#callback-count {\n font-size: 12px;\n color: #9e9e9e;\n }\n\n .debugger-title-container {\n display: flex;\n align-items: center;\n justify-content: space-between; \n padding: 0 0px; \n }\n .title-group { \n display: flex;\n align-items: center;\n gap: 8px; \n\n }\n .minimize-button {\n background: none; border: none; color: white;\n font-size: 22px; cursor: pointer;\n line-height: 1;\n padding-inline: 0px;\n }\n .debugger-title-container h2 { margin: 0; font-size: 15px; }\n\n .copy-settings-button {\n background: none; border: none; color: white;\n cursor: pointer; padding: 0;\n display: flex; align-items: center; justify-content: center;\n }\n\n .copy-settings-button svg {\n width: 16px; height: 16px;\n stroke: white;\n }\n\n .minimized-element-count {\n font-size: 14px;\n min-width: 30px;\n text-align: right;\n }\n\n .all-settings-sections-container {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n\n .debugger-section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 5px;\n margin-bottom: 2px;\n padding-bottom: 2px;\n border-bottom: 1px solid #444;\n }\n .debugger-section-header.collapsible {\n cursor: pointer;\n }\n .debugger-section-header h3 {\n margin: 0;\n font-size: 14px;\n font-weight: bold;\n color: #b0c4de;\n flex-grow: 1;\n }\n\n .section-minimize-button {\n background: none;\n border: none;\n color: white;\n font-size: 18px;\n cursor: pointer;\n padding: 0px;\n line-height: 1;\n }\n\n #debug-controls .control-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n #debug-controls label {\n display: flex;\n align-items: center;\n gap: 5px;\n cursor: pointer;\n }\n #debug-controls .control-row:has(input[type=\"checkbox\"]) label {\n flex-grow: 1;\n }\n #debug-controls .control-row input[type=\"checkbox\"] {\n appearance: none; -webkit-appearance: none; -moz-appearance: none;\n position: relative; width: 40px; height: 18px;\n background-color: #555; border-radius: 10px; cursor: pointer;\n outline: none; transition: background-color 0.2s ease;\n vertical-align: middle; flex-shrink: 0; margin: 0;\n }\n #debug-controls .control-row input[type=\"checkbox\"]::before {\n content: \"\"; position: absolute; width: 14px; height: 14px;\n border-radius: 50%; background-color: white; top: 2px; left: 2px;\n transition: transform 0.2s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.4);\n }\n #debug-controls .control-row input[type=\"checkbox\"]:checked {\n background-color: #b0c4de;\n }\n #debug-controls .control-row input[type=\"checkbox\"]:checked::before {\n transform: translateX(22px);\n }\n #debug-controls .control-row:has(input[type=\"range\"]) label {\n flex-basis: 170px; flex-shrink: 0;\n }\n #debug-controls input[type=\"range\"] {\n flex-grow: 1; margin: 0; curso