@@ -206,6 +206,10 @@ const menuShortcutButtonUi = {
206206const currentThemeAccent = computed (() => getThemePreset (currentThemePresetId .value )?.accent || " var(--theme-accent)" );
207207const themePaletteOpen = ref (false );
208208const previewedThemePresetId = ref <ThemePresetId | null >(null );
209+ const palettePreviewArmed = ref (false );
210+ const PALETTE_PREVIEW_DELAY_MS = 300 ;
211+ let palettePreviewArmFrame = 0 ;
212+ let palettePreviewTimer: ReturnType <typeof setTimeout > | null = null ;
209213
210214const resolvedPaletteMode = computed <" light" | " dark" >(() => {
211215 if (currentAppearanceMode .value === " light" ) return " light" ;
@@ -217,6 +221,38 @@ const visibleThemePresets = computed(() =>
217221 resolvedPaletteMode .value === " dark" ? DARK_THEME_PRESETS : LIGHT_THEME_PRESETS
218222);
219223
224+ const handleProfileOpenAutoFocus = (event : Event ) => {
225+ if (profileOpenedByPointer .value ) event .preventDefault ();
226+ profileOpenedByPointer .value = false ;
227+ };
228+
229+ const handlePaletteOpenAutoFocus = (event : Event ) => {
230+ event .preventDefault ();
231+ };
232+
233+ const cancelPalettePreviewArm = () => {
234+ if (! palettePreviewArmFrame ) return ;
235+ cancelAnimationFrame (palettePreviewArmFrame );
236+ palettePreviewArmFrame = 0 ;
237+ };
238+
239+ const cancelPalettePreviewTimer = () => {
240+ if (! palettePreviewTimer ) return ;
241+ clearTimeout (palettePreviewTimer );
242+ palettePreviewTimer = null ;
243+ };
244+
245+ const disarmPalettePreview = () => {
246+ cancelPalettePreviewArm ();
247+ cancelPalettePreviewTimer ();
248+ palettePreviewArmed .value = false ;
249+ };
250+
251+ const armPalettePreview = () => {
252+ cancelPalettePreviewArm ();
253+ palettePreviewArmed .value = true ;
254+ };
255+
220256watch (profileOpen , (open ) => {
221257 if (! open ) {
222258 themePaletteOpen .value = false ;
@@ -225,24 +261,44 @@ watch(profileOpen, (open) => {
225261});
226262
227263watch (themePaletteOpen , (open ) => {
228- if (! open ) restorePalettePreview ();
229- });
264+ if (! open ) {
265+ disarmPalettePreview ();
266+ restorePalettePreview ();
267+ return ;
268+ }
230269
231- const handleProfileOpenAutoFocus = (event : Event ) => {
232- if (profileOpenedByPointer .value ) event .preventDefault ();
233- profileOpenedByPointer .value = false ;
234- };
270+ disarmPalettePreview ();
271+ palettePreviewArmFrame = requestAnimationFrame (() => {
272+ palettePreviewArmFrame = requestAnimationFrame (() => {
273+ palettePreviewArmFrame = 0 ;
274+ if (themePaletteOpen .value ) palettePreviewArmed .value = true ;
275+ });
276+ });
277+ });
235278
236279const previewPalettePreset = (id : ThemePresetId ) => {
237- const preset = getThemePreset (id );
238- if (! preset || ! import .meta .client ) return ;
280+ if (! palettePreviewArmed .value ) return ;
281+ if (previewedThemePresetId .value === id ) {
282+ cancelPalettePreviewTimer ();
283+ return ;
284+ }
239285
240- previewedThemePresetId .value = id ;
241- document .documentElement .dataset .themePreset = id ;
242- applyPrimaryColor (preset .accent );
286+ cancelPalettePreviewTimer ();
287+ palettePreviewTimer = setTimeout (() => {
288+ palettePreviewTimer = null ;
289+ if (! palettePreviewArmed .value || ! themePaletteOpen .value ) return ;
290+
291+ const preset = getThemePreset (id );
292+ if (! preset || ! import .meta .client ) return ;
293+
294+ previewedThemePresetId .value = id ;
295+ document .documentElement .dataset .themePreset = id ;
296+ applyPrimaryColor (preset .accent );
297+ }, PALETTE_PREVIEW_DELAY_MS );
243298};
244299
245300function restorePalettePreview() {
301+ cancelPalettePreviewTimer ();
246302 if (! previewedThemePresetId .value || ! import .meta .client ) return ;
247303
248304 previewedThemePresetId .value = null ;
@@ -830,6 +886,7 @@ onMounted(async () => {
830886});
831887
832888onBeforeUnmount (() => {
889+ disarmPalettePreview ();
833890 restorePalettePreview ();
834891 if (unlistenAuthUrlRef .value ) unlistenAuthUrlRef .value ();
835892 if (unlistenErrorPageRef .value ) unlistenErrorPageRef .value ();
@@ -1011,7 +1068,12 @@ onBeforeUnmount(() => {
10111068 </div >
10121069 <UPopover
10131070 v-model :open =" themePaletteOpen "
1014- :content =" { align: ' start' , side: ' left' , sideOffset: 8 } "
1071+ :content =" {
1072+ align: ' start' ,
1073+ side: ' left' ,
1074+ sideOffset: 8 ,
1075+ onOpenAutoFocus: handlePaletteOpenAutoFocus
1076+ } "
10151077 :ui =" {
10161078 content:
10171079 ' w-56 max-h-80 overflow-y-auto rounded-xl bg-[var(--app-surface-overlay)] p-1.5 shadow-[var(--theme-shadow-soft)] ring-1 ring-[var(--app-border)] backdrop-blur-md'
@@ -1028,7 +1090,12 @@ onBeforeUnmount(() => {
10281090 </UButton >
10291091
10301092 <template #content >
1031- <div class =" space-y-0.5" @pointerleave =" restorePalettePreview" @focusout =" restorePalettePreview" >
1093+ <div
1094+ class =" space-y-0.5"
1095+ @pointermove =" armPalettePreview"
1096+ @pointerleave =" restorePalettePreview"
1097+ @focusout =" restorePalettePreview"
1098+ >
10321099 <UButton
10331100 v-for =" item in visibleThemePresets "
10341101 :key =" item .id "
0 commit comments