summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Erika <3019731+Princesseuh@users.noreply.github.com> 2023-10-31 23:35:32 +0100
committerGravatar GitHub <noreply@github.com> 2023-10-31 23:35:32 +0100
commit262cef2487c7494bd8d23b4ab27bfcdf1870a111 (patch)
treeb561732ae00a74ef4b75c1299ad4b15e7f26939b
parent463e03633e9cb712213b263b280c1bc81eccd8df (diff)
downloadastro-262cef2487c7494bd8d23b4ab27bfcdf1870a111.tar.gz
astro-262cef2487c7494bd8d23b4ab27bfcdf1870a111.tar.zst
astro-262cef2487c7494bd8d23b4ab27bfcdf1870a111.zip
refactor: dev overlay to make it easier to work with VT (#8966)
Diffstat (limited to '')
-rw-r--r--.changeset/healthy-hornets-kiss.md5
-rw-r--r--packages/astro/src/@types/astro.ts14
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/entrypoint.ts84
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/overlay.ts478
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts3
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts55
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts9
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts27
-rw-r--r--packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts2
-rw-r--r--packages/astro/src/vite-plugin-astro-server/route.ts2
10 files changed, 386 insertions, 293 deletions
diff --git a/.changeset/healthy-hornets-kiss.md b/.changeset/healthy-hornets-kiss.md
new file mode 100644
index 000000000..df69c840c
--- /dev/null
+++ b/.changeset/healthy-hornets-kiss.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fix Dev Overlay not working properly when view transitions are enabled
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 7b08808a5..4ae5e7138 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -21,7 +21,11 @@ import type { TSConfig } from '../core/config/tsconfig.js';
import type { AstroCookies } from '../core/cookies/index.js';
import type { ResponseWithEncoding } from '../core/endpoint/index.js';
import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger/core.js';
+import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
+import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
import type { Icon } from '../runtime/client/dev-overlay/ui-library/icons.js';
+import type { DevOverlayTooltip } from '../runtime/client/dev-overlay/ui-library/tooltip.js';
+import type { DevOverlayWindow } from '../runtime/client/dev-overlay/ui-library/window.js';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server/index.js';
import type { OmitIndexSignature, Simplify } from '../type-utils.js';
import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
@@ -2322,3 +2326,13 @@ export type DevOverlayMetadata = Window &
root: string;
};
};
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'astro-dev-overlay': AstroDevOverlay;
+ 'astro-dev-overlay-window': DevOverlayWindow;
+ 'astro-dev-overlay-plugin-canvas': DevOverlayCanvas;
+ 'astro-dev-overlay-tooltip': DevOverlayTooltip;
+ 'astro-dev-overlay-highlight': DevOverlayHighlight;
+ }
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts b/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
new file mode 100644
index 000000000..fe7efcccc
--- /dev/null
+++ b/packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
@@ -0,0 +1,84 @@
+import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
+import { type AstroDevOverlay, type DevOverlayPlugin } from './overlay.js';
+
+let overlay: AstroDevOverlay;
+
+document.addEventListener('DOMContentLoaded', async () => {
+ const [
+ { loadDevOverlayPlugins },
+ { default: astroDevToolPlugin },
+ { default: astroAuditPlugin },
+ { default: astroXrayPlugin },
+ { AstroDevOverlay, DevOverlayCanvas },
+ { DevOverlayCard },
+ { DevOverlayHighlight },
+ { DevOverlayTooltip },
+ { DevOverlayWindow },
+ ] = await Promise.all([
+ // @ts-expect-error
+ import('astro:dev-overlay'),
+ import('./plugins/astro.js'),
+ import('./plugins/audit.js'),
+ import('./plugins/xray.js'),
+ import('./overlay.js'),
+ import('./ui-library/card.js'),
+ import('./ui-library/highlight.js'),
+ import('./ui-library/tooltip.js'),
+ import('./ui-library/window.js'),
+ ]);
+
+ // Register custom elements
+ customElements.define('astro-dev-overlay', AstroDevOverlay);
+ customElements.define('astro-dev-overlay-window', DevOverlayWindow);
+ customElements.define('astro-dev-overlay-plugin-canvas', DevOverlayCanvas);
+ customElements.define('astro-dev-overlay-tooltip', DevOverlayTooltip);
+ customElements.define('astro-dev-overlay-highlight', DevOverlayHighlight);
+ customElements.define('astro-dev-overlay-card', DevOverlayCard);
+
+ overlay = document.createElement('astro-dev-overlay');
+
+ const preparePlugin = (
+ pluginDefinition: DevOverlayPluginDefinition,
+ builtIn: boolean
+ ): DevOverlayPlugin => {
+ const eventTarget = new EventTarget();
+ const plugin = {
+ ...pluginDefinition,
+ builtIn: builtIn,
+ active: false,
+ status: 'loading' as const,
+ eventTarget: eventTarget,
+ };
+
+ // Events plugins can send to the overlay to update their status
+ eventTarget.addEventListener('plugin-notification', (evt) => {
+ const target = overlay.shadowRoot?.querySelector(`[data-plugin-id="${plugin.id}"]`);
+ if (!target) return;
+
+ let newState = true;
+ if (evt instanceof CustomEvent) {
+ newState = evt.detail.state ?? true;
+ }
+
+ target.querySelector('.notification')?.toggleAttribute('data-active', newState);
+ });
+
+ return plugin;
+ };
+
+ const customPluginsDefinitions = (await loadDevOverlayPlugins()) as DevOverlayPluginDefinition[];
+ const plugins: DevOverlayPlugin[] = [
+ ...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin].map((pluginDef) =>
+ preparePlugin(pluginDef, true)
+ ),
+ ...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false)),
+ ];
+
+ overlay.plugins = plugins;
+
+ document.body.append(overlay);
+
+ document.addEventListener('astro:after-swap', () => {
+ document.body.append(overlay);
+ });
+});
diff --git a/packages/astro/src/runtime/client/dev-overlay/overlay.ts b/packages/astro/src/runtime/client/dev-overlay/overlay.ts
index e93f3bcac..00ca35569 100644
--- a/packages/astro/src/runtime/client/dev-overlay/overlay.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/overlay.ts
@@ -1,79 +1,47 @@
/* eslint-disable no-console */
-// @ts-expect-error
-import { loadDevOverlayPlugins } from 'astro:dev-overlay';
import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
-import astroDevToolPlugin from './plugins/astro.js';
-import astroAuditPlugin from './plugins/audit.js';
-import astroXrayPlugin from './plugins/xray.js';
-import { DevOverlayCard } from './ui-library/card.js';
-import { DevOverlayHighlight } from './ui-library/highlight.js';
import { getIconElement, isDefinedIcon, type Icon } from './ui-library/icons.js';
-import { DevOverlayTooltip } from './ui-library/tooltip.js';
-import { DevOverlayWindow } from './ui-library/window.js';
-type DevOverlayPlugin = DevOverlayPluginDefinition & {
+export type DevOverlayPlugin = DevOverlayPluginDefinition & {
+ builtIn: boolean;
active: boolean;
status: 'ready' | 'loading' | 'error';
eventTarget: EventTarget;
};
-document.addEventListener('DOMContentLoaded', async () => {
- const WS_EVENT_NAME = 'astro-dev-overlay';
- const HOVER_DELAY = 750;
-
- const builtinPlugins: DevOverlayPlugin[] = [
- astroDevToolPlugin,
- astroXrayPlugin,
- astroAuditPlugin,
- ].map((plugin) => ({
- ...plugin,
- active: false,
- status: 'loading',
- eventTarget: new EventTarget(),
- }));
-
- const customPluginsImports = (await loadDevOverlayPlugins()) as DevOverlayPluginDefinition[];
- const customPlugins: DevOverlayPlugin[] = [];
- customPlugins.push(
- ...customPluginsImports.map((plugin) => ({
- ...plugin,
- active: false,
- status: 'loading' as const,
- eventTarget: new EventTarget(),
- }))
- );
-
- const plugins: DevOverlayPlugin[] = [...builtinPlugins, ...customPlugins];
-
- for (const plugin of plugins) {
- plugin.eventTarget.addEventListener('plugin-notification', (evt) => {
- const target = overlay.shadowRoot?.querySelector(`[data-plugin-id="${plugin.id}"]`);
- if (!target) return;
-
- let newState = true;
- if (evt instanceof CustomEvent) {
- newState = evt.detail.state ?? true;
- }
+const WS_EVENT_NAME = 'astro-dev-overlay';
- target.querySelector('.notification')?.toggleAttribute('data-active', newState);
- });
- }
+export class AstroDevOverlay extends HTMLElement {
+ shadowRoot: ShadowRoot;
+ hoverTimeout: number | undefined;
+ isHidden: () => boolean = () => this.devOverlay?.hasAttribute('data-hidden') ?? true;
+ devOverlay: HTMLDivElement | undefined;
+ plugins: DevOverlayPlugin[] = [];
+ HOVER_DELAY = 750;
+ hasBeenInitialized = false;
- class AstroDevOverlay extends HTMLElement {
- shadowRoot: ShadowRoot;
- hoverTimeout: number | undefined;
- isHidden: () => boolean = () => this.devOverlay?.hasAttribute('data-hidden') ?? true;
- devOverlay: HTMLDivElement | undefined;
-
- constructor() {
- super();
- this.shadowRoot = this.attachShadow({ mode: 'open' });
- }
+ constructor() {
+ super();
+ this.shadowRoot = this.attachShadow({ mode: 'open' });
+ }
- // connect component
- async connectedCallback() {
+ // Happens whenever the component is connected to the DOM
+ // When view transitions are enabled, this happens every time the view changes
+ async connectedCallback() {
+ if (!this.hasBeenInitialized) {
this.shadowRoot.innerHTML = `
<style>
+ :host {
+ z-index: 999999;
+ view-transition-name: astro-dev-overlay;
+ display: contents;
+ }
+
+ ::view-transition-old(astro-dev-overlay),
+ ::view-transition-new(astro-dev-overlay) {
+ animation: none;
+ }
+
#dev-overlay {
position: fixed;
bottom: 7.5%;
@@ -250,258 +218,252 @@ document.addEventListener('DOMContentLoaded', async () => {
<div id="dev-overlay">
<div id="dev-bar">
<div id="bar-container">
- ${builtinPlugins.map((plugin) => this.getPluginTemplate(plugin)).join('')}
+ ${this.plugins
+ .filter((plugin) => plugin.builtIn)
+ .map((plugin) => this.getPluginTemplate(plugin))
+ .join('')}
<div class="separator"></div>
- ${customPlugins.map((plugin) => this.getPluginTemplate(plugin)).join('')}
+ ${this.plugins
+ .filter((plugin) => !plugin.builtIn)
+ .map((plugin) => this.getPluginTemplate(plugin))
+ .join('')}
</div>
</div>
<button id="minimize-button">${getIconElement('arrow-down')?.outerHTML}</button>
</div>`;
-
this.devOverlay = this.shadowRoot.querySelector<HTMLDivElement>('#dev-overlay')!;
this.attachEvents();
+ }
- // Init plugin lazily
- if ('requestIdleCallback' in window) {
- window.requestIdleCallback(async () => {
- await this.initAllPlugins();
- });
- } else {
- // Fallback to setTimeout for.. Safari...
- setTimeout(async () => {
- await this.initAllPlugins();
- }, 200);
+ // Create plugin canvases
+ this.plugins.forEach((plugin) => {
+ if (!this.hasBeenInitialized) {
+ console.log(`Creating plugin canvas for ${plugin.id}`);
+ const pluginCanvas = document.createElement('astro-dev-overlay-plugin-canvas');
+ pluginCanvas.dataset.pluginId = plugin.id;
+ this.shadowRoot?.append(pluginCanvas);
}
+
+ this.togglePluginStatus(plugin, plugin.active);
+ });
+
+ // Init plugin lazily - This is safe to do here because only plugins that are not initialized yet will be affected
+ if ('requestIdleCallback' in window) {
+ window.requestIdleCallback(async () => {
+ await this.initAllPlugins();
+ });
+ } else {
+ // Fallback to setTimeout for.. Safari...
+ setTimeout(async () => {
+ await this.initAllPlugins();
+ }, 200);
}
- attachEvents() {
- const items = this.shadowRoot.querySelectorAll<HTMLDivElement>('.item');
- items.forEach((item) => {
- item.addEventListener('click', async (e) => {
- const target = e.currentTarget;
- if (!target || !(target instanceof HTMLElement)) return;
+ this.hasBeenInitialized = true;
+ }
+
+ attachEvents() {
+ const items = this.shadowRoot.querySelectorAll<HTMLDivElement>('.item');
+ items.forEach((item) => {
+ item.addEventListener('click', async (e) => {
+ const target = e.currentTarget;
+ if (!target || !(target instanceof HTMLElement)) return;
- const id = target.dataset.pluginId;
- if (!id) return;
+ const id = target.dataset.pluginId;
+ if (!id) return;
- const plugin = this.getPluginById(id);
- if (!plugin) return;
+ const plugin = this.getPluginById(id);
+ if (!plugin) return;
- if (plugin.status === 'loading') {
- await this.initPlugin(plugin);
- }
+ if (plugin.status === 'loading') {
+ await this.initPlugin(plugin);
+ }
- this.togglePluginStatus(plugin);
- });
+ this.togglePluginStatus(plugin);
});
+ });
- const minimizeButton = this.shadowRoot.querySelector<HTMLDivElement>('#minimize-button');
- if (minimizeButton && this.devOverlay) {
- minimizeButton.addEventListener('click', () => {
- this.toggleOverlay(false);
- this.toggleMinimizeButton(false);
- });
- }
-
- const devBar = this.shadowRoot.querySelector<HTMLDivElement>('#dev-bar');
- if (devBar) {
- // On hover:
- // - If the overlay is hidden, show it after the hover delay
- // - If the overlay is visible, show the minimize button after the hover delay
- (['mouseenter', 'focusin'] as const).forEach((event) => {
- devBar.addEventListener(event, () => {
- if (this.hoverTimeout) {
- window.clearTimeout(this.hoverTimeout);
- }
-
- if (this.isHidden()) {
- this.hoverTimeout = window.setTimeout(() => {
- this.toggleOverlay(true);
- }, HOVER_DELAY);
- } else {
- this.hoverTimeout = window.setTimeout(() => {
- this.toggleMinimizeButton(true);
- }, HOVER_DELAY);
- }
- });
- });
+ const minimizeButton = this.shadowRoot.querySelector<HTMLDivElement>('#minimize-button');
+ if (minimizeButton && this.devOverlay) {
+ minimizeButton.addEventListener('click', () => {
+ this.toggleOverlay(false);
+ this.toggleMinimizeButton(false);
+ });
+ }
- // On unhover:
- // - Reset every timeout, as to avoid showing the overlay/minimize button when the user didn't really want to hover
- // - If the overlay is visible, hide the minimize button after the hover delay
- devBar.addEventListener('mouseleave', () => {
+ const devBar = this.shadowRoot.querySelector<HTMLDivElement>('#dev-bar');
+ if (devBar) {
+ // On hover:
+ // - If the overlay is hidden, show it after the hover delay
+ // - If the overlay is visible, show the minimize button after the hover delay
+ (['mouseenter', 'focusin'] as const).forEach((event) => {
+ devBar.addEventListener(event, () => {
if (this.hoverTimeout) {
window.clearTimeout(this.hoverTimeout);
}
- if (!this.isHidden()) {
+ if (this.isHidden()) {
+ this.hoverTimeout = window.setTimeout(() => {
+ this.toggleOverlay(true);
+ }, this.HOVER_DELAY);
+ } else {
this.hoverTimeout = window.setTimeout(() => {
- this.toggleMinimizeButton(false);
- }, HOVER_DELAY);
+ this.toggleMinimizeButton(true);
+ }, this.HOVER_DELAY);
}
});
+ });
+
+ // On unhover:
+ // - Reset every timeout, as to avoid showing the overlay/minimize button when the user didn't really want to hover
+ // - If the overlay is visible, hide the minimize button after the hover delay
+ devBar.addEventListener('mouseleave', () => {
+ if (this.hoverTimeout) {
+ window.clearTimeout(this.hoverTimeout);
+ }
- // On click, show the overlay if it's hidden, it's likely the user wants to interact with it
- devBar.addEventListener('click', () => {
+ if (!this.isHidden()) {
+ this.hoverTimeout = window.setTimeout(() => {
+ this.toggleMinimizeButton(false);
+ }, this.HOVER_DELAY);
+ }
+ });
+
+ // On click, show the overlay if it's hidden, it's likely the user wants to interact with it
+ devBar.addEventListener('click', () => {
+ if (!this.isHidden()) return;
+ this.toggleOverlay(true);
+ });
+
+ devBar.addEventListener('keyup', (event) => {
+ if (event.code === 'Space' || event.code === 'Enter') {
if (!this.isHidden()) return;
this.toggleOverlay(true);
- });
-
- devBar.addEventListener('keyup', (event) => {
- if (event.code === 'Space' || event.code === 'Enter') {
- if (!this.isHidden()) return;
- this.toggleOverlay(true);
- }
- });
- }
+ }
+ });
}
+ }
- async initAllPlugins() {
- await Promise.all(
- plugins
- .filter((plugin) => plugin.status === 'loading')
- .map((plugin) => this.initPlugin(plugin))
- );
- }
+ async initAllPlugins() {
+ await Promise.all(
+ this.plugins
+ .filter((plugin) => plugin.status === 'loading')
+ .map((plugin) => this.initPlugin(plugin))
+ );
+ }
- async initPlugin(plugin: DevOverlayPlugin) {
- if (plugin.status === 'ready') return;
+ async initPlugin(plugin: DevOverlayPlugin) {
+ if (plugin.status === 'ready') return;
- const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!;
+ const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!;
- try {
- console.info(`Initing plugin ${plugin.id}`);
- await plugin.init?.(shadowRoot, plugin.eventTarget);
- plugin.status = 'ready';
+ try {
+ console.info(`Initializing plugin ${plugin.id}`);
+ await plugin.init?.(shadowRoot, plugin.eventTarget);
+ plugin.status = 'ready';
- if (import.meta.hot) {
- import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:init`);
- }
- } catch (e) {
- console.error(`Failed to init plugin ${plugin.id}, error: ${e}`);
- plugin.status = 'error';
+ if (import.meta.hot) {
+ import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:init`);
}
+ } catch (e) {
+ console.error(`Failed to init plugin ${plugin.id}, error: ${e}`);
+ plugin.status = 'error';
}
+ }
- getPluginTemplate(plugin: DevOverlayPlugin) {
- return `<button class="item" data-plugin-id="${plugin.id}">
+ getPluginTemplate(plugin: DevOverlayPlugin) {
+ return `<button class="item" data-plugin-id="${plugin.id}">
<div class="icon">${this.getPluginIcon(plugin.icon)}<div class="notification"></div></div>
<span class="sr-only">${plugin.name}</span>
</button>`;
- }
-
- getPluginIcon(icon: Icon) {
- if (isDefinedIcon(icon)) {
- return getIconElement(icon)?.outerHTML;
- }
+ }
- return icon;
+ getPluginIcon(icon: Icon) {
+ if (isDefinedIcon(icon)) {
+ return getIconElement(icon)?.outerHTML;
}
- getPluginById(id: string) {
- return plugins.find((plugin) => plugin.id === id);
- }
+ return icon;
+ }
- getPluginCanvasById(id: string) {
- return this.shadowRoot.querySelector(
- `astro-dev-overlay-plugin-canvas[data-plugin-id="${id}"]`
- );
- }
+ getPluginById(id: string) {
+ return this.plugins.find((plugin) => plugin.id === id);
+ }
- togglePluginStatus(plugin: DevOverlayPlugin, status?: boolean) {
- plugin.active = status ?? !plugin.active;
- const target = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`);
- if (!target) return;
- target.classList.toggle('active', plugin.active);
- this.getPluginCanvasById(plugin.id)?.toggleAttribute('data-active', plugin.active);
-
- plugin.eventTarget.dispatchEvent(
- new CustomEvent('plugin-toggle', {
- detail: {
- state: plugin.active,
- plugin,
- },
- })
- );
+ getPluginCanvasById(id: string) {
+ return this.shadowRoot.querySelector(`astro-dev-overlay-plugin-canvas[data-plugin-id="${id}"]`);
+ }
- if (import.meta.hot) {
- import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:toggle`, { state: plugin.active });
- }
+ togglePluginStatus(plugin: DevOverlayPlugin, status?: boolean) {
+ plugin.active = status ?? !plugin.active;
+ const target = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`);
+ if (!target) return;
+ target.classList.toggle('active', plugin.active);
+ this.getPluginCanvasById(plugin.id)?.toggleAttribute('data-active', plugin.active);
+
+ plugin.eventTarget.dispatchEvent(
+ new CustomEvent('plugin-toggle', {
+ detail: {
+ state: plugin.active,
+ plugin,
+ },
+ })
+ );
+
+ if (import.meta.hot) {
+ import.meta.hot.send(`${WS_EVENT_NAME}:${plugin.id}:toggle`, { state: plugin.active });
}
+ }
- toggleMinimizeButton(newStatus?: boolean) {
- const minimizeButton = this.shadowRoot.querySelector<HTMLDivElement>('#minimize-button');
- if (!minimizeButton) return;
-
- if (newStatus !== undefined) {
- if (newStatus === true) {
- minimizeButton.removeAttribute('inert');
- minimizeButton.style.opacity = '1';
- } else {
- minimizeButton.setAttribute('inert', '');
- minimizeButton.style.opacity = '0';
- }
+ toggleMinimizeButton(newStatus?: boolean) {
+ const minimizeButton = this.shadowRoot.querySelector<HTMLDivElement>('#minimize-button');
+ if (!minimizeButton) return;
+
+ if (newStatus !== undefined) {
+ if (newStatus === true) {
+ minimizeButton.removeAttribute('inert');
+ minimizeButton.style.opacity = '1';
} else {
- minimizeButton.toggleAttribute('inert');
- minimizeButton.style.opacity = minimizeButton.hasAttribute('inert') ? '0' : '1';
+ minimizeButton.setAttribute('inert', '');
+ minimizeButton.style.opacity = '0';
}
+ } else {
+ minimizeButton.toggleAttribute('inert');
+ minimizeButton.style.opacity = minimizeButton.hasAttribute('inert') ? '0' : '1';
}
+ }
- toggleOverlay(newStatus?: boolean) {
- const barContainer = this.shadowRoot.querySelector<HTMLDivElement>('#bar-container');
- const devBar = this.shadowRoot.querySelector<HTMLDivElement>('#dev-bar');
-
- if (newStatus !== undefined) {
- if (newStatus === true) {
- this.devOverlay?.removeAttribute('data-hidden');
- barContainer?.removeAttribute('inert');
- devBar?.removeAttribute('tabindex');
- } else {
- this.devOverlay?.setAttribute('data-hidden', '');
- barContainer?.setAttribute('inert', '');
- devBar?.setAttribute('tabindex', '0');
- }
+ toggleOverlay(newStatus?: boolean) {
+ const barContainer = this.shadowRoot.querySelector<HTMLDivElement>('#bar-container');
+ const devBar = this.shadowRoot.querySelector<HTMLDivElement>('#dev-bar');
+
+ if (newStatus !== undefined) {
+ if (newStatus === true) {
+ this.devOverlay?.removeAttribute('data-hidden');
+ barContainer?.removeAttribute('inert');
+ devBar?.removeAttribute('tabindex');
} else {
- this.devOverlay?.toggleAttribute('data-hidden');
- barContainer?.toggleAttribute('inert');
- if (this.isHidden()) {
- devBar?.setAttribute('tabindex', '0');
- } else {
- devBar?.removeAttribute('tabindex');
- }
+ this.devOverlay?.setAttribute('data-hidden', '');
+ barContainer?.setAttribute('inert', '');
+ devBar?.setAttribute('tabindex', '0');
+ }
+ } else {
+ this.devOverlay?.toggleAttribute('data-hidden');
+ barContainer?.toggleAttribute('inert');
+ if (this.isHidden()) {
+ devBar?.setAttribute('tabindex', '0');
+ } else {
+ devBar?.removeAttribute('tabindex');
}
}
}
+}
- class DevOverlayCanvas extends HTMLElement {
- shadowRoot: ShadowRoot;
+export class DevOverlayCanvas extends HTMLElement {
+ shadowRoot: ShadowRoot;
- constructor() {
- super();
- this.shadowRoot = this.attachShadow({ mode: 'open' });
- }
-
- // connect component
- async connectedCallback() {
- this.shadowRoot.innerHTML = ``;
- }
+ constructor() {
+ super();
+ this.shadowRoot = this.attachShadow({ mode: 'open' });
}
-
- customElements.define('astro-dev-overlay', AstroDevOverlay);
- customElements.define('astro-dev-overlay-window', DevOverlayWindow);
- customElements.define('astro-dev-overlay-plugin-canvas', DevOverlayCanvas);
- customElements.define('astro-dev-overlay-tooltip', DevOverlayTooltip);
- customElements.define('astro-dev-overlay-highlight', DevOverlayHighlight);
- customElements.define('astro-dev-overlay-card', DevOverlayCard);
-
- const overlay = document.createElement('astro-dev-overlay');
- overlay.style.zIndex = '999999';
- document.body.append(overlay);
-
- // Create plugin canvases
- plugins.forEach((plugin) => {
- const pluginCanvas = document.createElement('astro-dev-overlay-plugin-canvas');
- pluginCanvas.dataset.pluginId = plugin.id;
- overlay.shadowRoot?.append(pluginCanvas);
- });
-});
+}
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
index 11e7bb7e0..cc83cbe83 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
@@ -1,12 +1,11 @@
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
-import type { DevOverlayWindow } from '../ui-library/window.js';
export default {
id: 'astro',
name: 'Astro',
icon: 'astro:logo',
init(canvas) {
- const astroWindow = document.createElement('astro-dev-overlay-window') as DevOverlayWindow;
+ const astroWindow = document.createElement('astro-dev-overlay-window');
astroWindow.windowTitle = 'Astro';
astroWindow.windowIcon = 'astro:logo';
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts
index f8bda831f..46ed88da0 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/audit.ts
@@ -1,6 +1,5 @@
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
import type { DevOverlayHighlight } from '../ui-library/highlight.js';
-import type { DevOverlayTooltip } from '../ui-library/tooltip.js';
import { attachTooltipToHighlight, createHighlight, positionHighlight } from './utils/highlight.js';
const icon =
@@ -26,20 +25,39 @@ export default {
init(canvas, eventTarget) {
let audits: { highlightElement: DevOverlayHighlight; auditedElement: HTMLElement }[] = [];
- selectorBasedRules.forEach((rule) => {
- document.querySelectorAll(rule.selector).forEach((el) => {
- createAuditProblem(rule, el);
+ lint();
+
+ document.addEventListener('astro:after-swap', lint);
+ document.addEventListener('astro:page-load', refreshLintPositions);
+
+ function lint() {
+ audits.forEach(({ highlightElement }) => {
+ highlightElement.remove();
+ });
+ audits = [];
+
+ selectorBasedRules.forEach((rule) => {
+ document.querySelectorAll(rule.selector).forEach((el) => {
+ createAuditProblem(rule, el);
+ });
+ });
+
+ if (audits.length > 0) {
+ eventTarget.dispatchEvent(
+ new CustomEvent('plugin-notification', {
+ detail: {
+ state: true,
+ },
+ })
+ );
+ }
+ }
+
+ function refreshLintPositions() {
+ audits.forEach(({ highlightElement, auditedElement }) => {
+ const rect = auditedElement.getBoundingClientRect();
+ positionHighlight(highlightElement, rect);
});
- });
-
- if (audits.length > 0) {
- eventTarget.dispatchEvent(
- new CustomEvent('plugin-notification', {
- detail: {
- state: true,
- },
- })
- );
}
function createAuditProblem(rule: AuditRule, originalElement: Element) {
@@ -60,17 +78,12 @@ export default {
audits.push({ highlightElement: highlight, auditedElement: originalElement as HTMLElement });
(['scroll', 'resize'] as const).forEach((event) => {
- window.addEventListener(event, () => {
- audits.forEach(({ highlightElement, auditedElement }) => {
- const newRect = auditedElement.getBoundingClientRect();
- positionHighlight(highlightElement, newRect);
- });
- });
+ window.addEventListener(event, refreshLintPositions);
});
}
function buildAuditTooltip(rule: AuditRule) {
- const tooltip = document.createElement('astro-dev-overlay-tooltip') as DevOverlayTooltip;
+ const tooltip = document.createElement('astro-dev-overlay-tooltip');
tooltip.sections = [
{
icon: 'warning',
diff --git a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts
index 79948dcd7..3af467ecd 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/utils/highlight.ts
@@ -2,16 +2,21 @@ import type { DevOverlayHighlight } from '../../ui-library/highlight.js';
import type { Icon } from '../../ui-library/icons.js';
export function createHighlight(rect: DOMRect, icon?: Icon) {
- const highlight = document.createElement('astro-dev-overlay-highlight') as DevOverlayHighlight;
+ const highlight = document.createElement('astro-dev-overlay-highlight');
if (icon) highlight.icon = icon;
highlight.tabIndex = 0;
- positionHighlight(highlight, rect);
+ if (rect.width === 0 || rect.height === 0) {
+ highlight.style.display = 'none';
+ } else {
+ positionHighlight(highlight, rect);
+ }
return highlight;
}
export function positionHighlight(highlight: DevOverlayHighlight, rect: DOMRect) {
+ highlight.style.display = 'block';
// Make an highlight that is 10px bigger than the element on all sides
highlight.style.top = `${Math.max(rect.top + window.scrollY - 10, 0)}px`;
highlight.style.left = `${Math.max(rect.left + window.scrollX - 10, 0)}px`;
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 fe92604f4..7b5f3539c 100644
--- a/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/plugins/xray.ts
@@ -1,6 +1,5 @@
import type { DevOverlayMetadata, DevOverlayPlugin } from '../../../../@types/astro.js';
import type { DevOverlayHighlight } from '../ui-library/highlight.js';
-import type { DevOverlayTooltip } from '../ui-library/tooltip.js';
import { attachTooltipToHighlight, createHighlight, positionHighlight } from './utils/highlight.js';
const icon =
@@ -12,9 +11,18 @@ export default {
icon: icon,
init(canvas) {
let islandsOverlays: { highlightElement: DevOverlayHighlight; island: HTMLElement }[] = [];
+
addIslandsOverlay();
+ document.addEventListener('astro:after-swap', addIslandsOverlay);
+ document.addEventListener('astro:page-load', refreshIslandsOverlayPositions);
+
function addIslandsOverlay() {
+ islandsOverlays.forEach(({ highlightElement }) => {
+ highlightElement.remove();
+ });
+ islandsOverlays = [];
+
const islands = document.querySelectorAll<HTMLElement>('astro-island');
islands.forEach((island) => {
@@ -22,6 +30,7 @@ export default {
const islandElement = (island.children[0] as HTMLElement) || island;
// If the island is hidden, don't show an overlay on it
+ // TODO: For `client:only` islands, it might not have finished loading yet, so we should wait for that
if (islandElement.offsetParent === null || computedStyle.display === 'none') {
return;
}
@@ -36,17 +45,19 @@ export default {
});
(['scroll', 'resize'] as const).forEach((event) => {
- window.addEventListener(event, () => {
- islandsOverlays.forEach(({ highlightElement, island: islandElement }) => {
- const newRect = islandElement.getBoundingClientRect();
- positionHighlight(highlightElement, newRect);
- });
- });
+ window.addEventListener(event, refreshIslandsOverlayPositions);
+ });
+ }
+
+ function refreshIslandsOverlayPositions() {
+ islandsOverlays.forEach(({ highlightElement, island: islandElement }) => {
+ const rect = islandElement.getBoundingClientRect();
+ positionHighlight(highlightElement, rect);
});
}
function buildIslandTooltip(island: HTMLElement) {
- const tooltip = document.createElement('astro-dev-overlay-tooltip') as DevOverlayTooltip;
+ const tooltip = document.createElement('astro-dev-overlay-tooltip');
tooltip.sections = [];
const islandProps = island.getAttribute('props')
diff --git a/packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts b/packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts
index 0671fff0c..0ff67750f 100644
--- a/packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts
+++ b/packages/astro/src/runtime/client/dev-overlay/ui-library/tooltip.ts
@@ -59,7 +59,7 @@ export class DevOverlayTooltip extends HTMLElement {
.section-content {
max-height: 250px;
- overflow-y: scroll;
+ overflow-y: auto;
}
.modal-title {
diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts
index 163fcdd04..c9eb06bf6 100644
--- a/packages/astro/src/vite-plugin-astro-server/route.ts
+++ b/packages/astro/src/vite-plugin-astro-server/route.ts
@@ -281,7 +281,7 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
scripts.add({
props: {
type: 'module',
- src: await resolveIdToUrl(moduleLoader, 'astro/runtime/client/dev-overlay/overlay.js'),
+ src: await resolveIdToUrl(moduleLoader, 'astro/runtime/client/dev-overlay/entrypoint.js'),
},
children: '',
});