UNPKG

vite-plugin-react-click-to-component

Version:
180 lines (179 loc) 5.92 kB
// src/client.ts var style = document.createElement("style"); style.setAttribute("type", "text/css"); style.setAttribute("data-vite-dev-id", "react-click-to-component"); style.innerHTML = `[data-click-to-component-target] { outline: auto 1px !important; } #click-to-component-menu { position: fixed !important; z-index: 1000; margin-top: 8px !important; margin-bottom: 8px !important; background: #222 !important; color: white !important; padding: 8px !important; border-radius: 6px !important; font-size: 14px !important; line-height: 1.5 !important; display: flex !important; gap: 2px !important; overflow: auto !important; } .click-to-component-menu-item { padding: 4px !important; border-radius: 4px !important; cursor: pointer !important; display: flex !important; justify-content: space-between !important; gap: 8px !important; } .click-to-component-menu-item:hover { background: #333 !important; } `; document.head.appendChild(style); var root = "__ROOT__"; var currentTarget; var hasMenu = false; var menuElement = document.createElement("div"); menuElement.setAttribute("id", "click-to-component-menu"); window.addEventListener("keyup", (event) => { if (!event.altKey && (hasMenu || currentTarget)) cleanUp(); }); window.addEventListener("mousemove", (event) => { if (!event.altKey) { cleanUp(); return; } if (hasMenu) return; if (!(event.target instanceof HTMLElement)) { clearOverlay(); return; } if (event.target === currentTarget) return; clearOverlay(); currentTarget = event.target; event.target.dataset["clickToComponentTarget"] = "true"; }); var getMaxZIndex = (target, current) => { const parent = target.parentElement; if (!parent || parent === document.body) return current; const zIndex = parseInt(window.getComputedStyle(parent).zIndex); return getMaxZIndex( parent, isNaN(zIndex) ? current : Math.max(zIndex, current) ); }; window.addEventListener("contextmenu", (event) => { if (!event.altKey) return; const target = event.target; if (!(target instanceof HTMLElement)) return; event.preventDefault(); const layers = getLayersForElement(target); if (layers.length === 0) return; const zIndex = getMaxZIndex(target, 999); if (zIndex > 999) menuElement.style.zIndex = `${zIndex + 1}`; const rect = target.getBoundingClientRect(); if (rect.bottom < window.innerHeight / 2) { menuElement.style.top = `${rect.bottom}px`; menuElement.style.bottom = ""; menuElement.style.maxHeight = `${window.innerHeight - rect.bottom - 16}px`; } else if (rect.top > window.innerHeight / 2) { menuElement.style.bottom = `${window.innerHeight - rect.top}px`; menuElement.style.top = ""; menuElement.style.maxHeight = `${rect.top - 16}px`; } else { const bottomVisible = rect.bottom < window.innerHeight; menuElement.style.bottom = `${bottomVisible ? window.innerHeight - rect.bottom : 0}px`; menuElement.style.top = ""; menuElement.style.maxHeight = `${(bottomVisible ? rect.bottom : window.innerHeight) - 16}px`; } if (rect.left < window.innerWidth / 2) { menuElement.style.left = `${rect.left}px`; menuElement.style.right = ""; } else { menuElement.style.right = `${window.innerWidth - rect.right}px`; menuElement.style.left = ""; } while (menuElement.firstChild) { menuElement.removeChild(menuElement.firstChild); } menuElement.style.flexDirection = menuElement.style.top ? "column" : "column-reverse"; for (const layer of layers) { const item = document.createElement("div"); item.className = "click-to-component-menu-item"; const spanL = document.createElement("span"); spanL.textContent = `<${layer.name} />`; item.appendChild(spanL); const spanR = document.createElement("span"); spanR.textContent = layer.path.replace(`${root}/`, ""); item.appendChild(spanR); item.addEventListener("click", () => { void fetch(`/__open-in-editor?file=${encodeURIComponent(layer.path)}`); cleanUp(); }); menuElement.appendChild(item); } if (!hasMenu) { document.body.appendChild(menuElement); hasMenu = true; } }); var cleanUp = () => { clearOverlay(); removeMenu(); }; var clearOverlay = () => { if (!currentTarget) return; const current = document.querySelector( "[data-click-to-component-target]" ); if (current) delete current.dataset["clickToComponentTarget"]; currentTarget = void 0; }; var removeMenu = () => { if (!hasMenu) return; document.body.removeChild(menuElement); hasMenu = false; }; var getLayersForElement = (element) => { let instance = getReactInstanceForElement(element); const layers = []; while (instance) { const path = getPath(instance); if (path) { const name = typeof instance.type === "string" ? instance.type : instance.type.displayName ?? instance.type.name ?? instance.type.render?.name ?? "undefined"; layers.push({ name, path }); } instance = instance._debugOwner; } return layers; }; var getPath = (fiber) => { const source = fiber._debugSource ?? fiber._debugInfo; if (!source) { console.debug("Couldn't find a React instance for the element", fiber); return; } const { columnNumber = 1, fileName, lineNumber = 1 } = source; return `${fileName}:${lineNumber}:${columnNumber}`; }; var getReactInstanceForElement = (element) => { if ("__REACT_DEVTOOLS_GLOBAL_HOOK__" in window) { const { renderers } = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; for (const renderer of renderers.values()) { try { const fiber = renderer.findFiberByHostInstance(element); if (fiber) return fiber; } catch { } } } if ("_reactRootContainer" in element) { return element._reactRootContainer._internalRoot.current.child; } for (const key in element) { if (key.startsWith("__reactFiber")) return element[key]; } };