svelte-theme-picker 1.0.0 → 1.1.0
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.
- package/README.md +210 -0
- package/dist/ThemeHead.svelte +69 -0
- package/dist/ThemeHead.svelte.d.ts +20 -0
- package/dist/ThemePicker.svelte +50 -15
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -0
- package/dist/ssr.d.ts +42 -0
- package/dist/ssr.js +239 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +23 -1
- package/dist/themes.js +10 -0
- package/dist/types.d.ts +47 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,155 @@ interface ThemePickerConfig {
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
## SSR Support (Preventing Flash)
|
|
78
|
+
|
|
79
|
+
When using SvelteKit with SSR, themes are applied after JavaScript loads, causing a flash of unstyled content (FOUC). Use the `ThemeHead` component or SSR utilities to prevent this.
|
|
80
|
+
|
|
81
|
+
### Using ThemeHead Component (Recommended)
|
|
82
|
+
|
|
83
|
+
The easiest way to prevent theme flash in SvelteKit:
|
|
84
|
+
|
|
85
|
+
```svelte
|
|
86
|
+
<!-- src/routes/+layout.svelte -->
|
|
87
|
+
<script>
|
|
88
|
+
import { ThemeHead, ThemePicker, defaultThemes } from 'svelte-theme-picker';
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<ThemeHead
|
|
92
|
+
themes={defaultThemes}
|
|
93
|
+
storageKey="my-app-theme"
|
|
94
|
+
defaultTheme="dreamy"
|
|
95
|
+
preloadFonts={true}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<ThemePicker config={{ themes: defaultThemes, storageKey: 'my-app-theme' }} />
|
|
99
|
+
|
|
100
|
+
<slot />
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The `ThemeHead` component:
|
|
104
|
+
- Injects a blocking script that applies CSS variables before first paint
|
|
105
|
+
- Adds `no-transitions` class to prevent transition animations during hydration
|
|
106
|
+
- Optionally preloads Google Fonts for all themes
|
|
107
|
+
|
|
108
|
+
### ThemeHead Props
|
|
109
|
+
|
|
110
|
+
| Prop | Type | Default | Description |
|
|
111
|
+
|------|------|---------|-------------|
|
|
112
|
+
| `themes` | `Record<string, Theme>` | required | All available themes |
|
|
113
|
+
| `storageKey` | `string` | `'svelte-theme-picker'` | localStorage key |
|
|
114
|
+
| `defaultTheme` | `string` | first theme | Default theme ID |
|
|
115
|
+
| `cssVarPrefix` | `string` | `''` | CSS variable prefix |
|
|
116
|
+
| `preventTransitions` | `boolean` | `true` | Prevent transition animations during hydration |
|
|
117
|
+
| `preloadFonts` | `boolean` | `false` | Enable Google Fonts preloading |
|
|
118
|
+
| `fontConfig` | `FontConfig` | `{ provider: 'google' }` | Font preloading configuration |
|
|
119
|
+
|
|
120
|
+
### Using SSR Utilities Directly
|
|
121
|
+
|
|
122
|
+
For more control, use the SSR utilities to generate blocking scripts:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// src/hooks.server.ts
|
|
126
|
+
import { generateSSRHead } from 'svelte-theme-picker';
|
|
127
|
+
import { myThemes } from './themes';
|
|
128
|
+
|
|
129
|
+
export async function handle({ event, resolve }) {
|
|
130
|
+
return resolve(event, {
|
|
131
|
+
transformPageChunk: ({ html }) => {
|
|
132
|
+
const ssrHead = generateSSRHead({
|
|
133
|
+
themes: myThemes,
|
|
134
|
+
storageKey: 'my-app-theme',
|
|
135
|
+
defaultTheme: 'dreamy',
|
|
136
|
+
});
|
|
137
|
+
return html.replace('</head>', `${ssrHead}</head>`);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Removing No-Transitions Class
|
|
144
|
+
|
|
145
|
+
After hydration, remove the `no-transitions` class to enable animations:
|
|
146
|
+
|
|
147
|
+
```svelte
|
|
148
|
+
<!-- src/routes/+layout.svelte -->
|
|
149
|
+
<script>
|
|
150
|
+
import { browser } from '$app/environment';
|
|
151
|
+
import { onMount } from 'svelte';
|
|
152
|
+
|
|
153
|
+
onMount(() => {
|
|
154
|
+
// Small delay to ensure hydration is complete
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
document.documentElement.classList.remove('no-transitions');
|
|
157
|
+
}, 50);
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### SSR Utility Functions
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import {
|
|
166
|
+
generateBlockingScript, // Generate minified blocking script string
|
|
167
|
+
generateSSRHead, // Generate complete head HTML (script + styles + fonts)
|
|
168
|
+
applyThemeToElement, // Apply theme to an element (sync, for blocking scripts)
|
|
169
|
+
getThemeCSS, // Get CSS variable declarations as string
|
|
170
|
+
extractFonts, // Extract font names from a theme
|
|
171
|
+
generateFontPreloadLinks, // Generate Google Fonts preload links
|
|
172
|
+
themeSchema, // CSS variable mapping (for consistency)
|
|
173
|
+
} from 'svelte-theme-picker';
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Cross-Tab Synchronization
|
|
177
|
+
|
|
178
|
+
Enable automatic theme sync across browser tabs:
|
|
179
|
+
|
|
180
|
+
```svelte
|
|
181
|
+
<script>
|
|
182
|
+
import { createThemeStore, ThemePicker } from 'svelte-theme-picker';
|
|
183
|
+
|
|
184
|
+
const store = createThemeStore({
|
|
185
|
+
syncTabs: true, // Enable cross-tab sync
|
|
186
|
+
storageKey: 'my-app-theme',
|
|
187
|
+
});
|
|
188
|
+
</script>
|
|
189
|
+
|
|
190
|
+
<ThemePicker store={store} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
When a user changes the theme in one tab, all other tabs will automatically update. The store listens for `storage` events and syncs the theme state.
|
|
194
|
+
|
|
195
|
+
To clean up listeners when done:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
store.destroy(); // Removes storage event listener
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## External Store Synchronization
|
|
202
|
+
|
|
203
|
+
If you're using an external store and the `ThemePicker` doesn't update when the store changes externally, use the `{#key}` pattern to force a re-render:
|
|
204
|
+
|
|
205
|
+
```svelte
|
|
206
|
+
<script>
|
|
207
|
+
import { ThemePicker, createThemeStore } from 'svelte-theme-picker';
|
|
208
|
+
|
|
209
|
+
const themeStore = createThemeStore({ /* config */ });
|
|
210
|
+
let currentThemeId = $state('dreamy');
|
|
211
|
+
|
|
212
|
+
// Subscribe to track external changes
|
|
213
|
+
themeStore.subscribe((themeId) => {
|
|
214
|
+
currentThemeId = themeId;
|
|
215
|
+
});
|
|
216
|
+
</script>
|
|
217
|
+
|
|
218
|
+
<!-- Force re-render when theme changes externally -->
|
|
219
|
+
{#key currentThemeId}
|
|
220
|
+
<ThemePicker store={themeStore} />
|
|
221
|
+
{/key}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
> **Note**: The `ThemePicker` component captures its configuration once at mount. This is intentional for performance. Use `{#key}` to create a new instance when props need to change.
|
|
225
|
+
|
|
77
226
|
## Headless Mode (No UI)
|
|
78
227
|
|
|
79
228
|
The `ThemePicker` component is completely optional. You can use just the store and utilities for full programmatic control without rendering any UI. This is useful when:
|
|
@@ -569,6 +718,57 @@ You can provide your own themes:
|
|
|
569
718
|
<ThemePicker config={{ themes: myThemes, defaultTheme: 'my-theme' }} />
|
|
570
719
|
```
|
|
571
720
|
|
|
721
|
+
## Styling the Picker
|
|
722
|
+
|
|
723
|
+
The `ThemePicker` component can be customized using CSS custom properties. The picker automatically uses your theme's CSS variables as fallbacks, so it adapts to your theme.
|
|
724
|
+
|
|
725
|
+
### Picker CSS Properties
|
|
726
|
+
|
|
727
|
+
Override these to customize the picker appearance:
|
|
728
|
+
|
|
729
|
+
```css
|
|
730
|
+
/* In your global CSS or :root */
|
|
731
|
+
:root {
|
|
732
|
+
/* Colors */
|
|
733
|
+
--stp-bg: var(--bg-card); /* Panel background */
|
|
734
|
+
--stp-bg-hover: var(--bg-glow); /* Hover state background */
|
|
735
|
+
--stp-bg-active: ...; /* Active/selected item */
|
|
736
|
+
--stp-border: ...; /* Border color */
|
|
737
|
+
--stp-text: var(--text-primary); /* Primary text */
|
|
738
|
+
--stp-text-muted: var(--text-muted);/* Secondary text */
|
|
739
|
+
--stp-accent: var(--accent-1); /* Accent color */
|
|
740
|
+
--stp-accent-glow: ...; /* Glow effect color */
|
|
741
|
+
|
|
742
|
+
/* Trigger button */
|
|
743
|
+
--stp-trigger-bg: ...; /* Trigger background gradient */
|
|
744
|
+
--stp-trigger-color: var(--bg-deep);/* Trigger icon color */
|
|
745
|
+
|
|
746
|
+
/* Layout */
|
|
747
|
+
--stp-radius: 12px; /* Panel border radius */
|
|
748
|
+
--stp-radius-sm: 8px; /* Item border radius */
|
|
749
|
+
--stp-space: 12px; /* Standard spacing */
|
|
750
|
+
--stp-space-sm: 8px; /* Small spacing */
|
|
751
|
+
--stp-transition: 0.2s ease; /* Transition timing */
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
The picker uses `color-mix()` for calculated colors (active states, borders) that automatically adjust to your theme. No `!important` overrides should be needed.
|
|
756
|
+
|
|
757
|
+
### Theme Mode
|
|
758
|
+
|
|
759
|
+
Themes can declare a `mode` property (`'light'` or `'dark'`) to help the picker adjust its styling:
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
const myTheme: Theme = {
|
|
763
|
+
name: 'My Light Theme',
|
|
764
|
+
description: 'A light theme',
|
|
765
|
+
mode: 'light', // or 'dark'
|
|
766
|
+
colors: { ... },
|
|
767
|
+
fonts: { ... },
|
|
768
|
+
effects: { ... },
|
|
769
|
+
};
|
|
770
|
+
```
|
|
771
|
+
|
|
572
772
|
## CSS Variables
|
|
573
773
|
|
|
574
774
|
The theme picker applies these CSS variables to your document:
|
|
@@ -669,18 +869,28 @@ Full TypeScript support with exported types:
|
|
|
669
869
|
|
|
670
870
|
```typescript
|
|
671
871
|
import type {
|
|
872
|
+
// Theme types
|
|
672
873
|
Theme,
|
|
673
874
|
ThemeColors,
|
|
674
875
|
ThemeFonts,
|
|
675
876
|
ThemeEffects,
|
|
676
877
|
ThemePickerConfig,
|
|
878
|
+
// Catalog types
|
|
677
879
|
ThemeCatalog,
|
|
678
880
|
ThemeCatalogEntry,
|
|
679
881
|
ThemeMeta,
|
|
680
882
|
ThemeFilterOptions,
|
|
883
|
+
// SSR types
|
|
884
|
+
SSRConfig,
|
|
885
|
+
FontConfig,
|
|
886
|
+
ThemeSchema,
|
|
681
887
|
} from 'svelte-theme-picker';
|
|
682
888
|
```
|
|
683
889
|
|
|
890
|
+
## Migration Guide
|
|
891
|
+
|
|
892
|
+
Upgrading from a previous version? See [MIGRATION.md](./MIGRATION.md) for breaking changes and migration steps.
|
|
893
|
+
|
|
684
894
|
## License
|
|
685
895
|
|
|
686
896
|
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { generateBlockingScript, extractFonts, generateFontPreloadLinks } from './ssr.js';
|
|
3
|
+
import type { Theme, FontConfig } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** All available themes */
|
|
7
|
+
themes: Record<string, Theme>;
|
|
8
|
+
/** localStorage key for theme persistence */
|
|
9
|
+
storageKey?: string;
|
|
10
|
+
/** Default theme ID if none is stored */
|
|
11
|
+
defaultTheme?: string;
|
|
12
|
+
/** CSS variable prefix */
|
|
13
|
+
cssVarPrefix?: string;
|
|
14
|
+
/** Whether to prevent transitions during hydration (default: true) */
|
|
15
|
+
preventTransitions?: boolean;
|
|
16
|
+
/** Enable font preloading */
|
|
17
|
+
preloadFonts?: boolean;
|
|
18
|
+
/** Font configuration for preloading */
|
|
19
|
+
fontConfig?: FontConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
themes,
|
|
24
|
+
storageKey = 'svelte-theme-picker',
|
|
25
|
+
defaultTheme,
|
|
26
|
+
cssVarPrefix = '',
|
|
27
|
+
preventTransitions = true,
|
|
28
|
+
preloadFonts = false,
|
|
29
|
+
fontConfig = { provider: 'google' },
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
// Generate the blocking script
|
|
33
|
+
const blockingScript = $derived(generateBlockingScript({
|
|
34
|
+
themes,
|
|
35
|
+
storageKey,
|
|
36
|
+
defaultTheme,
|
|
37
|
+
cssVarPrefix,
|
|
38
|
+
preventTransitions,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// Generate font preload links if enabled
|
|
42
|
+
const fontLinks = $derived.by(() => {
|
|
43
|
+
if (!preloadFonts) return '';
|
|
44
|
+
|
|
45
|
+
const allFonts = new Set<string>();
|
|
46
|
+
for (const theme of Object.values(themes)) {
|
|
47
|
+
for (const font of extractFonts(theme)) {
|
|
48
|
+
allFonts.add(font);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return generateFontPreloadLinks([...allFonts], fontConfig);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// No-transitions CSS
|
|
55
|
+
const noTransitionsCSS = '.no-transitions,.no-transitions *,.no-transitions *::before,.no-transitions *::after{transition:none!important;animation:none!important}';
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<svelte:head>
|
|
59
|
+
{#if preloadFonts && fontLinks}
|
|
60
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
61
|
+
{@html fontLinks}
|
|
62
|
+
{/if}
|
|
63
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
64
|
+
{@html `<script>${blockingScript}</script>`}
|
|
65
|
+
{#if preventTransitions}
|
|
66
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
67
|
+
{@html `<style>${noTransitionsCSS}</style>`}
|
|
68
|
+
{/if}
|
|
69
|
+
</svelte:head>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Theme, FontConfig } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** All available themes */
|
|
4
|
+
themes: Record<string, Theme>;
|
|
5
|
+
/** localStorage key for theme persistence */
|
|
6
|
+
storageKey?: string;
|
|
7
|
+
/** Default theme ID if none is stored */
|
|
8
|
+
defaultTheme?: string;
|
|
9
|
+
/** CSS variable prefix */
|
|
10
|
+
cssVarPrefix?: string;
|
|
11
|
+
/** Whether to prevent transitions during hydration (default: true) */
|
|
12
|
+
preventTransitions?: boolean;
|
|
13
|
+
/** Enable font preloading */
|
|
14
|
+
preloadFonts?: boolean;
|
|
15
|
+
/** Font configuration for preloading */
|
|
16
|
+
fontConfig?: FontConfig;
|
|
17
|
+
}
|
|
18
|
+
declare const ThemeHead: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type ThemeHead = ReturnType<typeof ThemeHead>;
|
|
20
|
+
export default ThemeHead;
|
package/dist/ThemePicker.svelte
CHANGED
|
@@ -171,15 +171,50 @@
|
|
|
171
171
|
</div>
|
|
172
172
|
|
|
173
173
|
<style>
|
|
174
|
+
/*
|
|
175
|
+
* CSS Custom Properties for ThemePicker
|
|
176
|
+
* Override these to customize the picker appearance:
|
|
177
|
+
*
|
|
178
|
+
* Colors:
|
|
179
|
+
* --stp-bg : Panel background color
|
|
180
|
+
* --stp-bg-hover : Hover state background
|
|
181
|
+
* --stp-bg-active : Active/selected item background
|
|
182
|
+
* --stp-border : Border color
|
|
183
|
+
* --stp-text : Primary text color
|
|
184
|
+
* --stp-text-muted : Secondary/muted text color
|
|
185
|
+
* --stp-accent : Accent color (trigger, scrollbar, checks)
|
|
186
|
+
* --stp-accent-glow : Glow effect color for trigger
|
|
187
|
+
*
|
|
188
|
+
* Trigger button:
|
|
189
|
+
* --stp-trigger-bg : Trigger button background
|
|
190
|
+
* --stp-trigger-color : Trigger button icon color
|
|
191
|
+
*
|
|
192
|
+
* Layout:
|
|
193
|
+
* --stp-radius : Panel border radius
|
|
194
|
+
* --stp-radius-sm : Button/item border radius
|
|
195
|
+
* --stp-space : Standard spacing
|
|
196
|
+
* --stp-space-sm : Small spacing
|
|
197
|
+
* --stp-transition : Transition duration/easing
|
|
198
|
+
*
|
|
199
|
+
* Example usage:
|
|
200
|
+
* :root {
|
|
201
|
+
* --stp-bg: var(--bg-card);
|
|
202
|
+
* --stp-text: var(--text-primary);
|
|
203
|
+
* --stp-accent: var(--accent-1);
|
|
204
|
+
* }
|
|
205
|
+
*/
|
|
174
206
|
.stp-theme-picker {
|
|
175
|
-
|
|
176
|
-
--stp-bg
|
|
177
|
-
--stp-bg-
|
|
178
|
-
--stp-
|
|
179
|
-
--stp-
|
|
180
|
-
--stp-text
|
|
181
|
-
--stp-
|
|
182
|
-
--stp-accent
|
|
207
|
+
/* Fallback to theme variables, then to default values */
|
|
208
|
+
--stp-bg: var(--bg-card, #2a2a4a);
|
|
209
|
+
--stp-bg-hover: var(--bg-glow, #3d3d6b);
|
|
210
|
+
--stp-bg-active: color-mix(in srgb, var(--stp-accent, var(--accent-1, #a855f7)) 15%, transparent);
|
|
211
|
+
--stp-border: color-mix(in srgb, var(--stp-text, var(--text-primary, #e8e0f0)) 10%, transparent);
|
|
212
|
+
--stp-text: var(--text-primary, #e8e0f0);
|
|
213
|
+
--stp-text-muted: var(--text-muted, #9090b0);
|
|
214
|
+
--stp-accent: var(--accent-1, #a855f7);
|
|
215
|
+
--stp-accent-glow: color-mix(in srgb, var(--stp-accent) 30%, transparent);
|
|
216
|
+
--stp-trigger-bg: linear-gradient(135deg, var(--primary-3, #c9a0dc), var(--stp-accent));
|
|
217
|
+
--stp-trigger-color: var(--bg-deep, #1a1a2e);
|
|
183
218
|
--stp-radius: 12px;
|
|
184
219
|
--stp-radius-sm: 8px;
|
|
185
220
|
--stp-space: 12px;
|
|
@@ -221,15 +256,15 @@
|
|
|
221
256
|
height: 48px;
|
|
222
257
|
border-radius: 50%;
|
|
223
258
|
border: none;
|
|
224
|
-
background:
|
|
225
|
-
color:
|
|
259
|
+
background: var(--stp-trigger-bg);
|
|
260
|
+
color: var(--stp-trigger-color);
|
|
226
261
|
cursor: pointer;
|
|
227
262
|
display: flex;
|
|
228
263
|
align-items: center;
|
|
229
264
|
justify-content: center;
|
|
230
265
|
box-shadow:
|
|
231
266
|
0 4px 20px var(--stp-accent-glow),
|
|
232
|
-
0 0 40px
|
|
267
|
+
0 0 40px color-mix(in srgb, var(--stp-accent) 10%, transparent);
|
|
233
268
|
transition: transform var(--stp-transition), box-shadow var(--stp-transition);
|
|
234
269
|
/* Performance: Hint browser to prepare GPU layer for animations */
|
|
235
270
|
will-change: transform;
|
|
@@ -239,7 +274,7 @@
|
|
|
239
274
|
transform: scale(1.1);
|
|
240
275
|
box-shadow:
|
|
241
276
|
0 8px 30px var(--stp-accent-glow),
|
|
242
|
-
0 0 60px
|
|
277
|
+
0 0 60px color-mix(in srgb, var(--stp-accent) 20%, transparent);
|
|
243
278
|
}
|
|
244
279
|
|
|
245
280
|
.stp-backdrop {
|
|
@@ -251,7 +286,7 @@
|
|
|
251
286
|
.stp-panel {
|
|
252
287
|
position: absolute;
|
|
253
288
|
width: 320px;
|
|
254
|
-
background:
|
|
289
|
+
background: color-mix(in srgb, var(--stp-bg) 95%, transparent);
|
|
255
290
|
backdrop-filter: blur(16px);
|
|
256
291
|
-webkit-backdrop-filter: blur(16px); /* Safari support */
|
|
257
292
|
border-radius: var(--stp-radius);
|
|
@@ -409,7 +444,7 @@
|
|
|
409
444
|
}
|
|
410
445
|
|
|
411
446
|
.stp-list::-webkit-scrollbar-thumb {
|
|
412
|
-
background:
|
|
447
|
+
background: color-mix(in srgb, var(--stp-accent) 50%, transparent);
|
|
413
448
|
border-radius: 3px;
|
|
414
449
|
}
|
|
415
450
|
|
|
@@ -482,7 +517,7 @@
|
|
|
482
517
|
|
|
483
518
|
.stp-check {
|
|
484
519
|
flex-shrink: 0;
|
|
485
|
-
color: #8ad4d4;
|
|
520
|
+
color: var(--accent-2, #8ad4d4);
|
|
486
521
|
}
|
|
487
522
|
|
|
488
523
|
@media (max-width: 640px) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { default as ThemePicker } from './ThemePicker.svelte';
|
|
2
|
-
export
|
|
2
|
+
export { default as ThemeHead } from './ThemeHead.svelte';
|
|
3
|
+
export type { Theme, ThemeColors, ThemeFonts, ThemeEffects, ThemePickerConfig, ThemeMeta, ThemeCatalogEntry, ThemeCatalog, ThemeFilterOptions, SSRConfig, FontConfig, ThemeSchema, } from './types.js';
|
|
3
4
|
export { createThemeStore, applyTheme, themeStore, type ThemeStore, } from './store.js';
|
|
4
5
|
export { defaultThemes, defaultThemeCatalog, DEFAULT_THEME_ID, dreamy, cyberpunk, sunset, ocean, mono, sakura, aurora, galaxy, milk, light, } from './themes.js';
|
|
5
6
|
export { themesToCatalog, catalogToThemes, filterCatalog, getActiveThemes, getThemesByTag, getThemesByAnyTag, sortCatalog, getCatalogTags, createCatalogEntry, mergeCatalogs, loadCatalogFromJSON, } from './catalog.js';
|
|
7
|
+
export { generateBlockingScript, generateSSRHead, applyThemeToElement, getThemeCSS, extractFonts, generateFontPreloadLinks, themeSchema, } from './ssr.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Components
|
|
2
2
|
export { default as ThemePicker } from './ThemePicker.svelte';
|
|
3
|
+
export { default as ThemeHead } from './ThemeHead.svelte';
|
|
3
4
|
// Store
|
|
4
5
|
export { createThemeStore, applyTheme, themeStore, } from './store.js';
|
|
5
6
|
// Default themes
|
|
6
7
|
export { defaultThemes, defaultThemeCatalog, DEFAULT_THEME_ID, dreamy, cyberpunk, sunset, ocean, mono, sakura, aurora, galaxy, milk, light, } from './themes.js';
|
|
7
8
|
// Catalog utilities
|
|
8
9
|
export { themesToCatalog, catalogToThemes, filterCatalog, getActiveThemes, getThemesByTag, getThemesByAnyTag, sortCatalog, getCatalogTags, createCatalogEntry, mergeCatalogs, loadCatalogFromJSON, } from './catalog.js';
|
|
10
|
+
// SSR utilities
|
|
11
|
+
export { generateBlockingScript, generateSSRHead, applyThemeToElement, getThemeCSS, extractFonts, generateFontPreloadLinks, themeSchema, } from './ssr.js';
|
package/dist/ssr.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Theme, SSRConfig, FontConfig, ThemeSchema } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* CSS variable schema - maps theme properties to CSS variable names.
|
|
4
|
+
* Use this to ensure consistency between blocking scripts and runtime.
|
|
5
|
+
*/
|
|
6
|
+
export declare const themeSchema: ThemeSchema;
|
|
7
|
+
/**
|
|
8
|
+
* Get CSS variable declarations for a theme as a string.
|
|
9
|
+
* Useful for generating inline styles or CSS rules.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getThemeCSS(theme: Theme, prefix?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Apply theme CSS variables to an HTML element synchronously.
|
|
14
|
+
* This is the same logic as the runtime applyTheme but without requestAnimationFrame,
|
|
15
|
+
* making it suitable for use in blocking scripts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyThemeToElement(element: HTMLElement, theme: Theme, prefix?: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Extract font family names from a theme.
|
|
20
|
+
* Parses CSS font-family values to extract the primary font name.
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractFonts(theme: Theme): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Generate Google Fonts preload links for the given font names.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateFontPreloadLinks(fonts: string[], config?: FontConfig): string;
|
|
27
|
+
/**
|
|
28
|
+
* Generate a blocking script that prevents theme flash on page load.
|
|
29
|
+
* This script should be placed in the <head> of your HTML before any other scripts.
|
|
30
|
+
*
|
|
31
|
+
* The script:
|
|
32
|
+
* 1. Reads the stored theme from localStorage
|
|
33
|
+
* 2. Applies CSS variables immediately (before paint)
|
|
34
|
+
* 3. Optionally adds a 'no-transitions' class to prevent transition animations during hydration
|
|
35
|
+
* 4. Optionally preloads fonts for the current theme
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateBlockingScript(config: SSRConfig): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate a complete SSR script tag that can be inserted into HTML.
|
|
40
|
+
* Includes the blocking script and optionally font preload links.
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateSSRHead(config: SSRConfig): string;
|
package/dist/ssr.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS variable schema - maps theme properties to CSS variable names.
|
|
3
|
+
* Use this to ensure consistency between blocking scripts and runtime.
|
|
4
|
+
*/
|
|
5
|
+
export const themeSchema = {
|
|
6
|
+
colors: {
|
|
7
|
+
bgDeep: 'bg-deep',
|
|
8
|
+
bgMid: 'bg-mid',
|
|
9
|
+
bgCard: 'bg-card',
|
|
10
|
+
bgGlow: 'bg-glow',
|
|
11
|
+
bgOverlay: 'bg-overlay',
|
|
12
|
+
primary1: 'primary-1',
|
|
13
|
+
primary2: 'primary-2',
|
|
14
|
+
primary3: 'primary-3',
|
|
15
|
+
primary4: 'primary-4',
|
|
16
|
+
primary5: 'primary-5',
|
|
17
|
+
primary6: 'primary-6',
|
|
18
|
+
accent1: 'accent-1',
|
|
19
|
+
accent2: 'accent-2',
|
|
20
|
+
accent3: 'accent-3',
|
|
21
|
+
textPrimary: 'text-primary',
|
|
22
|
+
textSecondary: 'text-secondary',
|
|
23
|
+
textMuted: 'text-muted',
|
|
24
|
+
},
|
|
25
|
+
fonts: {
|
|
26
|
+
heading: 'font-heading',
|
|
27
|
+
body: 'font-body',
|
|
28
|
+
mono: 'font-mono',
|
|
29
|
+
},
|
|
30
|
+
effects: {
|
|
31
|
+
shadowGlow: 'shadow-glow',
|
|
32
|
+
glowColor: 'glow-color',
|
|
33
|
+
glowIntensity: 'glow-intensity',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Get CSS variable declarations for a theme as a string.
|
|
38
|
+
* Useful for generating inline styles or CSS rules.
|
|
39
|
+
*/
|
|
40
|
+
export function getThemeCSS(theme, prefix = '') {
|
|
41
|
+
const p = prefix ? `${prefix}-` : '';
|
|
42
|
+
const vars = [];
|
|
43
|
+
// Colors
|
|
44
|
+
for (const [key, cssVar] of Object.entries(themeSchema.colors)) {
|
|
45
|
+
const value = theme.colors[key];
|
|
46
|
+
vars.push(`--${p}${cssVar}: ${value}`);
|
|
47
|
+
}
|
|
48
|
+
// Fonts
|
|
49
|
+
for (const [key, cssVar] of Object.entries(themeSchema.fonts)) {
|
|
50
|
+
const value = theme.fonts[key];
|
|
51
|
+
vars.push(`--${p}${cssVar}: ${value}`);
|
|
52
|
+
}
|
|
53
|
+
// Effects
|
|
54
|
+
vars.push(`--${p}${themeSchema.effects.shadowGlow}: 0 0 40px ${theme.effects.glowColor}`);
|
|
55
|
+
vars.push(`--${p}${themeSchema.effects.glowColor}: ${theme.effects.glowColor}`);
|
|
56
|
+
vars.push(`--${p}${themeSchema.effects.glowIntensity}: ${theme.effects.glowIntensity}`);
|
|
57
|
+
return vars.join('; ');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Apply theme CSS variables to an HTML element synchronously.
|
|
61
|
+
* This is the same logic as the runtime applyTheme but without requestAnimationFrame,
|
|
62
|
+
* making it suitable for use in blocking scripts.
|
|
63
|
+
*/
|
|
64
|
+
export function applyThemeToElement(element, theme, prefix = '') {
|
|
65
|
+
const p = prefix ? `${prefix}-` : '';
|
|
66
|
+
// Background colors
|
|
67
|
+
element.style.setProperty(`--${p}bg-deep`, theme.colors.bgDeep);
|
|
68
|
+
element.style.setProperty(`--${p}bg-mid`, theme.colors.bgMid);
|
|
69
|
+
element.style.setProperty(`--${p}bg-card`, theme.colors.bgCard);
|
|
70
|
+
element.style.setProperty(`--${p}bg-glow`, theme.colors.bgGlow);
|
|
71
|
+
element.style.setProperty(`--${p}bg-overlay`, theme.colors.bgOverlay);
|
|
72
|
+
// Primary palette
|
|
73
|
+
element.style.setProperty(`--${p}primary-1`, theme.colors.primary1);
|
|
74
|
+
element.style.setProperty(`--${p}primary-2`, theme.colors.primary2);
|
|
75
|
+
element.style.setProperty(`--${p}primary-3`, theme.colors.primary3);
|
|
76
|
+
element.style.setProperty(`--${p}primary-4`, theme.colors.primary4);
|
|
77
|
+
element.style.setProperty(`--${p}primary-5`, theme.colors.primary5);
|
|
78
|
+
element.style.setProperty(`--${p}primary-6`, theme.colors.primary6);
|
|
79
|
+
// Accent colors
|
|
80
|
+
element.style.setProperty(`--${p}accent-1`, theme.colors.accent1);
|
|
81
|
+
element.style.setProperty(`--${p}accent-2`, theme.colors.accent2);
|
|
82
|
+
element.style.setProperty(`--${p}accent-3`, theme.colors.accent3);
|
|
83
|
+
// Text colors
|
|
84
|
+
element.style.setProperty(`--${p}text-primary`, theme.colors.textPrimary);
|
|
85
|
+
element.style.setProperty(`--${p}text-secondary`, theme.colors.textSecondary);
|
|
86
|
+
element.style.setProperty(`--${p}text-muted`, theme.colors.textMuted);
|
|
87
|
+
// Fonts
|
|
88
|
+
element.style.setProperty(`--${p}font-heading`, theme.fonts.heading);
|
|
89
|
+
element.style.setProperty(`--${p}font-body`, theme.fonts.body);
|
|
90
|
+
element.style.setProperty(`--${p}font-mono`, theme.fonts.mono);
|
|
91
|
+
// Effects
|
|
92
|
+
element.style.setProperty(`--${p}shadow-glow`, `0 0 40px ${theme.effects.glowColor}`);
|
|
93
|
+
element.style.setProperty(`--${p}glow-color`, theme.effects.glowColor);
|
|
94
|
+
element.style.setProperty(`--${p}glow-intensity`, theme.effects.glowIntensity.toString());
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Extract font family names from a theme.
|
|
98
|
+
* Parses CSS font-family values to extract the primary font name.
|
|
99
|
+
*/
|
|
100
|
+
export function extractFonts(theme) {
|
|
101
|
+
const fonts = [];
|
|
102
|
+
const fontValues = [theme.fonts.heading, theme.fonts.body, theme.fonts.mono];
|
|
103
|
+
for (const fontValue of fontValues) {
|
|
104
|
+
// Extract the first font family from a CSS font-family value
|
|
105
|
+
// e.g., "'Quicksand', sans-serif" -> "Quicksand"
|
|
106
|
+
const match = fontValue.match(/^['"]?([^'",]+)/);
|
|
107
|
+
if (match && match[1]) {
|
|
108
|
+
const fontName = match[1].trim();
|
|
109
|
+
// Exclude generic font families
|
|
110
|
+
if (!['sans-serif', 'serif', 'monospace', 'cursive', 'fantasy', 'system-ui'].includes(fontName.toLowerCase())) {
|
|
111
|
+
if (!fonts.includes(fontName)) {
|
|
112
|
+
fonts.push(fontName);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return fonts;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Generate Google Fonts preload links for the given font names.
|
|
121
|
+
*/
|
|
122
|
+
export function generateFontPreloadLinks(fonts, config = {}) {
|
|
123
|
+
const { provider = 'google', weights = {} } = config;
|
|
124
|
+
if (provider !== 'google' || fonts.length === 0) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
const defaultWeights = {
|
|
128
|
+
heading: weights.heading || [400, 600, 700],
|
|
129
|
+
body: weights.body || [400, 500],
|
|
130
|
+
mono: weights.mono || [400],
|
|
131
|
+
};
|
|
132
|
+
// For simplicity, use all weights for each font
|
|
133
|
+
const allWeights = [...new Set([
|
|
134
|
+
...defaultWeights.heading,
|
|
135
|
+
...defaultWeights.body,
|
|
136
|
+
...defaultWeights.mono,
|
|
137
|
+
])].sort((a, b) => a - b);
|
|
138
|
+
const fontSpecs = fonts.map(font => {
|
|
139
|
+
const weightStr = allWeights.join(';');
|
|
140
|
+
return `family=${encodeURIComponent(font)}:wght@${weightStr}`;
|
|
141
|
+
});
|
|
142
|
+
const url = `https://fonts.googleapis.com/css2?${fontSpecs.join('&')}&display=swap`;
|
|
143
|
+
return `<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
144
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
145
|
+
<link rel="stylesheet" href="${url}">`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Generate a blocking script that prevents theme flash on page load.
|
|
149
|
+
* This script should be placed in the <head> of your HTML before any other scripts.
|
|
150
|
+
*
|
|
151
|
+
* The script:
|
|
152
|
+
* 1. Reads the stored theme from localStorage
|
|
153
|
+
* 2. Applies CSS variables immediately (before paint)
|
|
154
|
+
* 3. Optionally adds a 'no-transitions' class to prevent transition animations during hydration
|
|
155
|
+
* 4. Optionally preloads fonts for the current theme
|
|
156
|
+
*/
|
|
157
|
+
export function generateBlockingScript(config) {
|
|
158
|
+
const { themes, storageKey = 'svelte-theme-picker', defaultTheme, cssVarPrefix = '', preventTransitions = true, } = config;
|
|
159
|
+
// Determine the actual default theme
|
|
160
|
+
const themeIds = Object.keys(themes);
|
|
161
|
+
const fallbackDefault = defaultTheme && themes[defaultTheme] ? defaultTheme : themeIds[0];
|
|
162
|
+
// Serialize themes for the blocking script (only what's needed: colors, fonts, effects)
|
|
163
|
+
const minimalThemes = {};
|
|
164
|
+
for (const [id, theme] of Object.entries(themes)) {
|
|
165
|
+
minimalThemes[id] = {
|
|
166
|
+
colors: theme.colors,
|
|
167
|
+
fonts: theme.fonts,
|
|
168
|
+
effects: theme.effects,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const themesJson = JSON.stringify(minimalThemes);
|
|
172
|
+
const prefix = cssVarPrefix ? `${cssVarPrefix}-` : '';
|
|
173
|
+
// Generate the blocking script
|
|
174
|
+
const script = `(function(){
|
|
175
|
+
var T=${themesJson};
|
|
176
|
+
var k="${storageKey}";
|
|
177
|
+
var d="${fallbackDefault}";
|
|
178
|
+
var p="${prefix}";
|
|
179
|
+
var r=document.documentElement;
|
|
180
|
+
${preventTransitions ? 'r.classList.add("no-transitions");' : ''}
|
|
181
|
+
try{var s=localStorage.getItem(k);if(!s||!T[s])s=d;}catch(e){s=d;}
|
|
182
|
+
var t=T[s];if(t){
|
|
183
|
+
var c=t.colors,f=t.fonts,e=t.effects;
|
|
184
|
+
r.style.setProperty("--"+p+"bg-deep",c.bgDeep);
|
|
185
|
+
r.style.setProperty("--"+p+"bg-mid",c.bgMid);
|
|
186
|
+
r.style.setProperty("--"+p+"bg-card",c.bgCard);
|
|
187
|
+
r.style.setProperty("--"+p+"bg-glow",c.bgGlow);
|
|
188
|
+
r.style.setProperty("--"+p+"bg-overlay",c.bgOverlay);
|
|
189
|
+
r.style.setProperty("--"+p+"primary-1",c.primary1);
|
|
190
|
+
r.style.setProperty("--"+p+"primary-2",c.primary2);
|
|
191
|
+
r.style.setProperty("--"+p+"primary-3",c.primary3);
|
|
192
|
+
r.style.setProperty("--"+p+"primary-4",c.primary4);
|
|
193
|
+
r.style.setProperty("--"+p+"primary-5",c.primary5);
|
|
194
|
+
r.style.setProperty("--"+p+"primary-6",c.primary6);
|
|
195
|
+
r.style.setProperty("--"+p+"accent-1",c.accent1);
|
|
196
|
+
r.style.setProperty("--"+p+"accent-2",c.accent2);
|
|
197
|
+
r.style.setProperty("--"+p+"accent-3",c.accent3);
|
|
198
|
+
r.style.setProperty("--"+p+"text-primary",c.textPrimary);
|
|
199
|
+
r.style.setProperty("--"+p+"text-secondary",c.textSecondary);
|
|
200
|
+
r.style.setProperty("--"+p+"text-muted",c.textMuted);
|
|
201
|
+
r.style.setProperty("--"+p+"font-heading",f.heading);
|
|
202
|
+
r.style.setProperty("--"+p+"font-body",f.body);
|
|
203
|
+
r.style.setProperty("--"+p+"font-mono",f.mono);
|
|
204
|
+
r.style.setProperty("--"+p+"shadow-glow","0 0 40px "+e.glowColor);
|
|
205
|
+
r.style.setProperty("--"+p+"glow-color",e.glowColor);
|
|
206
|
+
r.style.setProperty("--"+p+"glow-intensity",e.glowIntensity);
|
|
207
|
+
r.setAttribute("data-theme",s);
|
|
208
|
+
}})();`;
|
|
209
|
+
return script;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Generate a complete SSR script tag that can be inserted into HTML.
|
|
213
|
+
* Includes the blocking script and optionally font preload links.
|
|
214
|
+
*/
|
|
215
|
+
export function generateSSRHead(config) {
|
|
216
|
+
const { themes, fonts } = config;
|
|
217
|
+
const parts = [];
|
|
218
|
+
// Add font preload links if configured
|
|
219
|
+
if (fonts) {
|
|
220
|
+
// Get all unique fonts from all themes
|
|
221
|
+
const allFonts = new Set();
|
|
222
|
+
for (const theme of Object.values(themes)) {
|
|
223
|
+
for (const font of extractFonts(theme)) {
|
|
224
|
+
allFonts.add(font);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const fontLinks = generateFontPreloadLinks([...allFonts], fonts);
|
|
228
|
+
if (fontLinks) {
|
|
229
|
+
parts.push(fontLinks);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Add the blocking script
|
|
233
|
+
parts.push(`<script>${generateBlockingScript(config)}</script>`);
|
|
234
|
+
// Add no-transitions CSS if enabled
|
|
235
|
+
if (config.preventTransitions !== false) {
|
|
236
|
+
parts.push(`<style>.no-transitions,.no-transitions *,.no-transitions *::before,.no-transitions *::after{transition:none!important;animation:none!important}</style>`);
|
|
237
|
+
}
|
|
238
|
+
return parts.join('\n');
|
|
239
|
+
}
|
package/dist/store.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface ThemeStore extends Writable<string> {
|
|
|
20
20
|
revertPreview: () => void;
|
|
21
21
|
/** Check if currently in preview mode */
|
|
22
22
|
isPreviewMode: () => boolean;
|
|
23
|
+
/** Destroy the store and clean up any listeners (e.g., cross-tab sync) */
|
|
24
|
+
destroy: () => void;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Create a theme store with the given configuration
|
package/dist/store.js
CHANGED
|
@@ -5,7 +5,7 @@ const isBrowser = typeof window !== 'undefined';
|
|
|
5
5
|
* Create a theme store with the given configuration
|
|
6
6
|
*/
|
|
7
7
|
export function createThemeStore(config = {}) {
|
|
8
|
-
const { storageKey = 'svelte-theme-picker', defaultTheme = DEFAULT_THEME_ID, themes = defaultThemes, } = config;
|
|
8
|
+
const { storageKey = 'svelte-theme-picker', defaultTheme = DEFAULT_THEME_ID, themes = defaultThemes, syncTabs = false, } = config;
|
|
9
9
|
function getInitialTheme() {
|
|
10
10
|
if (isBrowser) {
|
|
11
11
|
try {
|
|
@@ -66,7 +66,29 @@ export function createThemeStore(config = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
isPreviewMode: () => previewMode,
|
|
69
|
+
destroy: () => {
|
|
70
|
+
if (storageEventHandler) {
|
|
71
|
+
window.removeEventListener('storage', storageEventHandler);
|
|
72
|
+
storageEventHandler = null;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
69
75
|
};
|
|
76
|
+
// Cross-tab synchronization via storage events
|
|
77
|
+
let storageEventHandler = null;
|
|
78
|
+
if (syncTabs && isBrowser) {
|
|
79
|
+
storageEventHandler = (event) => {
|
|
80
|
+
if (event.key === storageKey && event.newValue) {
|
|
81
|
+
const newThemeId = event.newValue;
|
|
82
|
+
if (themes[newThemeId]) {
|
|
83
|
+
persistedThemeId = newThemeId;
|
|
84
|
+
currentThemeId = newThemeId;
|
|
85
|
+
previewMode = false;
|
|
86
|
+
set(newThemeId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
window.addEventListener('storage', storageEventHandler);
|
|
91
|
+
}
|
|
70
92
|
return store;
|
|
71
93
|
}
|
|
72
94
|
/**
|
package/dist/themes.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export const dreamy = {
|
|
5
5
|
name: 'Dreamy',
|
|
6
6
|
description: 'Soft pastels with dreamy atmosphere',
|
|
7
|
+
mode: 'dark',
|
|
7
8
|
colors: {
|
|
8
9
|
bgDeep: '#1a1a2e',
|
|
9
10
|
bgMid: '#232342',
|
|
@@ -42,6 +43,7 @@ export const dreamy = {
|
|
|
42
43
|
export const cyberpunk = {
|
|
43
44
|
name: 'Cyberpunk',
|
|
44
45
|
description: 'High contrast neons against dark backgrounds',
|
|
46
|
+
mode: 'dark',
|
|
45
47
|
colors: {
|
|
46
48
|
bgDeep: '#0a0a0f',
|
|
47
49
|
bgMid: '#12121a',
|
|
@@ -80,6 +82,7 @@ export const cyberpunk = {
|
|
|
80
82
|
export const sunset = {
|
|
81
83
|
name: 'Sunset',
|
|
82
84
|
description: 'Warm oranges and purples like a summer sunset',
|
|
85
|
+
mode: 'dark',
|
|
83
86
|
colors: {
|
|
84
87
|
bgDeep: '#1a1020',
|
|
85
88
|
bgMid: '#2a1830',
|
|
@@ -118,6 +121,7 @@ export const sunset = {
|
|
|
118
121
|
export const ocean = {
|
|
119
122
|
name: 'Ocean',
|
|
120
123
|
description: 'Deep blues and teals with bioluminescent accents',
|
|
124
|
+
mode: 'dark',
|
|
121
125
|
colors: {
|
|
122
126
|
bgDeep: '#0a1628',
|
|
123
127
|
bgMid: '#0f2035',
|
|
@@ -156,6 +160,7 @@ export const ocean = {
|
|
|
156
160
|
export const mono = {
|
|
157
161
|
name: 'Mono',
|
|
158
162
|
description: 'Clean monochromatic design with subtle accents',
|
|
163
|
+
mode: 'dark',
|
|
159
164
|
colors: {
|
|
160
165
|
bgDeep: '#111111',
|
|
161
166
|
bgMid: '#1a1a1a',
|
|
@@ -194,6 +199,7 @@ export const mono = {
|
|
|
194
199
|
export const sakura = {
|
|
195
200
|
name: 'Sakura',
|
|
196
201
|
description: 'Delicate cherry blossom pinks with spring greens',
|
|
202
|
+
mode: 'dark',
|
|
197
203
|
colors: {
|
|
198
204
|
bgDeep: '#1a1520',
|
|
199
205
|
bgMid: '#251d28',
|
|
@@ -232,6 +238,7 @@ export const sakura = {
|
|
|
232
238
|
export const aurora = {
|
|
233
239
|
name: 'Aurora',
|
|
234
240
|
description: 'Mystical aurora borealis dancing across the sky',
|
|
241
|
+
mode: 'dark',
|
|
235
242
|
colors: {
|
|
236
243
|
bgDeep: '#0a0f1a',
|
|
237
244
|
bgMid: '#0f1725',
|
|
@@ -270,6 +277,7 @@ export const aurora = {
|
|
|
270
277
|
export const galaxy = {
|
|
271
278
|
name: 'Galaxy',
|
|
272
279
|
description: 'Deep space with distant stars and cosmic nebulae',
|
|
280
|
+
mode: 'dark',
|
|
273
281
|
colors: {
|
|
274
282
|
bgDeep: '#05050f',
|
|
275
283
|
bgMid: '#0a0a1a',
|
|
@@ -308,6 +316,7 @@ export const galaxy = {
|
|
|
308
316
|
export const milk = {
|
|
309
317
|
name: 'Milk',
|
|
310
318
|
description: 'Clean and creamy whites with warm neutral accents',
|
|
319
|
+
mode: 'light',
|
|
311
320
|
colors: {
|
|
312
321
|
bgDeep: '#fefefe',
|
|
313
322
|
bgMid: '#faf9f7',
|
|
@@ -346,6 +355,7 @@ export const milk = {
|
|
|
346
355
|
export const light = {
|
|
347
356
|
name: 'Light',
|
|
348
357
|
description: 'A crisp, modern light theme with purple accents',
|
|
358
|
+
mode: 'light',
|
|
349
359
|
colors: {
|
|
350
360
|
bgDeep: '#ffffff',
|
|
351
361
|
bgMid: '#f8f9fa',
|
package/dist/types.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ export interface Theme {
|
|
|
70
70
|
fonts: ThemeFonts;
|
|
71
71
|
/** Effect configuration */
|
|
72
72
|
effects: ThemeEffects;
|
|
73
|
+
/** Theme mode for styling adjustments (affects picker UI, shadows, contrast) */
|
|
74
|
+
mode?: 'light' | 'dark';
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* Configuration options for the theme picker
|
|
@@ -83,6 +85,8 @@ export interface ThemePickerConfig {
|
|
|
83
85
|
themes?: Record<string, Theme>;
|
|
84
86
|
/** CSS variable prefix (default: none, uses standard names) */
|
|
85
87
|
cssVarPrefix?: string;
|
|
88
|
+
/** Enable cross-tab synchronization via storage events (default: false) */
|
|
89
|
+
syncTabs?: boolean;
|
|
86
90
|
}
|
|
87
91
|
/**
|
|
88
92
|
* Metadata for a theme in a catalog
|
|
@@ -126,3 +130,46 @@ export interface ThemeFilterOptions {
|
|
|
126
130
|
/** Exclude themes with ANY of these tags */
|
|
127
131
|
excludeTags?: string[];
|
|
128
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Configuration for SSR blocking script generation
|
|
135
|
+
*/
|
|
136
|
+
export interface SSRConfig {
|
|
137
|
+
/** All available themes */
|
|
138
|
+
themes: Record<string, Theme>;
|
|
139
|
+
/** localStorage key for theme persistence (default: 'svelte-theme-picker-theme') */
|
|
140
|
+
storageKey?: string;
|
|
141
|
+
/** Default theme ID if none is stored */
|
|
142
|
+
defaultTheme?: string;
|
|
143
|
+
/** CSS variable prefix (default: none) */
|
|
144
|
+
cssVarPrefix?: string;
|
|
145
|
+
/** Whether to add no-transitions class during hydration (default: true) */
|
|
146
|
+
preventTransitions?: boolean;
|
|
147
|
+
/** Font configuration for preloading */
|
|
148
|
+
fonts?: FontConfig;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Configuration for font preloading
|
|
152
|
+
*/
|
|
153
|
+
export interface FontConfig {
|
|
154
|
+
/** Font provider: 'google', 'local', or 'custom' */
|
|
155
|
+
provider?: 'google' | 'local' | 'custom';
|
|
156
|
+
/** Font weights to preload per category */
|
|
157
|
+
weights?: {
|
|
158
|
+
heading?: number[];
|
|
159
|
+
body?: number[];
|
|
160
|
+
mono?: number[];
|
|
161
|
+
};
|
|
162
|
+
/** Custom font URL generator (for 'custom' provider) */
|
|
163
|
+
getFontUrl?: (fontName: string, weights: number[]) => string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* CSS variable schema mapping theme properties to CSS variable names
|
|
167
|
+
*/
|
|
168
|
+
export interface ThemeSchema {
|
|
169
|
+
/** Color property to CSS variable name mapping */
|
|
170
|
+
colors: Record<keyof ThemeColors, string>;
|
|
171
|
+
/** Font property to CSS variable name mapping */
|
|
172
|
+
fonts: Record<keyof ThemeFonts, string>;
|
|
173
|
+
/** Effect property to CSS variable name mapping */
|
|
174
|
+
effects: Record<string, string>;
|
|
175
|
+
}
|