@@ -6,47 +6,98 @@ import type { Maybe } from '../../common/types.js';
6
6
import { LocalStorage } from './local-storage.js' ;
7
7
8
8
/**
9
- * The functions here are for tracking and setting the current theme.
10
- * localStorage is used to store the currently preferred them, though
11
- * that doesn't work on the server, where we just use a default.
9
+ * Find all the theme links on the page that we could switch between.
10
+ * See `_document.tsx` for how these are added to the page.
12
11
*/
12
+ const getAllThemeLinks = ( ) : Array < HTMLLinkElement > => {
13
+ return [
14
+ ...document . querySelectorAll < HTMLLinkElement > (
15
+ 'link[data-name="eui-theme"]'
16
+ ) ,
17
+ ] ;
18
+ } ;
13
19
14
- const getAllThemes = ( ) : Array < HTMLLinkElement > => {
15
- // @ts -ignore
16
- return [ ...document . querySelectorAll ( 'link[data-name="eui-theme"]' ) ] ;
20
+ /**
21
+ * Find the theme link on the page that matches the given theme name.
22
+ */
23
+ const getThemeLink = ( themeName : EuiThemeColorMode ) : Maybe < HTMLLinkElement > => {
24
+ return getAllThemeLinks ( ) . find ( ( themeLink ) => {
25
+ return themeLink . dataset . theme === themeName ;
26
+ } ) ;
17
27
} ;
18
28
29
+ /**
30
+ * Sets the user's theme preference and actively updates the UI to match.
31
+ * To only set the preference without updating the UI, use `setThemeName`.
32
+ */
19
33
export const enableTheme = ( newThemeName : EuiThemeColorMode ) : void => {
20
34
const oldThemeName = getThemeName ( ) ;
21
- LocalStorage . set < EuiThemeColorMode > ( 'theme' , newThemeName ) ;
35
+ setThemeName ( newThemeName ) ;
22
36
23
- for ( const themeLink of getAllThemes ( ) ) {
24
- // Disable all theme links, except for the desired theme, which we enable
25
- themeLink . disabled = themeLink . dataset . theme !== newThemeName ;
26
- themeLink . ariaDisabled = String ( themeLink . dataset . theme !== newThemeName ) ;
27
- }
37
+ const newThemeLink = getThemeLink ( newThemeName ) ;
28
38
29
- // Add a class to the `body` element that indicates which theme we're using.
30
- // This allows any custom styling to adapt to the current theme.
31
- if ( document . body . classList . contains ( `appTheme-${ oldThemeName } ` ) ) {
32
- document . body . classList . replace (
33
- `appTheme-${ oldThemeName } ` ,
34
- `appTheme-${ newThemeName } `
35
- ) ;
36
- } else {
37
- document . body . classList . add ( `appTheme-${ newThemeName } ` ) ;
39
+ if ( ! newThemeLink ) {
40
+ return ;
38
41
}
42
+
43
+ // When toggling the theme, to prevent a flash of unstyled content
44
+ // then preload the new theme's CSS before enabling it.
45
+ const preloadThemeLink = document . createElement ( 'link' ) ;
46
+ preloadThemeLink . rel = 'preload' ;
47
+ preloadThemeLink . as = 'style' ;
48
+ preloadThemeLink . href = newThemeLink . href ;
49
+ document . head . appendChild ( preloadThemeLink ) ;
50
+
51
+ // Once the new theme is loaded then we can disable the old theme.
52
+ // Because the new theme is preloaded, the change should be instant.
53
+ preloadThemeLink . onload = ( ) => {
54
+ for ( const themeLink of getAllThemeLinks ( ) ) {
55
+ // Disable all theme links, except for the desired theme, which we enable
56
+ themeLink . disabled = themeLink . dataset . theme !== newThemeName ;
57
+ themeLink . ariaDisabled = String ( themeLink . dataset . theme !== newThemeName ) ;
58
+ }
59
+
60
+ // Add a class to the `body` element that indicates which theme we're using.
61
+ // This allows any custom styling to adapt to the current theme.
62
+ if ( document . body . classList . contains ( `appTheme-${ oldThemeName } ` ) ) {
63
+ document . body . classList . replace (
64
+ `appTheme-${ oldThemeName } ` ,
65
+ `appTheme-${ newThemeName } `
66
+ ) ;
67
+ } else {
68
+ document . body . classList . add ( `appTheme-${ newThemeName } ` ) ;
69
+ }
70
+
71
+ // Remove the preload link element.
72
+ document . head . removeChild ( preloadThemeLink ) ;
73
+ } ;
39
74
} ;
40
75
76
+ /**
77
+ * Gets the user's theme preference.
78
+ */
41
79
export const getThemeName = ( ) : EuiThemeColorMode => {
42
80
return getStoredThemeName ( ) || getDefaultThemeName ( ) ;
43
81
} ;
44
82
45
- export const getStoredThemeName = ( ) : Maybe < EuiThemeColorMode > => {
83
+ /**
84
+ * Sets the user's theme preference.
85
+ * Does not actively change the UI look and feel.
86
+ * To do that, use `enableTheme` or reload the app.
87
+ */
88
+ export const setThemeName = ( themeName : EuiThemeColorMode ) : void => {
89
+ setStoredThemeName ( themeName ) ;
90
+ } ;
91
+
92
+ const getStoredThemeName = ( ) : Maybe < EuiThemeColorMode > => {
46
93
return LocalStorage . get < EuiThemeColorMode > ( 'theme' ) ;
47
94
} ;
48
95
49
- export const getDefaultThemeName = ( ) : EuiThemeColorMode => {
96
+ const setStoredThemeName = ( themeName : EuiThemeColorMode ) : void => {
97
+ LocalStorage . set < EuiThemeColorMode > ( 'theme' , themeName ) ;
98
+ } ;
99
+
100
+ const getDefaultThemeName = ( ) : EuiThemeColorMode => {
50
101
return 'dark' ;
51
102
} ;
52
103
0 commit comments