@code-coaching/vuetiful 0.23.2 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/types/components/atoms/VLightSwitch.vue.d.ts +5 -1
  2. package/dist/types/services/dark-mode.service.d.ts +13 -13
  3. package/dist/types/services/index.d.ts +2 -2
  4. package/dist/types/utils/colors/colors.service.d.ts +69 -0
  5. package/dist/types/utils/index.d.ts +5 -1
  6. package/dist/types/utils/theme/theme.service.d.ts +12 -23
  7. package/dist/types/utils/theme/themes.d.ts +39 -0
  8. package/dist/vuetiful.es.mjs +452 -146
  9. package/dist/vuetiful.umd.js +71 -16
  10. package/package.json +1 -1
  11. package/src/components/atoms/VLightSwitch.test.ts +61 -12
  12. package/src/components/atoms/VLightSwitch.vue +13 -19
  13. package/src/components/molecules/VTabs/VTab.test.ts +21 -0
  14. package/src/directives/clipboard.test.ts +2 -2
  15. package/src/services/dark-mode.service.test.ts +58 -210
  16. package/src/services/dark-mode.service.ts +32 -51
  17. package/src/services/drawer.service.test.ts +4 -4
  18. package/src/services/highlight.service.test.ts +3 -3
  19. package/src/services/index.ts +2 -2
  20. package/src/services/rail.service.test.ts +2 -2
  21. package/src/utils/colors/colors.service.ts +293 -0
  22. package/src/utils/index.ts +5 -1
  23. package/src/utils/platform/platform.service.test.ts +3 -3
  24. package/src/utils/theme/callback.test.ts +9 -5
  25. package/src/utils/theme/remove.test.ts +7 -5
  26. package/src/utils/theme/theme-switcher.vue +34 -37
  27. package/src/utils/theme/theme.service.test.ts +160 -58
  28. package/src/utils/theme/theme.service.ts +140 -78
  29. package/src/utils/theme/themes.ts +127 -0
  30. package/dist/types/components/index.test.d.ts +0 -1
  31. package/dist/types/index.test.d.ts +0 -1
  32. package/dist/types/utils/index.test.d.ts +0 -1
  33. package/src/components/index.test.ts +0 -10
  34. package/src/index.test.ts +0 -26
  35. package/src/utils/index.test.ts +0 -11
  36. /package/src/themes/{theme-vuetiful-0.0.1.css → theme-vuetiful.css} +0 -0
@@ -1,5 +1,4 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { Theme } from './theme.service';
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
3
2
 
4
3
  const localStorageMock = {
5
4
  getItem: vi.fn(),
@@ -12,7 +11,7 @@ describe('useTheme', () => {
12
11
  });
13
12
  describe('initializetheme', () => {
14
13
  describe('given not in browser', () => {
15
- it('should set the theme to the default theme if no theme is stored', async () => {
14
+ test('should set the theme to the default theme if no theme is stored', async () => {
16
15
  const platform = await import('../platform/platform.service');
17
16
  vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
18
17
  isBrowser: false,
@@ -33,8 +32,9 @@ describe('useTheme', () => {
33
32
  beforeEach(() => {
34
33
  window.localStorage = localStorageMock as any;
35
34
  });
35
+
36
36
  describe('given no theme is stored', () => {
37
- it('should set the theme to the default theme', async () => {
37
+ test('should set the theme to the default theme', async () => {
38
38
  const platform = await import('../platform/platform.service');
39
39
  vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
40
40
  isBrowser: true,
@@ -43,125 +43,227 @@ describe('useTheme', () => {
43
43
  const localStorageSpy = vi.spyOn(window.localStorage, 'getItem');
44
44
 
45
45
  const { useTheme } = await import('./theme.service');
46
- const { initializeTheme, THEMES, chosenTheme } = useTheme();
46
+ const { initializeTheme, themes, chosenTheme } = useTheme();
47
47
 
48
48
  initializeTheme();
49
49
 
50
- expect(localStorageSpy).toHaveBeenCalledWith('vuetiful-theme');
51
- expect(chosenTheme.value).toBe(THEMES.VUETIFUL);
50
+ expect(localStorageSpy).not.toHaveBeenCalled();
51
+ expect(chosenTheme.value.name).toBe(themes[0].name);
52
52
  });
53
53
  });
54
+
54
55
  describe('given a theme is stored', () => {
55
- it('should set the theme to the stored theme', async () => {
56
+ test('should set the theme to the stored theme', async () => {
56
57
  const platform = await import('../platform/platform.service');
57
58
  vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
58
59
  isBrowser: true,
59
60
  });
60
61
 
61
62
  const { useTheme } = await import('./theme.service');
62
- const { initializeTheme, THEMES, chosenTheme } = useTheme();
63
+ const { initializeTheme, themes, chosenTheme } = useTheme();
64
+
65
+ const customTheme = JSON.parse(JSON.stringify(themes[1]));
66
+ customTheme.name = 'rocket';
63
67
 
64
68
  const localStorageSpy = vi.spyOn(window.localStorage, 'getItem');
65
- localStorageSpy.mockReturnValueOnce(THEMES.ROCKET);
66
69
 
70
+ document.cookie = 'vuetiful-theme=rocket';
67
71
  initializeTheme();
68
72
 
69
- expect(localStorageSpy).toHaveBeenCalledWith('vuetiful-theme');
70
- expect(chosenTheme.value).toBe(THEMES.ROCKET);
73
+ expect(localStorageSpy).not.toHaveBeenCalled();
74
+ expect(chosenTheme.value).toEqual(customTheme);
71
75
  });
72
76
  });
73
77
 
74
- describe('given the theme is not valid', () => {
75
- it('should set the theme to the default theme', async () => {
78
+ describe('given the theme cookie is not a known theme', () => {
79
+ test('should set the theme to the default theme', async () => {
76
80
  const platform = await import('../platform/platform.service');
77
81
  vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
78
82
  isBrowser: true,
79
83
  });
80
84
 
81
85
  const { useTheme } = await import('./theme.service');
82
- const { initializeTheme, THEMES, chosenTheme } = useTheme();
86
+ const { initializeTheme, themes, chosenTheme } = useTheme();
83
87
 
84
88
  const localStorageSpy = vi.spyOn(window.localStorage, 'getItem');
85
- localStorageSpy.mockReturnValueOnce('invalid-theme');
86
89
 
90
+ document.cookie = 'vuetiful-theme=not-a-theme';
87
91
  initializeTheme();
88
92
 
89
- expect(localStorageSpy).toHaveBeenCalledWith('vuetiful-theme');
90
- expect(chosenTheme.value).toBe(THEMES.VUETIFUL);
93
+ expect(localStorageSpy).not.toHaveBeenCalled();
94
+ expect(chosenTheme.value).toEqual(themes[0]);
91
95
  });
92
96
  });
97
+
98
+ describe('given the theme is set to custom', () => {
99
+ test('should set the custom theme from local storage', async () => {
100
+ const platform = await import('../platform/platform.service');
101
+ vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
102
+ isBrowser: true,
103
+ });
104
+
105
+ const { useTheme } = await import('./theme.service');
106
+ const { initializeTheme, themes, chosenTheme } = useTheme();
107
+
108
+ const localStorageSpy = vi.spyOn(window.localStorage, 'getItem');
109
+ const customTheme = JSON.parse(JSON.stringify(themes[0]));
110
+ customTheme.name = 'custom';
111
+ localStorageSpy.mockReturnValueOnce(JSON.stringify(customTheme));
112
+
113
+ document.cookie = 'vuetiful-theme=custom';
114
+ initializeTheme();
115
+
116
+ expect(localStorageSpy).toHaveBeenCalledWith('vuetiful-custom-theme');
117
+ expect(chosenTheme.value).toEqual(customTheme);
118
+ });
119
+
120
+ test('should set the default theme if an invalid theme is stored', async () => {
121
+ const platform = await import('../platform/platform.service');
122
+ vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
123
+ isBrowser: true,
124
+ });
125
+
126
+ const { useTheme } = await import('./theme.service');
127
+ const { initializeTheme, themes, chosenTheme } = useTheme();
128
+
129
+ const localStorageSpy = vi.spyOn(window.localStorage, 'getItem');
130
+ localStorageSpy.mockReturnValueOnce('not-a-theme');
131
+
132
+ document.cookie = 'vuetiful-theme=custom';
133
+ initializeTheme();
134
+
135
+ expect(localStorageSpy).toHaveBeenCalledWith('vuetiful-custom-theme');
136
+ expect(chosenTheme.value).toEqual(themes[0]);
137
+ })
138
+
139
+ test('should set the default theme if no theme is stored', async () => {
140
+ const platform = await import('../platform/platform.service');
141
+ vi.spyOn(platform, 'usePlatform').mockReturnValueOnce({
142
+ isBrowser: true,
143
+ });
144
+
145
+ const { useTheme } = await import('./theme.service');
146
+ const { initializeTheme, themes, chosenTheme } = useTheme();
147
+
148
+ const localStorageSpy = vi.spyOn(window.localStorage, 'getItem');
149
+ localStorageSpy.mockReturnValueOnce(null);
150
+
151
+ document.cookie = 'vuetiful-theme=custom';
152
+ initializeTheme();
153
+
154
+ expect(localStorageSpy).toHaveBeenCalledWith('vuetiful-custom-theme');
155
+ expect(chosenTheme.value).toEqual(themes[0]);
156
+ })
157
+ });
158
+
93
159
  });
94
160
  });
95
161
 
96
- describe('loadTheme', () => {
97
- describe('given theme name exists in themes', () => {
98
- it('should set the theme to the given theme', async () => {
162
+ describe('registerTheme', () => {
163
+ test('should register a theme', async () => {
164
+ const { useTheme } = await import('./theme.service');
165
+ const { registerTheme, themes } = useTheme();
166
+
167
+ const newTheme = JSON.parse(JSON.stringify(themes[0]));
168
+ newTheme.name = 'new-theme';
169
+
170
+ registerTheme(newTheme);
171
+ expect(themes).toContain(newTheme);
172
+ });
173
+
174
+ describe('given the theme is already registered', () => {
175
+ test('should update the theme', async () => {
99
176
  const { useTheme } = await import('./theme.service');
100
- const { loadTheme, THEMES, chosenTheme } = useTheme();
177
+ const { registerTheme, themes } = useTheme();
101
178
 
102
- loadTheme(THEMES.ROCKET);
179
+ const newTheme = JSON.parse(JSON.stringify(themes[0]));
180
+ newTheme.customCss = 'new-custom-css';
103
181
 
104
- expect(chosenTheme.value).toBe(THEMES.ROCKET);
182
+ registerTheme(newTheme);
183
+
184
+ expect(themes.find((theme) => theme.name === newTheme.name)).toEqual(newTheme);
105
185
  });
106
186
  });
107
- describe('given theme name does not exist in themes', () => {
108
- it('should set the theme to the default theme', async () => {
187
+ });
188
+
189
+ describe('getThemeFromCookie', () => {
190
+ describe('given there is no cookie', () => {
191
+ test('should return default theme', async () => {
109
192
  const { useTheme } = await import('./theme.service');
110
- const { loadTheme, THEMES, chosenTheme } = useTheme();
193
+ const { getThemeFromCookie, themes } = useTheme();
111
194
 
112
- loadTheme('invalid-theme');
195
+ const cookie = '';
196
+ const theme = getThemeFromCookie(cookie);
113
197
 
114
- expect(chosenTheme.value).toBe(THEMES.VUETIFUL);
198
+ expect(theme).toEqual(themes[0]);
115
199
  });
116
200
  });
117
- describe('given default theme is not in themes', () => {
118
- it('should set the theme to the first theme in the themes object', async () => {
201
+
202
+ describe('given there is a cookie', () => {
203
+ test('should return the theme', async () => {
119
204
  const { useTheme } = await import('./theme.service');
120
- const { loadTheme, THEMES, chosenTheme, overwriteThemes, themes } = useTheme();
205
+ const { getThemeFromCookie, themes } = useTheme();
121
206
 
122
- const theme: Theme = { name: 'fake theme', url: '' };
123
- overwriteThemes([themes.value[1], theme]);
207
+ const cookie = 'vuetiful-theme=rocket';
124
208
 
125
- loadTheme('invalid-theme');
209
+ const theme = getThemeFromCookie(cookie);
126
210
 
127
- expect(chosenTheme.value).toBe(themes.value[0].name);
211
+ const rocketTheme = themes.find((theme) => theme.name === 'rocket');
212
+ expect(theme).toEqual(rocketTheme);
128
213
  });
129
214
  });
130
215
  });
131
216
 
132
- describe('registerAllBuiltInThemes', () => {
133
- it('should register all built in themes', async () => {
217
+ describe('applyThemeSSR', () => {
218
+ test('should apply the theme', async () => {
134
219
  const { useTheme } = await import('./theme.service');
135
- const { registerAllBuiltInThemes, THEMES, themes } = useTheme();
220
+ const { applyThemeSSR, themes } = useTheme();
136
221
 
137
- registerAllBuiltInThemes();
222
+ const theme = themes[0];
138
223
 
139
- expect(themes.value).toHaveLength(Object.keys(THEMES).length);
224
+ const preHtml = '<html><head></head><body></body></html>';
225
+ const html = applyThemeSSR(preHtml, theme);
226
+
227
+ const style = html.includes('id="vuetiful-theme"');
228
+ expect(style).toBe(true);
229
+ const body = html.includes('data-theme="vuetiful"');
230
+ expect(body).toBe(true);
140
231
  });
141
- });
142
232
 
143
- describe('registerTheme', () => {
144
- it('should register a theme', async () => {
233
+ test('no customBase, no customHeadings, no gradients, no custom css', async () => {
145
234
  const { useTheme } = await import('./theme.service');
146
- const { registerTheme } = useTheme();
147
-
148
- const theme: Theme = { name: 'fake theme', url: '' };
149
- expect(registerTheme(theme.name, '')).toEqual(theme);
235
+ const { applyThemeSSR, themes } = useTheme();
236
+
237
+ const theme = JSON.parse(JSON.stringify(themes[0]));
238
+ theme.fonts.customBase = '';
239
+ theme.fonts.customHeadings = '';
240
+ theme.gradients.light = '';
241
+ theme.gradients.dark = '';
242
+ theme.customCss = '';
243
+
244
+ const preHtml = '<html><head></head><body></body></html>';
245
+ const html = applyThemeSSR(preHtml, theme);
246
+
247
+ const style = html.includes('id="vuetiful-theme"');
248
+ expect(style).toBe(true);
249
+ const body = html.includes('data-theme="vuetiful"');
250
+ expect(body).toBe(true);
150
251
  });
151
- });
152
252
 
153
- describe('saveThemeToStorage', () => {
154
- describe('given the theme does not exist', () => {
155
- it('should not save the theme to storage', async () => {
156
- const { useTheme } = await import('./theme.service');
157
- const { saveThemeToStorage } = useTheme();
253
+ test('invalid hex colors', async () => {
254
+ const { useTheme } = await import('./theme.service');
255
+ const { applyThemeSSR, themes } = useTheme();
158
256
 
159
- const localStorageSpy = vi.spyOn(window.localStorage, 'setItem');
257
+ const theme = JSON.parse(JSON.stringify(themes[0]));
258
+ theme.colors.primary = 'invalid';
160
259
 
161
- saveThemeToStorage('invalid-theme');
260
+ const preHtml = '<html><head></head><body></body></html>';
261
+ const html = applyThemeSSR(preHtml, theme);
162
262
 
163
- expect(localStorageSpy).not.toHaveBeenCalled();
164
- });
263
+ const style = html.includes('id="vuetiful-theme"');
264
+ expect(style).toBe(true);
265
+ const body = html.includes('data-theme="vuetiful"');
266
+ expect(body).toBe(true);
165
267
  });
166
268
  });
167
269
  });
@@ -1,109 +1,171 @@
1
- import { readonly, Ref, ref } from 'vue';
1
+ import { Ref, ref } from 'vue';
2
2
  import { usePlatform } from '../platform/platform.service';
3
+ import { ColorSettings, Palette, useColors } from '../colors/colors.service';
4
+ import { Theme, themes, THEME } from './themes';
3
5
 
4
6
  const { isBrowser } = usePlatform();
5
-
6
- export interface Theme {
7
- name: string;
8
- url: string;
9
- }
10
-
11
- const THEMES = {
12
- VUETIFUL: 'vuetiful',
13
- ROCKET: 'rocket',
14
- SAHARA: 'sahara',
15
- SEAFOAM: 'seafoam',
16
- SEASONAL: 'seasonal',
17
- SKELETON: 'skeleton',
18
- VINTAGE: 'vintage',
19
- };
20
-
21
- const builtInUrl = (name: string): string => {
22
- return `https://code-coaching.dev/vuetiful-themes/theme-${name}.css`;
7
+ const chosenTheme: Ref<Theme> = ref(themes[0]);
8
+ const { generatePalette, hexValuesAreValid } = useColors();
9
+
10
+ const generateColorCSS = (theme: Theme): string => {
11
+ let newCSS = '';
12
+ const newPalette: Record<string, Palette> = {};
13
+ Object.values(theme.colors).forEach((color: ColorSettings) => {
14
+ const colorKey = color.key;
15
+ newPalette[color.key] = generatePalette(color.hex);
16
+ newCSS += '\n\t';
17
+ newCSS += `/* ${colorKey} | ${newPalette[colorKey][500].hex} */\n\t`;
18
+ for (let [k, v] of Object.entries(newPalette[colorKey])) {
19
+ newCSS += `--color-${colorKey}-${k}: ${v.rgb}; /* ⬅ ${v.hex} */\n\t`;
20
+ }
21
+ });
22
+ return newCSS;
23
23
  };
24
24
 
25
- const builtInThemes: Array<Theme> = [
26
- { name: THEMES.VUETIFUL, url: builtInUrl(`${THEMES.VUETIFUL}-0.0.1`) },
27
- { name: THEMES.ROCKET, url: builtInUrl(`${THEMES.ROCKET}`) },
28
- { name: THEMES.SAHARA, url: builtInUrl(`${THEMES.SAHARA}`) },
29
- { name: THEMES.SEAFOAM, url: builtInUrl(`${THEMES.SEAFOAM}`) },
30
- { name: THEMES.SEASONAL, url: builtInUrl(`${THEMES.SEASONAL}`) },
31
- { name: THEMES.SKELETON, url: builtInUrl(`${THEMES.SKELETON}`) },
32
- { name: THEMES.VINTAGE, url: builtInUrl(`${THEMES.VINTAGE}`) },
33
- ];
34
-
35
- const themes: Ref<Array<Theme>> = ref([...builtInThemes]);
36
-
37
- const defaultTheme = THEMES.VUETIFUL;
38
- const chosenTheme = ref(defaultTheme);
39
-
40
25
  const useTheme = () => {
41
- const saveThemeToStorage = (name: string): void => {
42
- const theme = themes.value.find((t) => t.name === name);
43
- if (!theme) return;
26
+ const changeDataTheme = (name: string) => document.body.setAttribute('data-theme', name);
44
27
 
45
- if (isBrowser) {
46
- localStorage.setItem('vuetiful-theme', theme.name);
47
- document.body.setAttribute('data-theme', theme.name);
48
- }
28
+ const getThemeFromCookie = (cookies: string): Theme => {
29
+ const themeName = getThemeNameFromCookie(cookies);
30
+ const theme = themes.find((t) => t.name === themeName);
31
+ if (theme) return theme;
32
+ return themes[0];
49
33
  };
50
34
 
51
- const initializeTheme = (callback?: Function): void => {
52
- if (isBrowser) {
53
- const storedTheme = localStorage.getItem('vuetiful-theme');
54
- if (storedTheme) loadTheme(storedTheme, callback);
55
- else loadTheme(defaultTheme, callback);
56
- }
35
+ const getThemeNameFromCookie = (cookies: string): string => {
36
+ const cookie = cookies.split(';').find((c) => c.trim().startsWith(`vuetiful-theme=`));
37
+ const value = cookie?.split('=')[1];
38
+ return value || '';
57
39
  };
58
40
 
59
- const loadTheme = (themeName: string, callback?: Function) => {
60
- let themeToLoad = themes.value.find((t) => t.name === themeName);
61
- if (!themeToLoad) themeToLoad = themes.value.find((t) => t.name === defaultTheme) || themes.value[0];
41
+ const applyThemeSSR = (html: string, theme: Theme): string => {
42
+ chosenTheme.value = theme;
43
+ const css = generateCss(theme);
44
+ html = html.replace('</head>', `<style type="text/css" id="vuetiful-theme">${css}</style></head>`);
45
+ html = html.replace('<body', `<body data-theme="${theme.name}"`);
46
+ return html;
47
+ };
62
48
 
63
- const theme: Theme = themeToLoad;
64
- chosenTheme.value = theme.name;
49
+ const generateCss = (theme: Theme): string => {
50
+ if (hexValuesAreValid(Object.values(theme.colors))) {
51
+ return `${theme.fonts.baseImports}
52
+ ${theme.fonts.headingImports}
53
+ :root {
54
+ /* =~= Theme Properties =~= */
55
+ --theme-font-family-base: ${theme.fonts.customBase ? `"${theme.fonts.customBase}", ` : ''}${theme.fonts.base};
56
+ --theme-font-family-heading: ${theme.fonts.customHeadings ? `"${theme.fonts.customHeadings}", ` : ''}${
57
+ theme.fonts.headings
58
+ };
59
+ --theme-font-color-base: ${theme.textColorLight};
60
+ --theme-font-color-dark: ${theme.textColorDark};
61
+ --theme-rounded-base: ${theme.roundedBase};
62
+ --theme-rounded-container: ${theme.roundedContainer};
63
+ --theme-border-base: ${theme.borderBase};
64
+
65
+ /* =~= Theme On-X Colors =~= */
66
+ --on-primary: ${theme.colors.primary.on};
67
+ --on-secondary: ${theme.colors.secondary.on};
68
+ --on-tertiary: ${theme.colors.tertiary.on};
69
+ --on-success: ${theme.colors.success.on};
70
+ --on-warning: ${theme.colors.warning.on};
71
+ --on-error: ${theme.colors.error.on};
72
+ --on-surface: ${theme.colors.surface.on};
73
+
74
+ /* =~= Theme Colors =~= */
75
+ ${generateColorCSS(theme)}
76
+ }
77
+
78
+ ${
79
+ theme.gradients.light.length
80
+ ? `[data-theme="${theme.name}"] {
81
+ background-image:
82
+ ${theme.gradients.light};
83
+ }`
84
+ : ''
85
+ }
86
+ ${
87
+ theme.gradients.dark.length
88
+ ? `.dark [data-theme="${theme.name}"] {
89
+ background-image:
90
+ ${theme.gradients.dark};
91
+ }`
92
+ : ''
93
+ }
94
+ ${theme.customCss}
95
+ `;
96
+ }
97
+ return '';
98
+ };
65
99
 
66
- const existingStyle = document.getElementById('theme');
67
- let themeUrl = theme.url;
100
+ const applyTheme = (theme: Theme, callback?: Function) => {
101
+ const existingStyle = document.getElementById('vuetiful-theme');
102
+ const themeCss = generateCss(theme);
68
103
 
69
- const link = document.createElement('link');
70
- link.id = 'theme';
71
- link.href = themeUrl;
72
- link.type = 'text/css';
73
- link.rel = 'stylesheet';
74
- link.onload = () => {
104
+ const style = document.createElement('style');
105
+ style.innerHTML = themeCss;
106
+ style.id = 'vuetiful-theme';
107
+ style.onload = () => {
75
108
  if (existingStyle) existingStyle.remove();
76
- saveThemeToStorage(theme.name);
77
109
  if (callback) callback();
78
110
  };
79
111
 
80
112
  const head = document.querySelector('head');
81
- if (head) head.appendChild(link);
82
- };
113
+ if (head) head.appendChild(style);
83
114
 
84
- const registerAllBuiltInThemes = (): Array<Theme> => {
85
- return [...builtInThemes];
115
+ chosenTheme.value = theme;
116
+ if (isBrowser) {
117
+ document.cookie = `vuetiful-theme=${theme.name};path=/;max-age=31536000;SameSite=Lax`;
118
+ changeDataTheme(theme.name);
119
+ }
86
120
  };
87
121
 
88
- const registerTheme = (name: string, url: string): Theme => {
89
- return { url, name };
122
+ const initializeTheme = (callback?: Function): void => {
123
+ if (isBrowser) {
124
+ const themeName = getThemeNameFromCookie(document.cookie);
125
+
126
+ if (themeName === 'custom') {
127
+ const storedThemeJson = localStorage.getItem('vuetiful-custom-theme');
128
+ let storedTheme: Theme | null = null;
129
+
130
+ try {
131
+ storedTheme = storedThemeJson ? JSON.parse(storedThemeJson) : null;
132
+ if (storedTheme) {
133
+ applyTheme(storedTheme, callback);
134
+ registerTheme(storedTheme);
135
+ }
136
+ } catch (e) {
137
+ applyTheme(themes[0], callback);
138
+ }
139
+ } else {
140
+ const theme = themes.find((t) => t.name === themeName);
141
+ if (theme) {
142
+ applyTheme(theme, callback);
143
+ } else {
144
+ applyTheme(themes[0], callback);
145
+ }
146
+ }
147
+ }
90
148
  };
91
149
 
92
- const overwriteThemes = (newThemes: Array<Theme>): void => {
93
- themes.value = [...newThemes];
150
+ const registerTheme = (theme: Theme): void => {
151
+ const existingTheme = themes.find((t) => t.name === theme.name);
152
+ if (existingTheme) {
153
+ const index = themes.indexOf(existingTheme);
154
+ themes[index] = theme;
155
+ } else {
156
+ themes.push(theme);
157
+ }
94
158
  };
95
159
 
96
160
  return {
97
- themes: readonly(themes),
98
- chosenTheme: readonly(chosenTheme),
99
-
161
+ chosenTheme,
162
+ themes,
163
+ THEME,
164
+ applyThemeSSR,
165
+ applyTheme,
166
+ getThemeFromCookie,
100
167
  initializeTheme,
101
- loadTheme,
102
- saveThemeToStorage,
103
-
104
- THEMES,
105
- overwriteThemes,
106
- registerAllBuiltInThemes,
168
+ changeDataTheme,
107
169
  registerTheme,
108
170
  };
109
171
  };