UNPKG

crunchit

Version:

Autotrack the events from your users

216 lines (192 loc) 6.49 kB
const state = { hoveredElement: null, selectedElement: null, hoveredLines: [], selectedLines: [], successfulSelections: [], }; const config = { hoveredColorDark: "mediumpurple", selectedColorDark: "darkorange", selectedColorLight: "rgba(255, 165, 0, 0.1)", lineThickness: 3, zIndex: "999999999", boxShadow: "1px 1px 3px rgba(0, 0, 0, 0.2)", ignoreElementIds: ["CRUNCH_IFRAME_CONTAINER", "CRUNCH_NAVBAR_CONTAINER"], }; function clearPreviousSelection() { if (state.selectedElement) { state.selectedElement.style.background = ""; } state.selectedLines.forEach((line) => { if (line.parentNode) { line.parentNode.removeChild(line); } }); state.selectedLines.length = 0; } function unhighlightSelectedElement() { clearPreviousSelection(); state.selectedElement = null; } function isInsideIgnoredElement(element) { let parent = element; while (parent) { if (config.ignoreElementIds.includes(parent.id)) { return true; } parent = parent.parentElement; } return false; } function createLine(color) { const line = document.createElement("div"); line.style.position = "absolute"; line.style.backgroundColor = color; line.style.zIndex = config.zIndex; line.style.boxShadow = config.boxShadow; return line; } function updateLinePositions(target, color, lineArray) { const rect = target.getBoundingClientRect(); if (rect.height < 4 || rect.width < 4) { return; } lineArray.forEach((line) => { if (line.parentNode) { line.parentNode.removeChild(line); } }); lineArray.length = 0; let actualColor = rect.height < 12 || rect.width < 12 ? "orangered" : color; ["top", "bottom", "left", "right"].forEach((position) => { const line = createLine(actualColor); if (position === "top" || position === "bottom") { line.style.height = `${config.lineThickness}px`; line.style.left = `${rect.left}px`; line.style.width = position === "bottom" ? `${rect.width + 3}px` : `${rect.width}px`; line.style.top = position === "top" ? `${rect.top}px` : `${rect.bottom}px`; } else { line.style.width = `${config.lineThickness}px`; line.style.top = `${rect.top}px`; line.style.height = `${rect.height}px`; line.style.left = position === "left" ? `${rect.left}px` : `${rect.right}px`; } document.body.appendChild(line); lineArray.push(line); }); } function highlightHoveredElement(event) { if (isInsideIgnoredElement(event.target) || state.selectedElement) return; state.hoveredElement = event.target; updateLinePositions( state.hoveredElement, config.hoveredColorDark, state.hoveredLines ); } function highlightSelectedElement(event) { if (isInsideIgnoredElement(event.target)) return; event.preventDefault(); event.stopImmediatePropagation(); clearPreviousSelection(); let selection = event.target; selection.style.background = config.selectedColorLight; state.selectedElement = selection; updateLinePositions( state.selectedElement, config.selectedColorDark, state.selectedLines ); } function activateHighlighter() { unhighlightSelectedElement(); document.addEventListener("mouseover", highlightHoveredElement); document.addEventListener("click", highlightSelectedElement, true); } function deactivateHighlighter() { document.removeEventListener("mouseover", highlightHoveredElement); document.removeEventListener("click", highlightSelectedElement, true); // Optional: Clear any existing highlights clearPreviousSelection(); state.hoveredLines.forEach((line) => { if (line.parentNode) { line.parentNode.removeChild(line); } }); state.hoveredLines.length = 0; state.selectedLines.length = 0; state.hoveredElement = null; state.selectedElement = null; } function successfulSelection(details) { if (state.selectedElement && state.selectedLines.length > 0) { const rect = state.selectedElement.getBoundingClientRect(); const successfulLines = state.selectedLines.map((line, index) => { const newLine = line.cloneNode(); newLine.style.backgroundColor = "lawngreen"; newLine.style.position = "absolute"; // Relative to the parent selectedElement switch (index) { case 0: newLine.style.top = "0px"; // Relative to the parent selectedElement newLine.style.left = "0px"; // Relative to the parent selectedElement break; case 1: newLine.style.top = `${rect.height}px`; // Relative to the parent selectedElement newLine.style.left = "0px"; // Relative to the parent selectedElement break; case 2: newLine.style.top = "0px"; // Relative to the parent selectedElement newLine.style.left = "0px"; // Relative to the parent selectedElement break; case 3: newLine.style.top = "0px"; // Relative to the parent selectedElement newLine.style.left = `${rect.width}px`; // Relative to the parent selectedElement break; default: break; } return newLine; }); // Add the successful selection to the state state.successfulSelections.push({ element: state.selectedElement, lines: successfulLines, }); // Make the selected element's position relative so the lines will be relative to it state.selectedElement.style.position = "relative"; // Append the lines to the selected element successfulLines.forEach((line) => { state.selectedElement.appendChild(line); }); let successfulSelectionObject = { ...details, successfulLines }; let foundEntryIndex; let foundEntry = state.successfulSelections.find((singleEntry, index) => { if (singleEntry.xpath == details.xpath) { foundEntryIndex = index; return true; } return false; }); if (foundEntry) { let localSuccess = [...state.successfulSelections]; localSuccess[foundEntryIndex] = successfulSelectionObject; state.successfulSelections = localSuccess; } else state.successfulSelections.push(successfulSelectionObject); } } const isAlreadySelected = (xpath) => { return state.successfulSelections.find((singleSelection) => { return singleSelection.xpath == xpath; }); }; module.exports = { activateHighlighter, unhighlightSelectedElement, deactivateHighlighter, successfulSelection, isAlreadySelected, };