UNPKG

creatr-devtools

Version:

Mini-Sentry + DOM Inspector toolkit for any React/Next app

1,211 lines (1,203 loc) 63.3 kB
"use strict"; "use client"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/inspector/DOMInspector.tsx var DOMInspector_exports = {}; __export(DOMInspector_exports, { DOMInspector: () => DOMInspector }); var import_react3, import_jsx_runtime2, DOMInspector; var init_DOMInspector = __esm({ "src/inspector/DOMInspector.tsx"() { "use strict"; "use client"; import_react3 = require("react"); import_jsx_runtime2 = require("react/jsx-runtime"); DOMInspector = ({ children, highlightColor = "#4CAF50", highlightWidth = 2 }) => { const [isInspectorActive, setIsInspectorActive] = (0, import_react3.useState)(false); const [isImageInspectorActive, setIsImageInspectorActive] = (0, import_react3.useState)(false); const [isEditorActive, setIsEditorActive] = (0, import_react3.useState)(false); const hoverHighlightRef = (0, import_react3.useRef)(null); const activeHighlightRef = (0, import_react3.useRef)(null); const selectedHighlightsRef = (0, import_react3.useRef)([]); const containerRef = (0, import_react3.useRef)(null); const hoveredElementRef = (0, import_react3.useRef)(null); const activeElementRef = (0, import_react3.useRef)(null); const elementObserversRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map()); const elementVisibilityRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map()); const [tooltipPosition, setTooltipPosition] = (0, import_react3.useState)({ x: 0, y: 0 }); const [hoveredMetadata, setHoveredMetadata] = (0, import_react3.useState)(null); const [loadingImages, setLoadingImages] = (0, import_react3.useState)( /* @__PURE__ */ new Set() ); function checkElementOcclusion(element) { const rect = element.getBoundingClientRect(); const isInViewport = rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0; if (!isInViewport) { return { element, isVisible: false, visibilityRatio: 0, occludingElements: [] }; } const occludingElements = []; const elementZIndex = parseInt(window.getComputedStyle(element).zIndex) || 0; const elementPosition = window.getComputedStyle(element).position; const potentialOccluders = Array.from(document.elementsFromPoint( rect.left + rect.width / 2, rect.top + rect.height / 2 )); for (const potential of potentialOccluders) { if (potential === element || element.contains(potential) || potential.contains(element)) { continue; } const potentialRect = potential.getBoundingClientRect(); const isIntersecting = !(rect.right < potentialRect.left || rect.left > potentialRect.right || rect.bottom < potentialRect.top || rect.top > potentialRect.bottom); if (isIntersecting) { const potentialZIndex = parseInt(window.getComputedStyle(potential).zIndex) || 0; const potentialPosition = window.getComputedStyle(potential).position; const isOccluding = ( // Higher z-index and positioned potentialZIndex > elementZIndex && (potentialPosition === "absolute" || potentialPosition === "relative" || potentialPosition === "fixed" || potentialPosition === "sticky") || // Or appears later in DOM when z-index is the same potentialZIndex === elementZIndex && potential.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING ); if (isOccluding) { occludingElements.push(potential); } } } let visibleArea = rect.width * rect.height; let totalArea = visibleArea; for (const occluder of occludingElements) { const occluderRect = occluder.getBoundingClientRect(); const xOverlap = Math.max( 0, Math.min(rect.right, occluderRect.right) - Math.max(rect.left, occluderRect.left) ); const yOverlap = Math.max( 0, Math.min(rect.bottom, occluderRect.bottom) - Math.max(rect.top, occluderRect.top) ); const overlapArea = xOverlap * yOverlap; visibleArea -= overlapArea; } visibleArea = Math.max(0, visibleArea); const visibilityRatio = totalArea > 0 ? visibleArea / totalArea : 0; return { element, isVisible: visibilityRatio > 0.05, // Consider visible if at least 5% is showing visibilityRatio, occludingElements }; } function observeElementVisibility(element) { if (elementObserversRef.current.has(element)) { return; } const initialVisibility = checkElementOcclusion(element); elementVisibilityRef.current.set(element, initialVisibility); const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.target === element) { const visibilityInfo = checkElementOcclusion(element); elementVisibilityRef.current.set(element, visibilityInfo); updateHighlightForElement(element); } } }, { threshold: [0, 0.1, 0.5, 1] } ); observer.observe(element); elementObserversRef.current.set(element, observer); } function unobserveElementVisibility(element) { const observer = elementObserversRef.current.get(element); if (observer) { observer.disconnect(); elementObserversRef.current.delete(element); elementVisibilityRef.current.delete(element); } } function positionHighlightBox(highlightEl, element) { if (!highlightEl || !element || !containerRef.current) { if (highlightEl) highlightEl.style.display = "none"; return; } const visibilityInfo = elementVisibilityRef.current.get(element) || checkElementOcclusion(element); if (!visibilityInfo.isVisible) { highlightEl.style.display = "none"; return; } const containerRect = containerRef.current.getBoundingClientRect(); const elemRect = element.getBoundingClientRect(); const offsetLeft = elemRect.left - containerRect.left + containerRef.current.scrollLeft; const offsetTop = elemRect.top - containerRect.top + containerRef.current.scrollTop; highlightEl.style.display = "block"; highlightEl.style.left = `${offsetLeft}px`; highlightEl.style.top = `${offsetTop}px`; highlightEl.style.width = `${elemRect.width}px`; highlightEl.style.height = `${elemRect.height}px`; highlightEl.style.boxSizing = "border-box"; const opacity = 0.1 * visibilityInfo.visibilityRatio; highlightEl.style.backgroundColor = highlightEl === activeHighlightRef.current ? `rgba(76, 175, 80, ${opacity})` : `rgba(33, 150, 243, ${opacity})`; if (visibilityInfo.occludingElements.length > 0 && false) { let clipPath = "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)"; highlightEl.style.clipPath = clipPath; } } function updateHighlightForElement(element) { if (activeElementRef.current === element && activeHighlightRef.current) { positionHighlightBox(activeHighlightRef.current, element); } if (hoveredElementRef.current === element && hoverHighlightRef.current) { positionHighlightBox(hoverHighlightRef.current, element); } selectedHighlightsRef.current.forEach((highlight) => { if (highlight["targetElementRef"] === element) { positionHighlightBox(highlight, element); } }); } function createHighlight(element) { const highlight = document.createElement("div"); highlight.style.position = "absolute"; highlight.style.pointerEvents = "none"; highlight.style.border = `${highlightWidth}px solid ${highlightColor}`; highlight.style.backgroundColor = "rgba(76, 175, 80, 0.1)"; highlight.style.zIndex = "999999"; highlight.style.boxSizing = "border-box"; highlight.dataset.targetElement = element.outerHTML; highlight["targetElementRef"] = element; if (element.dataset.uniqueId) { highlight["targetElementUniqueId"] = element.dataset.uniqueId; } containerRef.current?.appendChild(highlight); observeElementVisibility(element); positionHighlightBox(highlight, element); return highlight; } function handlePositionUpdates() { const elementsToCheck = /* @__PURE__ */ new Set(); selectedHighlightsRef.current.forEach((highlight) => { const element = highlight["targetElementRef"]; if (element) { elementsToCheck.add(element); } }); if (activeElementRef.current) { elementsToCheck.add(activeElementRef.current); } if (hoveredElementRef.current) { elementsToCheck.add(hoveredElementRef.current); } elementsToCheck.forEach((element) => { const visibilityInfo = checkElementOcclusion(element); elementVisibilityRef.current.set(element, visibilityInfo); }); selectedHighlightsRef.current.forEach((highlight) => { const element = highlight["targetElementRef"]; if (element) { positionHighlightBox(highlight, element); } }); if (activeElementRef.current && activeHighlightRef.current) { positionHighlightBox(activeHighlightRef.current, activeElementRef.current); } if (hoveredElementRef.current && hoverHighlightRef.current) { positionHighlightBox(hoverHighlightRef.current, hoveredElementRef.current); } } function clearHighlights() { console.log("Clearing all highlights"); console.log( "Before clear - selected highlights:", selectedHighlightsRef.current.length ); selectedHighlightsRef.current.forEach((highlight) => { const element = highlight["targetElementRef"]; if (element) { unobserveElementVisibility(element); } }); if (hoveredElementRef.current) { unobserveElementVisibility(hoveredElementRef.current); } if (activeElementRef.current) { unobserveElementVisibility(activeElementRef.current); } hoveredElementRef.current = null; activeElementRef.current = null; if (activeHighlightRef.current) { activeHighlightRef.current.style.display = "none"; } if (hoverHighlightRef.current) { hoverHighlightRef.current.style.display = "none"; } selectedHighlightsRef.current.forEach((highlight) => { if (highlight["resizeObserver"]) { highlight["resizeObserver"].disconnect(); } if (highlight.parentNode) { highlight.parentNode.removeChild(highlight); } }); selectedHighlightsRef.current = []; console.log( "After clear - selected highlights:", selectedHighlightsRef.current.length ); } (0, import_react3.useEffect)(() => { window.parent.postMessage( { type: "URL_CHANGE", url: window.location.href }, "*" ); const handleNavigation = () => { window.parent.postMessage( { type: "URL_CHANGE", url: window.location.href }, "*" ); }; window.addEventListener("popstate", handleNavigation); return () => { window.removeEventListener("popstate", handleNavigation); }; }, []); (0, import_react3.useEffect)(() => { const handleResize = handlePositionUpdates; const handleScroll = handlePositionUpdates; window.addEventListener("resize", handleResize); window.addEventListener("scroll", handleScroll, true); if (containerRef.current) { containerRef.current.addEventListener("scroll", handleScroll, true); } let parent = containerRef.current?.parentElement; while (parent) { const overflowY = window.getComputedStyle(parent).overflowY; const overflowX = window.getComputedStyle(parent).overflowX; if (overflowY === "auto" || overflowY === "scroll" || overflowX === "auto" || overflowX === "scroll") { parent.addEventListener("scroll", handleScroll, true); } parent = parent.parentElement; } const resizeObserver = new ResizeObserver((entries) => { handlePositionUpdates(); }); selectedHighlightsRef.current.forEach((highlight) => { const element = highlight["targetElementRef"]; if (element) { resizeObserver.observe(element); highlight["resizeObserver"] = resizeObserver; } }); const mutationObserver = new MutationObserver(() => { handlePositionUpdates(); }); mutationObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["style", "class"] }); return () => { window.removeEventListener("resize", handleResize); window.removeEventListener("scroll", handleScroll, true); if (containerRef.current) { containerRef.current.removeEventListener("scroll", handleScroll, true); } let parent2 = containerRef.current?.parentElement; while (parent2) { const overflowY = window.getComputedStyle(parent2).overflowY; const overflowX = window.getComputedStyle(parent2).overflowX; if (overflowY === "auto" || overflowY === "scroll" || overflowX === "auto" || overflowX === "scroll") { parent2.removeEventListener("scroll", handleScroll, true); } parent2 = parent2.parentElement; } resizeObserver.disconnect(); mutationObserver.disconnect(); elementObserversRef.current.forEach((observer) => { observer.disconnect(); }); elementObserversRef.current.clear(); elementVisibilityRef.current.clear(); }; }, [selectedHighlightsRef.current, activeElementRef.current, hoveredElementRef.current]); (0, import_react3.useEffect)(() => { let styleEl = null; if (isImageInspectorActive) { styleEl = document.createElement("style"); styleEl.id = "only-images-clickable"; styleEl.textContent = ` /* Disable pointer events on everything... */ * { pointer-events: none !important; } /* ...re-enable for <img> and <video> */ img, video { pointer-events: auto !important; } `; document.head.appendChild(styleEl); } return () => { if (styleEl && styleEl.parentNode) { styleEl.parentNode.removeChild(styleEl); } }; }, [isImageInspectorActive]); function getElementMetadata(element) { const uniqueId = element.dataset.uniqueId || ""; let duplicateCount = 1; if (uniqueId) { duplicateCount = document.querySelectorAll(`[data-unique-id="${uniqueId}"]`).length; } const hasDynamicText = element.dataset.dynamicText === "true"; return { tagName: element.tagName.toLowerCase(), classNames: typeof element.className === "string" ? element.className : element.className && element.className.baseVal || "", elementId: element.id || null, textContent: element.textContent, boundingBox: element.getBoundingClientRect(), attributes: Array.from(element.attributes).map((attr) => ({ name: attr.name, value: attr.value })), uniqueId, duplicateCount, hasDynamicText }; } (0, import_react3.useEffect)(() => { const handleMessage = (event) => { const { type, enabled, targetId } = event.data; console.log("BROOO", type, targetId); function isTextOnly(el) { return el.childNodes.length === 1 && el.childNodes[0].nodeType === Node.TEXT_NODE; } if (type === "GET_ELEMENT_STYLES") { const targetElements = document.querySelectorAll(`[data-unique-id="${targetId}"]`); console.log("GET_ELEMENT_STYLES", targetId); if (targetElements.length > 0) { const targetElement = targetElements[0]; const computedStyle = window.getComputedStyle(targetElement); const hasDirectText = Array.from(targetElement.childNodes).some( (node) => node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0 ); const isElementTextOnly = isTextOnly(targetElement); const hasDynamicText = targetElement.dataset.dynamicText === "true"; console.log("HAS DYNAMIC TEXT", hasDynamicText); const isDynamicallyRendered = targetElements.length > 1 || hasDynamicText; const computedStyles = { // Padding paddingTop: computedStyle.paddingTop, paddingRight: computedStyle.paddingRight, paddingBottom: computedStyle.paddingBottom, paddingLeft: computedStyle.paddingLeft, // Margin margin: computedStyle.margin, marginTop: computedStyle.marginTop, marginRight: computedStyle.marginRight, marginBottom: computedStyle.marginBottom, marginLeft: computedStyle.marginLeft, // Dimensions width: computedStyle.width, height: computedStyle.height, // Colors and appearance backgroundColor: computedStyle.backgroundColor, color: computedStyle.color, borderRadius: computedStyle.borderRadius, // Border properties borderWidth: computedStyle.borderWidth, borderStyle: computedStyle.borderStyle, borderColor: computedStyle.borderColor, // Text properties fontSize: computedStyle.fontSize, fontWeight: computedStyle.fontWeight }; const textContent = targetElement.textContent; console.log("GET_ELEMENT_STYLES", { computedStyles, tailwindClasses: targetElement.className.split(" "), isDynamicallyRendered, duplicateCount: targetElements.length, hasDirectText, isTextOnly: isElementTextOnly, hasDynamicText }); window.parent.postMessage({ type: "ELEMENT_STYLES_RETRIEVED", targetId, metadata: { computedStyles, tailwindClasses: targetElement.className.split(" "), isDynamicallyRendered, duplicateCount: targetElements.length, hasDirectText, isTextOnly: isElementTextOnly, hasDynamicText } }, "*"); } } if (type === "GET_ELEMENT_TEXT") { const targetElement = document.querySelector(`[data-unique-id="${targetId}"]`); console.log("GET_ELEMENT_TEXT", targetId); console.log("GET_ELEMENT_TEXT", { textContent: targetElement.textContent ?? null, targetId }); window.parent.postMessage({ type: "ELEMENT_TEXT_RETRIEVED", targetId, metadata: { textContent: targetElement.textContent ?? null, targetId } }, "*"); } if (type === "UPDATE_ELEMENT_STYLES") { const targetElements = document.querySelectorAll(`[data-unique-id="${targetId}"]`); console.log("UPDATE_ELEMENT_STYLES", targetId, event.data.styles); if (targetElements.length > 0) { const keys = Object.keys(event.data.styles); targetElements.forEach((element) => { for (const key of keys) { element.style[key] = event.data.styles[key]; } }); handlePositionUpdates(); } else { console.error(`No elements with data-unique-id="${targetId}" found`); } } if (type === "UPDATE_ELEMENT_TEXT") { const targetElements = document.querySelectorAll(`[data-unique-id="${targetId}"]`); console.log("UPDATE_ELEMENT_TEXT", targetId, event.data.text); if (targetElements.length > 0) { targetElements.forEach((element) => { element.textContent = event.data.text; }); handlePositionUpdates(); } else { console.error(`No elements with data-unique-id="${targetId}" found for text update`); } } if (type === "DELETE_ELEMENT") { const targetElements = document.querySelectorAll(`[data-unique-id="${targetId}"]`); console.log("DELETE_ELEMENT", targetId); if (targetElements.length > 0) { targetElements.forEach((element) => { element.parentNode.removeChild(element); }); clearHighlights(); } else { console.error(`No elements with data-unique-id="${targetId}" found for deletion`); } } if (type === "TOGGLE_EDITOR") { setIsEditorActive(!!enabled); if (!enabled) clearHighlights(); if (containerRef.current) { containerRef.current.style.cursor = enabled ? "crosshair" : ""; } } if (type === "TOGGLE_INSPECTOR") { setIsInspectorActive(!!enabled); if (enabled) setIsImageInspectorActive(false); if (!enabled) clearHighlights(); if (containerRef.current) { containerRef.current.style.cursor = enabled ? "crosshair" : ""; } } else if (type === "TOGGLE_IMAGE_INSPECTOR") { setIsImageInspectorActive(!!enabled); if (enabled) setIsInspectorActive(false); if (!enabled) clearHighlights(); if (containerRef.current) { containerRef.current.style.cursor = enabled ? "crosshair" : ""; } } else if (type === "UPDATE_IMAGE_URL") { const { url } = event.data; if (activeElementRef.current && url) { const activeEl = activeElementRef.current; const currentTagName = activeEl.tagName.toLowerCase(); const isVideoUrl = url.match(/\.(mp4|webm|ogg)$/i); const shouldBeVideo = isVideoUrl !== null; try { if (shouldBeVideo && currentTagName === "img") { const videoEl = document.createElement("video"); videoEl.autoplay = true; videoEl.loop = true; videoEl.muted = true; videoEl.playsInline = true; Array.from(activeEl.attributes).forEach((attr) => { if (attr.name !== "src" && attr.name !== "alt") { videoEl.setAttribute(attr.name, attr.value); } }); const styles = window.getComputedStyle(activeEl); Array.from(styles).forEach((key) => { try { videoEl.style[key] = styles.getPropertyValue(key); } catch (e) { } }); videoEl.src = url; if (activeEl.parentNode) { activeEl.parentNode.replaceChild(videoEl, activeEl); activeElementRef.current = videoEl; positionHighlightBox(activeHighlightRef.current, videoEl); selectedHighlightsRef.current.forEach((highlight) => { if (highlight.dataset.targetElement === activeEl.outerHTML) { positionHighlightBox(highlight, videoEl); highlight.dataset.targetElement = videoEl.outerHTML; } }); } } else if (!shouldBeVideo && currentTagName === "video") { const imgEl = document.createElement("img"); Array.from(activeEl.attributes).forEach((attr) => { if (attr.name !== "src" && !attr.name.startsWith("autoplay")) { imgEl.setAttribute(attr.name, attr.value); } }); const styles = window.getComputedStyle(activeEl); Array.from(styles).forEach((key) => { try { imgEl.style[key] = styles.getPropertyValue(key); } catch (e) { } }); imgEl.src = url; if (activeEl.parentNode) { activeEl.parentNode.replaceChild(imgEl, activeEl); activeElementRef.current = imgEl; positionHighlightBox(activeHighlightRef.current, imgEl); selectedHighlightsRef.current.forEach((highlight) => { if (highlight.dataset.targetElement === activeEl.outerHTML) { positionHighlightBox(highlight, imgEl); highlight.dataset.targetElement = imgEl.outerHTML; } }); } } else { if (currentTagName === "img") { activeEl.src = url; } else if (currentTagName === "video") { activeEl.src = url; } } } catch (error) { console.error("Error during element conversion:", error); } } } }; window.addEventListener("message", handleMessage); return () => { window.removeEventListener("message", handleMessage); }; }, []); (0, import_react3.useEffect)(() => { const container = containerRef.current; if (!container) return; function preventDefault(e) { if (isInspectorActive || isImageInspectorActive || isEditorActive) { e.preventDefault(); } } const handleMouseMove = (e) => { if (!isInspectorActive && !isImageInspectorActive && !isEditorActive) return; const target = e.target; if (isImageInspectorActive && target.tagName.toLowerCase() !== "img" && target.tagName.toLowerCase() !== "video") { hoveredElementRef.current = null; positionHighlightBox(hoverHighlightRef.current, null); setHoveredMetadata(null); return; } if (hoveredElementRef.current !== target) { if (target && target !== hoveredElementRef.current) { observeElementVisibility(target); } hoveredElementRef.current = target; positionHighlightBox(hoverHighlightRef.current, target); setTooltipPosition({ x: e.pageX + 10, y: e.pageY + 10 }); const id = target.id ? `#${target.id}` : ""; const classes = typeof target.className === "string" && target.className.trim() ? `.${target.className.trim().split(" ").filter(Boolean).join(".")}` : ""; setHoveredMetadata(`<${target.tagName.toLowerCase()}>${id}${classes}`); } }; const handleMouseLeave = () => { hoveredElementRef.current = null; positionHighlightBox(hoverHighlightRef.current, null); setHoveredMetadata(null); }; const handleClick = (e) => { if (!isInspectorActive && !isImageInspectorActive && !isEditorActive) { return; } const targetElement = e.target; e.preventDefault(); e.stopPropagation(); if (isImageInspectorActive) { const tagName = targetElement.tagName.toLowerCase(); if (tagName !== "img" && tagName !== "video") { return; } if (!targetElement.dataset.imageInspectorId) { const uniqueId = `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; targetElement.dataset.imageInspectorId = uniqueId; } } if (isEditorActive) { const uniqueId = targetElement.dataset.uniqueId; const isAlreadySelected2 = uniqueId && selectedHighlightsRef.current.some( (highlight) => highlight["targetElementUniqueId"] === uniqueId ); clearHighlights(); if (isAlreadySelected2) { const msg3 = { type: "ELEMENT_INSPECTED", metadata: getElementMetadata(targetElement), isShiftPressed: e.shiftKey }; window.parent.postMessage(msg3, "*"); return; } selectedHighlightsRef.current = []; let elementsToHighlight = [targetElement]; if (uniqueId) { const matchingElements = document.querySelectorAll(`[data-unique-id="${uniqueId}"]`); elementsToHighlight = Array.from(matchingElements); } const highlights = elementsToHighlight.map((element) => { observeElementVisibility(element); const highlight = createHighlight(element); highlight["targetElementUniqueId"] = uniqueId; return highlight; }); selectedHighlightsRef.current = highlights; activeElementRef.current = targetElement; const metadata = getElementMetadata(targetElement); const msg2 = { type: "ELEMENT_INSPECTED", metadata, // Already includes duplicateCount from getElementMetadata isShiftPressed: false // Always false in editor mode }; window.parent.postMessage(msg2, "*"); return; } const isAlreadySelected = selectedHighlightsRef.current.some( (highlight) => { const targetRect = targetElement.getBoundingClientRect(); const highlightRect = highlight.getBoundingClientRect(); const heightMatch = Math.abs(highlightRect.height - targetRect.height) < 1; const widthMatch = Math.abs(highlightRect.width - targetRect.width) < 1; const topMatch = Math.abs(highlightRect.top - targetRect.top) < 1; const horizontalOverlap = !(highlightRect.right < targetRect.left || highlightRect.left > targetRect.right); const isMatch = heightMatch && widthMatch && topMatch && horizontalOverlap; return isMatch; } ); if (!e.shiftKey && isAlreadySelected) { clearHighlights(); activeElementRef.current = null; if (!isImageInspectorActive) { const msg2 = { type: "ELEMENT_INSPECTED", metadata: null, isShiftPressed: false }; window.parent.postMessage(msg2, "*"); } return; } if (e.shiftKey) { if (isAlreadySelected) { selectedHighlightsRef.current = selectedHighlightsRef.current.filter( (highlight) => { const highlightRect = highlight.getBoundingClientRect(); const targetRect = targetElement.getBoundingClientRect(); const heightMatch = Math.abs(highlightRect.height - targetRect.height) < 1; const widthMatch = Math.abs(highlightRect.width - targetRect.width) < 1; const topMatch = Math.abs(highlightRect.top - targetRect.top) < 1; const horizontalOverlap = !(highlightRect.right < targetRect.left || highlightRect.left > targetRect.right); const shouldRemove = heightMatch && widthMatch && topMatch && horizontalOverlap; if (shouldRemove) { const element = highlight["targetElementRef"]; if (element) { unobserveElementVisibility(element); } if (highlight["resizeObserver"]) { highlight["resizeObserver"].disconnect(); } if (highlight.parentNode) { highlight.parentNode.removeChild(highlight); } } return !shouldRemove; } ); } else { observeElementVisibility(targetElement); const highlight = createHighlight(targetElement); selectedHighlightsRef.current.push(highlight); const resizeObserver = new ResizeObserver(() => { if (highlight["targetElementRef"]) { positionHighlightBox(highlight, highlight["targetElementRef"]); } }); resizeObserver.observe(targetElement); highlight["resizeObserver"] = resizeObserver; } } else { selectedHighlightsRef.current.forEach((highlight2) => { const element = highlight2["targetElementRef"]; if (element) { unobserveElementVisibility(element); } if (highlight2["resizeObserver"]) { highlight2["resizeObserver"].disconnect(); } if (highlight2.parentNode) { highlight2.parentNode.removeChild(highlight2); } }); selectedHighlightsRef.current = []; observeElementVisibility(targetElement); const highlight = createHighlight(targetElement); selectedHighlightsRef.current = [highlight]; const resizeObserver = new ResizeObserver(() => { if (highlight["targetElementRef"]) { positionHighlightBox(highlight, highlight["targetElementRef"]); } }); resizeObserver.observe(targetElement); highlight["resizeObserver"] = resizeObserver; } activeElementRef.current = targetElement; const msg = { type: "ELEMENT_INSPECTED", metadata: getElementMetadata(targetElement), isShiftPressed: e.shiftKey }; window.parent.postMessage(msg, "*"); }; container.addEventListener("click", preventDefault, true); container.addEventListener("mousedown", preventDefault, true); container.addEventListener("mouseup", preventDefault, true); container.addEventListener("submit", preventDefault, true); container.addEventListener("mousemove", handleMouseMove); container.addEventListener("mouseleave", handleMouseLeave); document.addEventListener("click", handleClick, true); return () => { container.removeEventListener("click", preventDefault, true); container.removeEventListener("mousedown", preventDefault, true); container.removeEventListener("mouseup", preventDefault, true); container.removeEventListener("submit", preventDefault, true); container.removeEventListener( "mousemove", handleMouseMove ); container.removeEventListener("mouseleave", handleMouseLeave); document.removeEventListener("click", handleClick, true); }; }, [isInspectorActive, isImageInspectorActive, isEditorActive]); return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( "div", { ref: containerRef, className: `relative h-full w-full ${isInspectorActive || isImageInspectorActive || isEditorActive ? "cursor-crosshair" : ""}`, children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "div", { ref: hoverHighlightRef, style: { position: "absolute", display: "none", pointerEvents: "none", border: `${highlightWidth}px dashed #2196F3`, backgroundColor: "rgba(33, 150, 243, 0.1)", // Light blue with 10% opacity zIndex: 999999 } } ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "div", { ref: activeHighlightRef, style: { position: "absolute", display: "none", pointerEvents: "none", border: `${highlightWidth}px solid ${highlightColor}`, backgroundColor: "rgba(76, 175, 80, 0.1)", // Light green with 10% opacity zIndex: 999999 } } ), children ] } ); }; } }); // src/index.tsx var index_exports = {}; __export(index_exports, { DevtoolsProvider: () => DevtoolsProvider, createIframeProxy: () => createIframeProxy, initMiniSentry: () => initMiniSentry }); module.exports = __toCommonJS(index_exports); // src/DevtoolsProvider.tsx var import_react4 = require("react"); // src/minisentry/error-normaliser.ts var import_error_stack_parser = __toESM(require("error-stack-parser")); function normaliseError(err) { const e = err instanceof Error ? err : new Error(String(err)); const frames = import_error_stack_parser.default.parse(e).map((f) => ({ file: f.fileName ?? "<unknown>", lineNumber: f.lineNumber ?? null, column: f.columnNumber ?? null, methodName: f.functionName ?? "<anon>" })); return { name: e.name, message: e.message, stack: e.stack ?? "", frames, route: typeof window !== "undefined" ? window.location.pathname : "<ssr>" // NEW }; } // src/minisentry/snippet-resolver.ts var import_source_map_js = require("source-map-js"); var warned = false; var failedSources = /* @__PURE__ */ new Set(); var originalFetch = typeof window !== "undefined" ? window.fetch.bind(window) : fetch; async function getCodeFrame(frame, ctxLines = 5) { if (!frame.file || frame.lineNumber == null || frame.column == null) return null; let cleanUrl = frame.file; if (cleanUrl.includes("(") && cleanUrl.includes(")")) { const match = cleanUrl.match(/\(([^)]+)\)/); if (match) { cleanUrl = match[1]; } } const mapUrl = `${cleanUrl}.map`; if (failedSources.has(mapUrl)) { return null; } try { const mapResp = await originalFetch(mapUrl); if (!mapResp.ok) { failedSources.add(mapUrl); throw new Error("map 404"); } const raw = await mapResp.json(); const consumer = new import_source_map_js.SourceMapConsumer(raw); const orig = consumer.originalPositionFor({ line: frame.lineNumber, column: frame.column, bias: import_source_map_js.SourceMapConsumer.GREATEST_LOWER_BOUND }); const content = orig.source && consumer.sourceContentFor(orig.source, true) || null; consumer.destroy?.(); if (!content || orig.line == null) return null; const lines = content.split("\n"); const start = Math.max(0, orig.line - 1 - ctxLines); const end = Math.min(lines.length, orig.line + ctxLines); const width = String(end).length; const snippet = lines.slice(start, end).map((ln, idx) => { const realLine = start + idx + 1; const prefix = realLine === orig.line ? ">" : " "; return `${prefix} ${String(realLine).padStart(width)} \u2502 ${ln}`; }).join("\n"); return { file: orig.source, line: orig.line, column: orig.column ?? 0, snippet // ← string instead of array }; } catch (e) { failedSources.add(mapUrl); if (!warned) { console.warn("[creatr-devtools] snippet resolver failed", e); warned = true; } return null; } } // src/minisentry/network-patcher.ts var HANDLED = Symbol("__miniSentryHandled"); var INTERNAL_RE = /mini?sentry\/network-patcher/; var SNIPPET_CTX = 5; var BODY_LIMIT = 32768; function trim(txt) { return txt && txt.length > BODY_LIMIT ? txt.slice(0, BODY_LIMIT) + "\u2026" : txt; } function parseFrames(stack) { return stack.split("\n").map((l) => { const m = /at\s+(.*?)\s+\(?(.+):(\d+):(\d+)\)?/.exec(l); if (!m) return null; return { file: m[2], lineNumber: Number(m[3]), column: Number(m[4]), methodName: m[1] || "<anon>" }; }).filter(Boolean); } async function captureSnippet(stack) { const frames = parseFrames(stack).filter((f) => !INTERNAL_RE.test(f.file)); if (!frames.length) return null; for (const f of frames) { try { const cf = await getCodeFrame(f, SNIPPET_CTX); if (!cf) continue; const { context, line } = cf; const markerIdx = line - (cf.contextStart ?? line) - 1; return context.map((ln, idx) => idx === markerIdx ? `> ${ln}` : ` ${ln}`).join("\n"); } catch { } } return null; } function parseXHRHeaders(raw) { return raw.trim().split(/[\r\n]+/).filter(Boolean).reduce((acc, l) => { const i = l.indexOf(":"); if (i !== -1) acc[l.slice(0, i).trim()] = l.slice(i + 1).trim(); return acc; }, {}); } function patchNetwork(dispatch) { if (typeof window.fetch === "function") { const origFetch = window.fetch.bind(window); window.fetch = async (...args) => { const stackHolder = new Error(); try { const res = await origFetch(...args); if (res.ok) return res; const snippet = await captureSnippet(stackHolder.stack || ""); const cloned = res.clone(); let responseBody = null; try { responseBody = trim(await cloned.text()); } catch { } dispatch({ type: "network-error", url: res.url, method: args[0] instanceof Request ? args[0].method || "GET" : "GET", status: res.status, reason: `${res.status} ${res.statusText}`, route: window.location.pathname, snippet, request: { headers: args[0] instanceof Request ? Object.fromEntries(args[0].headers.entries()) : {}, body: args[0] instanceof Request ? trim(await args[0].clone().text()) : null }, response: { status: res.status, headers: Object.fromEntries(res.headers.entries()), body: responseBody }, buildId: globalThis.__NEXT_DATA__?.buildId ?? "dev", ts: Date.now() }); return res; } catch (err) { err[HANDLED] = true; dispatch({ type: "network-error", url: args[0] instanceof Request ? args[0].url : String(args[0]), method: args[0] instanceof Request ? args[0].method || "GET" : "GET", status: 0, reason: err.message, route: window.location.pathname, snippet: await captureSnippet(stackHolder.stack || ""), request: { headers: args[0] instanceof Request ? Object.fromEntries(args[0].headers.entries()) : {}, body: args[0] instanceof Request ? trim(await args[0].clone().text()) : null }, response: null, buildId: globalThis.__NEXT_DATA__?.buildId ?? "dev", ts: Date.now() }); throw err; } }; } const OrigXHR = window.XMLHttpRequest; window.XMLHttpRequest = function PatchedXHR() { const xhr = new OrigXHR(); let method = "GET"; let url = ""; let reqBody = null; const stackHolder = new Error(); xhr.open = new Proxy(xhr.open, { apply(t, c, args) { method = args[0]; url = args[1]; return Reflect.apply(t, c, args); } }); xhr.send = new Proxy(xhr.send, { apply(t, c, args) { reqBody = typeof args[0] === "string" ? trim(args[0]) : null; return Reflect.apply(t, c, args); } }); xhr.addEventListener("loadend", async () => { if (xhr.status !== 0 && xhr.status < 400) return; dispatch({ type: "network-error", url, method, status: xhr.status, reason: xhr.statusText || "network error", route: window.location.pathname, snippet: await captureSnippet(stackHolder.stack || ""), request: { headers: {}, // cannot read request headers from XHR body: reqBody }, response: { status: xhr.status, headers: parseXHRHeaders(xhr.getAllResponseHeaders()), body: trim(xhr.responseText || null) }, buildId: globalThis.__NEXT_DATA__?.buildId ?? "dev", ts: Date.now() }); }); return xhr; }; } // src/minisentry/index.ts var installed = false; function initMiniSentry(opts = {}) { if (installed || typeof window === "undefined") return; installed = true; const ORIGIN = opts.parentOrigin ?? "*"; const CTX = opts.snippetContext ?? 5; const RATE = opts.rateLimit ?? { max: 10, per: 1e4 }; let count = 0; setInterval(() => count = 0, RATE.per); function dispatch(evt) { if (count >= RATE.max) return; count++; const finalEvt = opts.beforeSend ? opts.beforeSend(evt) : evt; if (finalEvt) window.parent?.postMessage(finalEvt, ORIGIN); } async function handleRuntimeError(err) { if (err && err[HANDLED]) return; const base = normaliseError(err); const candidate = base.frames.find( (f) => f.file && f.lineNumber != null && f.column != null ); let snippet = null; if (candidate) { try { snippet = await getCodeFrame(candidate, CTX); } catch { } } const evt = { type: "runtime-error", buildId: globalThis.__NEXT_DATA__?.buildId ?? "dev", ts: Date.now(), ...base, snippet }; dispatch(evt); } window.addEventListener("error", (e) => handleRuntimeError(e.error || e)); window.addEventListener("unhandledrejection", (e) => { if (e.reason && e.reason[HANDLED]) return; handleRuntimeError(e.reason); }); patchNetwork((netEvt) => dispatch(netEvt)); } // src/branding/BrandingTag.tsx var import_react = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); function Branding() { const [isVisible, setIsVisible] = (0, import_react.useState)(true); (0, import_react.useEffect)(() => { const handleMessage = (event) => { if (event.data.type === "TOGGLE_BRANDING") { setIsVisible(event.data.visible); } }; const isInIframe = window !== window.parent; if (isInIframe) { setIsVisible(false); } window.addEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage); }, []); if (!isVisible) return null; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: ` .creatr-branding-fixed { position: fixed; bottom: 1rem; right: 1rem; display: flex; align-items: center; background: rgba