-
-
-
-
-
switchEntry(e?.id)"
- >
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
switchEntry(e?.id)"
+ >
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
diff --git a/packages/core/src/client/webcomponents/components/dock/dock-layout.ts b/packages/core/src/client/webcomponents/components/dock/dock-layout.ts
index 1e9a9eb28..3f32bb93e 100644
--- a/packages/core/src/client/webcomponents/components/dock/dock-layout.ts
+++ b/packages/core/src/client/webcomponents/components/dock/dock-layout.ts
@@ -77,14 +77,14 @@ export interface DockMargins {
/** The built-in dock layout. Override individual fields via {@link resolveDockLayout}. */
export const DEFAULT_DOCK_LAYOUT: DockLayout = Object.freeze({
- barHeight: 35,
+ barHeight: 34,
barMinWidth: 100,
minimizedSize: 22,
glowSize: 160,
glowBlur: 60,
maxVisibleItems: 5,
viewportMargin: 2,
- panelOverlapFactor: 0.2,
+ panelOverlapFactor: -0.04,
edgeSnapPercent: 5,
centerSnapPercent: 2,
edgeZoneHeight: 70,
diff --git a/packages/core/src/client/webcomponents/components/floating/FloatingPopover.ts b/packages/core/src/client/webcomponents/components/floating/FloatingPopover.ts
index e3caeb3f6..5c1b63a21 100644
--- a/packages/core/src/client/webcomponents/components/floating/FloatingPopover.ts
+++ b/packages/core/src/client/webcomponents/components/floating/FloatingPopover.ts
@@ -125,7 +125,7 @@ const FloatingPopoverComponent = defineComponent({
{
ref: 'panel',
class: [
- `fixed z-floating-tooltip text-xs ${transitionClass} w-max bg-glass color-base border border-base rounded px2 p1`,
+ `fixed z-floating-tooltip text-xs ${transitionClass} w-max bg-glass:80 color-base border border-base rounded px2 p1`,
'op0 pointer-events-none',
props.panelClass,
],
@@ -162,7 +162,7 @@ const FloatingPopoverComponent = defineComponent({
{
ref: 'panel',
class: [
- `fixed z-floating-tooltip text-xs ${transitionClass} w-max bg-glass color-base border border-base rounded px2 p1`,
+ `fixed z-floating-tooltip text-xs ${transitionClass} w-max bg-glass:80 color-base border border-base rounded px2 p1`,
props.item ? 'op100' : 'op0 pointer-events-none',
props.panelClass,
],
diff --git a/packages/core/src/client/webcomponents/components/icons/ViteDevTools.vue b/packages/core/src/client/webcomponents/components/icons/ViteDevTools.vue
index 36e4c9c98..18ec6e279 100644
--- a/packages/core/src/client/webcomponents/components/icons/ViteDevTools.vue
+++ b/packages/core/src/client/webcomponents/components/icons/ViteDevTools.vue
@@ -1,8 +1,7 @@
diff --git a/packages/core/src/client/webcomponents/components/views-builtin/SettingsAppearance.vue b/packages/core/src/client/webcomponents/components/views-builtin/SettingsAppearance.vue
index 4e61e95e2..651309bff 100644
--- a/packages/core/src/client/webcomponents/components/views-builtin/SettingsAppearance.vue
+++ b/packages/core/src/client/webcomponents/components/views-builtin/SettingsAppearance.vue
@@ -3,6 +3,7 @@ import type { DocksContext } from '@vitejs/devtools-kit/client'
import type { SharedState } from 'devframe/utils/shared-state'
import type { DevToolsDocksUserSettings } from '../../state/dock-settings'
import { computed } from 'vue'
+import { colorSchemePreference, setColorSchemePreference } from '../../state/color-mode'
import { sharedStateToRef } from '../../state/docks'
import { isDockPopupSupported, requestDockPopupOpen, useIsDockPopupOpen } from '../../state/popup'
@@ -29,6 +30,12 @@ const dockModeOptions = computed(() => {
const currentDockMode = computed(() => panelStore.mode)
+const colorModeOptions = [
+ { value: 'auto', label: 'Auto', icon: 'i-ph-laptop-duotone' },
+ { value: 'light', label: 'Light', icon: 'i-ph-sun-duotone' },
+ { value: 'dark', label: 'Dark', icon: 'i-ph-moon-duotone' },
+] as const
+
function setDockMode(mode: string) {
if (mode === 'popup') {
requestDockPopupOpen(props.context)
@@ -41,6 +48,28 @@ function setDockMode(mode: string) {
+
+
+
+ Color mode
+ Theme for DevTools and its inner panels
+
+
+
+
+
+
diff --git a/packages/core/src/client/webcomponents/state/color-mode.ts b/packages/core/src/client/webcomponents/state/color-mode.ts
new file mode 100644
index 000000000..7af76f3ed
--- /dev/null
+++ b/packages/core/src/client/webcomponents/state/color-mode.ts
@@ -0,0 +1,31 @@
+import { usePreferredDark, useStorage } from '@vueuse/core'
+import { computed } from 'vue'
+
+export type ColorSchemePreference = 'auto' | 'light' | 'dark'
+
+/**
+ * Shared with the Nuxt plugin apps (`@vitejs/devtools-ui/composables/dark`).
+ * Those apps are served same-origin under the DevTools mount path, so writing
+ * this key here propagates into the iframes via `storage` events — one switch
+ * drives the dock shell and every inner integration at once.
+ */
+export const COLOR_SCHEME_STORAGE_KEY = 'vite-devtools-color-scheme'
+
+/** Persisted Auto / Light / Dark choice, defaulting to Auto (follow the OS). */
+export const colorSchemePreference = useStorage
(
+ COLOR_SCHEME_STORAGE_KEY,
+ 'auto',
+)
+
+const preferredDark = usePreferredDark()
+
+/** Resolved dark state — `auto` defers to the OS `prefers-color-scheme`. */
+export const isDark = computed(() =>
+ colorSchemePreference.value === 'auto'
+ ? preferredDark.value
+ : colorSchemePreference.value === 'dark',
+)
+
+export function setColorSchemePreference(preference: ColorSchemePreference) {
+ colorSchemePreference.value = preference
+}
diff --git a/packages/core/src/client/webcomponents/state/popup.ts b/packages/core/src/client/webcomponents/state/popup.ts
index 93555e64b..5d5cf20e2 100644
--- a/packages/core/src/client/webcomponents/state/popup.ts
+++ b/packages/core/src/client/webcomponents/state/popup.ts
@@ -1,13 +1,13 @@
import type { DocksContext } from '@vitejs/devtools-kit/client'
import { createEventEmitter } from 'devframe/utils/events'
-import { shallowRef } from 'vue'
+import { shallowRef, watch } from 'vue'
+import { isDark } from './color-mode'
import { setDocksOverflowPanel } from './floating-tooltip'
interface DocumentPictureInPicture {
requestWindow: (options?: { width?: number, height?: number }) => Promise
}
-type ColorMode = 'dark' | 'light'
interface DockPopupEvents {
'popup:open-requested': (context: DocksContext) => void
}
@@ -48,82 +48,17 @@ function clearListeners() {
detachColorModeSync = undefined
}
-function resolveColorMode(): ColorMode {
- const sourceWindow = window as Window & {
- document?: {
- documentElement?: { classList?: DOMTokenList, getAttribute?: (name: string) => string | null }
- body?: { classList?: DOMTokenList, getAttribute?: (name: string) => string | null }
- }
- matchMedia?: (query: string) => { matches: boolean }
- }
-
- const elements = [
- sourceWindow.document?.documentElement,
- sourceWindow.document?.body,
- ].filter(Boolean)
-
- for (const element of elements) {
- if (element?.classList?.contains('dark'))
- return 'dark'
- if (element?.classList?.contains('light'))
- return 'light'
-
- const dataTheme = element?.getAttribute?.('data-theme')
- if (dataTheme === 'dark' || dataTheme === 'light')
- return dataTheme
- }
-
- if (sourceWindow.matchMedia?.('(prefers-color-scheme: dark)').matches)
- return 'dark'
-
- return 'light'
-}
-
-function applyPopupColorMode(popup: Window, mode: ColorMode) {
- popup.document.documentElement?.style.setProperty('color-scheme', mode)
-}
-
function setupPopupColorModeSync(popup: Window): () => void {
- const cleanups: Array<() => void> = []
- const update = () => applyPopupColorMode(popup, resolveColorMode())
- update()
-
- const sourceWindow = window as Window & {
- document?: {
- documentElement?: Element
- body?: Element
- }
- matchMedia?: (query: string) => MediaQueryList
- }
- const sourceDocument = sourceWindow.document
-
- if (typeof MutationObserver !== 'undefined' && sourceDocument) {
- const observer = new MutationObserver(update)
- for (const element of [sourceDocument.documentElement, sourceDocument.body]) {
- if (!element)
- continue
- observer.observe(element, {
- attributes: true,
- attributeFilter: ['class', 'data-theme', 'style'],
- })
- }
- cleanups.push(() => observer.disconnect())
- }
-
- if (sourceWindow.matchMedia) {
- const darkQuery = sourceWindow.matchMedia('(prefers-color-scheme: dark)')
- const lightQuery = sourceWindow.matchMedia('(prefers-color-scheme: light)')
- darkQuery.addEventListener('change', update)
- lightQuery.addEventListener('change', update)
- cleanups.push(() => {
- darkQuery.removeEventListener('change', update)
- lightQuery.removeEventListener('change', update)
- })
- }
-
- return () => {
- cleanups.forEach(fn => fn())
- }
+ // Mirror the shared color-mode preference into the popup document's native
+ // `color-scheme` (scrollbars / form controls); the standalone shell inside
+ // handles its own `.dark`/`.light` class via `applyColorSchemeClass`.
+ return watch(
+ isDark,
+ (dark) => {
+ popup.document.documentElement?.style.setProperty('color-scheme', dark ? 'dark' : 'light')
+ },
+ { immediate: true },
+ )
}
function unmountPopupElement() {
diff --git a/packages/core/src/client/webcomponents/state/toasts.ts b/packages/core/src/client/webcomponents/state/toasts.ts
index 78219dc84..afa6faa21 100644
--- a/packages/core/src/client/webcomponents/state/toasts.ts
+++ b/packages/core/src/client/webcomponents/state/toasts.ts
@@ -15,7 +15,7 @@ export function useToasts(): Reactive {
}
export function addToast(entry: DevToolsMessageEntry): void {
- // Dedup: update existing toast with same id
+ // Dedupe: update existing toast with same id
const existing = toasts.find(t => t.id === entry.id)
if (existing) {
existing.entry = entry
diff --git a/packages/core/src/client/webcomponents/style.css b/packages/core/src/client/webcomponents/style.css
index ccb7983c2..125f1cfe5 100644
--- a/packages/core/src/client/webcomponents/style.css
+++ b/packages/core/src/client/webcomponents/style.css
@@ -69,7 +69,7 @@
#vite-devtools-anchor #vite-devtools-dock {
--uno: h-full rounded-full select-none touch-none ma;
- --uno: "shadow text-[#333] dark:text-white bg-glass";
+ --uno: "shadow text-[#333] dark:text-white bg-glass:80";
--uno: transition-all duration-500;
height: var(--vite-devtools-dock-height, 40px);
width: calc-size(max-content, size);
diff --git a/packages/core/src/client/webcomponents/uno.config.ts b/packages/core/src/client/webcomponents/uno.config.ts
index 162aa8189..3883e2c47 100644
--- a/packages/core/src/client/webcomponents/uno.config.ts
+++ b/packages/core/src/client/webcomponents/uno.config.ts
@@ -28,7 +28,11 @@ export default defineConfig({
},
presets: [
presetWind3({
- dark: 'media',
+ // Class-based so the settings switcher can override the OS preference.
+ // Each dock shell root wraps its content in a `.dark`/`.light` element
+ // (`ColorSchemeRoot`), so `.dark …` utilities resolve inside the shadow
+ // DOM and the light-DOM standalone page alike.
+ dark: 'class',
variablePrefix: 'vdt-',
}),
presetIcons({
diff --git a/packages/rolldown/src/app/components/data/AssetRelationships.vue b/packages/rolldown/src/app/components/data/AssetRelationships.vue
index 2d76b20da..df753d5bf 100644
--- a/packages/rolldown/src/app/components/data/AssetRelationships.vue
+++ b/packages/rolldown/src/app/components/data/AssetRelationships.vue
@@ -16,7 +16,7 @@ const MAX_LINKS = 20
const SPACING = {
width: 400,
height: 35,
- padding: 4,
+ padding: 9,
marginX: 8,
border: 1,
margin: 8,
diff --git a/packages/ui/src/components/Panel/PanelSideNav.vue b/packages/ui/src/components/Panel/PanelSideNav.vue
index b85083df9..4acee28a2 100644
--- a/packages/ui/src/components/Panel/PanelSideNav.vue
+++ b/packages/ui/src/components/Panel/PanelSideNav.vue
@@ -18,7 +18,7 @@ const items = computed(() => {
diff --git a/packages/ui/src/unocss/shared-shortcuts.ts b/packages/ui/src/unocss/shared-shortcuts.ts
index bd924ce2b..f4b55ad72 100644
--- a/packages/ui/src/unocss/shared-shortcuts.ts
+++ b/packages/ui/src/unocss/shared-shortcuts.ts
@@ -15,7 +15,7 @@ export const sharedShortcuts: (StaticShortcutMap | DynamicShortcut)[] = [
'color-active': 'color-primary-600 dark:color-primary-300',
'border-active': 'border-primary-600/25 dark:border-primary-400/25',
},
- // `bg-glass` / `bg-glass:75` — translucent surface + backdrop blur.
+ // `bg-glass` / `bg-glass:80` — translucent surface + backdrop blur.
[
/^bg-glass(?::(\d+))?$/,
([, opacity = '50']) => {
@@ -23,7 +23,7 @@ export const sharedShortcuts: (StaticShortcutMap | DynamicShortcut)[] = [
if (Number.isNaN(opInt) || opInt < 0 || opInt > 100)
return
const op = Math.min(Math.max(opInt, 0), 100)
- return `bg-white/${Math.min(Math.max(Math.round(opInt * 1.3), 0), 100)} dark:bg-#111/${op} backdrop-blur-7`
+ return `bg-white/${Math.min(Math.max(Math.round(opInt * 1.3), 0), 100)} dark:bg-#050505/${op} backdrop-blur-7`
},
{ layer: 'shortcuts' },
],