UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

286 lines (261 loc) 8.46 kB
// Source Inspector - shows source location on hover when holding Option for 0.5s // Also supports live highlighting from VSCode cursor position // Uses shadow DOM for style isolation // Note: createHotContext is already imported in dev.mjs which concatenates this file ;(function () { try { let active = false let cursorHighlightActive = false // separate state for vscode cursor highlighting let host = null let shadow = null let overlay = null let tag = null let holdTimer = null const holdDelay = 800 let otherKeyPressed = false const mousePos = { x: 0, y: 0 } let removalObserver = null // set up HMR listener for cursor position from vscode const cursorHot = createHotContext('/__one_cursor_hmr') cursorHot.on('one:cursor-highlight', (data) => { if (data.clear) { cursorHighlightActive = false if (!active) hideOverlay() return } cursorHighlightActive = true highlightBySource(data.file, data.line, data.column) }) function createHost() { if (host) return host = document.createElement('div') host.id = 'one-source-inspector' shadow = host.attachShadow({ mode: 'open' }) shadow.innerHTML = ` <style> * { box-sizing: border-box; margin: 0; padding: 0; } .overlay { position: fixed; pointer-events: none; z-index: 100000; background: rgba(100,100,100,0.2); border: 2px solid rgba(100,100,100,0.6); border-radius: 2px; transition: all 0.05s; display: none; } .tag { position: fixed; pointer-events: none; z-index: 100001; background: rgba(60,60,60,0.95); color: white; padding: 4px 8px; font-size: 12px; font-family: system-ui, -apple-system, sans-serif; border-radius: 4px; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,0.3); display: none; } </style> <div class="overlay"></div> <div class="tag"></div> ` document.body.appendChild(host) overlay = shadow.querySelector('.overlay') tag = shadow.querySelector('.tag') setupRemovalObserver() } function setupRemovalObserver() { if (removalObserver) return removalObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.removedNodes) { if (node === host) { host = null shadow = null overlay = null tag = null return } } } } }) removalObserver.observe(document.body, { childList: true }) } function hideOverlay() { if (overlay) overlay.style.display = 'none' if (tag) tag.style.display = 'none' } function showOverlay(el, source, fromVscode = false) { if (!host) createHost() const rect = el.getBoundingClientRect() overlay.style.display = 'block' overlay.style.top = rect.top + 'px' overlay.style.left = rect.left + 'px' overlay.style.width = rect.width + 'px' overlay.style.height = rect.height + 'px' // use different color for vscode cursor highlight if (fromVscode) { overlay.style.background = 'rgba(59, 130, 246, 0.2)' overlay.style.borderColor = 'rgba(59, 130, 246, 0.8)' } else { overlay.style.background = 'rgba(100, 100, 100, 0.2)' overlay.style.borderColor = 'rgba(100, 100, 100, 0.6)' } tag.style.display = 'block' tag.textContent = source let top = rect.top - 28 if (top < 0) top = rect.bottom + 4 tag.style.top = top + 'px' tag.style.left = Math.max(4, Math.min(rect.left, window.innerWidth - 200)) + 'px' } function cancelHold() { if (holdTimer) { clearTimeout(holdTimer) holdTimer = null } } function parseSource(source) { const parts = source.split(':') const column = parseInt(parts.pop(), 10) const line = parseInt(parts.pop(), 10) const filePath = parts.join(':') return { filePath, line, column } } // find element by source file and line/column from vscode function highlightBySource(file, line, column) { // find all elements with data-one-source that match this file const elements = document.querySelectorAll(`[data-one-source^="${file}:"]`) let bestMatch = null let bestDistance = Infinity for (const el of elements) { const source = el.getAttribute('data-one-source') const parsed = parseSource(source) // find element whose line is <= cursor line (closest opening tag above/at cursor) if (parsed.line <= line) { const distance = line - parsed.line if ( distance < bestDistance || (distance === bestDistance && parsed.line === line) ) { bestDistance = distance bestMatch = { el, source } } } } if (bestMatch) { showOverlay(bestMatch.el, bestMatch.source, true) } else { hideOverlay() } } function updateOverlayAtPosition(x, y) { const allElements = document.elementsFromPoint(x, y) let el = null for (const element of allElements) { if (element.hasAttribute('data-one-source')) { el = element break } } if (el) { showOverlay(el, el.getAttribute('data-one-source')) } else { hideOverlay() } } function activate() { active = true updateOverlayAtPosition(mousePos.x, mousePos.y) } // expose for devtools menu window.__oneSourceInspector = { activate, deactivate, isActive: () => active, } function deactivate() { cancelHold() active = false // only hide if vscode isn't actively highlighting if (!cursorHighlightActive) { hideOverlay() } } window.addEventListener('keydown', (e) => { if (e.key !== 'Alt') { // any other key pressed cancels and marks that we're in a combo cancelHold() otherKeyPressed = true return } // option key pressed if (e.metaKey || e.ctrlKey || e.shiftKey) { cancelHold() return } // if another key was held before option, don't start if (otherKeyPressed) return if (e.defaultPrevented) return if (holdTimer) return holdTimer = setTimeout(() => { // double check no other key was pressed during the delay if (!otherKeyPressed) { activate() } }, holdDelay) }) window.addEventListener('keyup', (e) => { if (e.defaultPrevented) return if (e.key === 'Alt') { deactivate() } else { // reset when other keys are released (check if no modifiers held) if (!e.metaKey && !e.ctrlKey && !e.shiftKey) { otherKeyPressed = false } } }) window.addEventListener('blur', () => { deactivate() otherKeyPressed = false }) // deactivate when window is being resized (e.g. option+drag corner on macOS) window.addEventListener('resize', () => { deactivate() }) document.addEventListener('mousemove', (e) => { mousePos.x = e.clientX mousePos.y = e.clientY if (!active) return updateOverlayAtPosition(e.clientX, e.clientY) }) document.addEventListener( 'click', (e) => { if (!active) return const allElements = document.elementsFromPoint(e.clientX, e.clientY) let el = null for (const element of allElements) { if (element.hasAttribute('data-one-source')) { el = element break } } if (el) { e.preventDefault() e.stopPropagation() const source = el.getAttribute('data-one-source') fetch('/__one/open-source?source=' + encodeURIComponent(source)) } }, true ) } catch (e) { console.error('[Source Inspector] Failed to initialize:', e) } })()