diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts index a8cd84a3ae..af60db62ec 100644 --- a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts +++ b/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts @@ -11,17 +11,115 @@ import { createWindowElement } from './utils/window.js'; const icon = ''; +interface SourceInfo { + file: string; + loc?: string; +} + +let altClickManager: AltClickManager | undefined = undefined; + export default { id: 'astro:xray', name: 'Inspect', icon: icon, init(canvas) { let islandsOverlays: { highlightElement: DevOverlayHighlight; island: HTMLElement }[] = []; + let elementToSourceMap = new WeakMap(); + altClickManager = new AltClickManager(); + altClickManager.addEventListener('alt:hover', () => { + + }) + altClickManager.addEventListener('alt:click', () => { + + }) + + afterSwap(); + document.addEventListener('astro:after-swap', afterSwap); + document.addEventListener('astro:page-load', pageLoad); - addIslandsOverlay(); + function afterSwap() { + addIslandsOverlay(); + removeMetadata(); + } - document.addEventListener('astro:after-swap', addIslandsOverlay); - document.addEventListener('astro:page-load', refreshIslandsOverlayPositions); + function pageLoad() { + refreshIslandsOverlayPositions() + } + + let altHoverHighlight: DevOverlayHighlight | undefined; + function stopAltHover() { + if (altHoverHighlight) { + altHoverHighlight.remove(); + altHoverHighlight = undefined; + } + } + function onAltHover(element: HTMLElement | null) { + stopAltHover() + if (!element) return; + const rect = element.getBoundingClientRect(); + altHoverHighlight = createHighlight(rect); + altHoverHighlight.style.setProperty('pointer-events', 'none'); + canvas.append(altHoverHighlight); + } + + // document.addEventListener('keydown', (evt) => { + // if (altKeyActive) { + // altKeyActive = false; + // } + // altKeyActive = evt.altKey; + // if (altKeyActive) { + // onAltHover(mouseoverElement); + // } else { + // stopAltHover() + // } + // }) + // document.addEventListener('keyup', (evt) => { + // if (!altKeyActive) return; + // altKeyActive = evt.altKey; + // if (!altKeyActive) { + // stopAltHover() + // } + // }) + // document.addEventListener('mousemove', (evt) => { + // if (!altKeyActive) { + // stopAltHover() + // mouseoverElement = evt.target as HTMLElement; + // return; + // } + // if (evt.target !== mouseoverElement) { + // mouseoverElement = evt.target as HTMLElement; + // onAltHover(mouseoverElement) + // } + // }) + // document.addEventListener('click', (evt) => { + // if (!altKeyActive) return; + // if (!mouseoverElement) return; + // if (!(evt.target instanceof Node)) return; + // if (!mouseoverElement.contains(evt.target)) return; + // const info = getSourceInfo(mouseoverElement); + // const selection = document.getSelection(); + // console.log('click to open?', { ...info, selectionAnchorOffset: selection?.anchorOffset }); + + // }) + + function getSourceInfo(target: EventTarget): SourceInfo | undefined { + if (!(target instanceof HTMLElement)) return; + const info = elementToSourceMap.get(target); + return info; + } + + function removeMetadata() { + const elements = document.querySelectorAll('[data-astro-source-file]'); + for (const el of elements) { + if (elementToSourceMap.has(el)) continue; + const { astroSourceFile: file, astroSourceLoc: loc } = el.dataset; + if (file && loc) { + elementToSourceMap.set(el, { file, loc }) + delete el.dataset['astroSourceFile']; + delete el.dataset['astroSourceLoc']; + } + } + } function addIslandsOverlay() { islandsOverlays.forEach(({ highlightElement }) => { @@ -164,3 +262,60 @@ export default { } }, } satisfies DevOverlayPlugin; + +class AltClickManager extends EventTarget { + static events = ['keydown', 'keyup', 'mousemove', 'click']; + + target: EventTarget | null = null; + #keys = new Set() + get altKey() { + return this.#keys.has('Alt') && this.#keys.size === 1; + } + + constructor() { + super(); + this.#addEventListeners(); + } + + teardown() { + this.#removeEventListeners(); + } + + handleEvent(event: Event): void { + if (isMouseEvent(event)) { + this.target = event.target; + return; + } + if (isKeyboardEvent(event)) { + const prev = this.altKey; + const fn = event.type === 'keydown' ? 'add' : 'delete'; + this.#keys[fn](event.key); + if (prev !== this.altKey) { + console.log(this.altKey); + } + return; + } + if (event.type === 'click') { + return; + } + } + + #addEventListeners() { + for (const event of AltClickManager.events) { + document.addEventListener(event, this); + } + } + + #removeEventListeners() { + for (const event of AltClickManager.events) { + document.removeEventListener(event, this); + } + } +} + +function isMouseEvent(event: Event): event is MouseEvent { + return event.type === 'mousemove'; +} +function isKeyboardEvent(event: Event): event is KeyboardEvent { + return event.type === 'keydown' || event.type === 'keyup'; +}