@lpdsgn/astro-themes 0.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 ADDED
@@ -0,0 +1,303 @@
1
+ # astro-themes
2
+
3
+ ![Astro](https://img.shields.io/badge/astro-%232C2052.svg?style=for-the-badge&logo=astro&logoColor=white)
4
+ ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
5
+
6
+ Perfect dark mode in Astro with no flash. An Astro integration that mirrors the behavior of [next-themes](https://github.com/pacocoursey/next-themes).
7
+
8
+ ## Features
9
+
10
+ - ✅ **Perfect dark mode in 2 lines of code**
11
+ - ✅ **No flash on load** (SSR and SSG)
12
+ - ✅ **System preference with `prefers-color-scheme`**
13
+ - ✅ **Themed browser UI with `color-scheme`**
14
+ - ✅ **Sync theme across tabs and windows**
15
+ - ✅ **Force pages to specific themes**
16
+ - ✅ **Class or data attribute selector**
17
+ - ✅ **TypeScript support**
18
+
19
+ ## Quick Start
20
+
21
+ ### 1. Install
22
+
23
+ ```sh frame="none"
24
+ pnpm astro add astro-themes
25
+ ```
26
+
27
+ ```sh frame="none"
28
+ npx astro add astro-themes
29
+ ```
30
+
31
+ ```sh frame="none"
32
+ yarn astro add astro-themes
33
+ ```
34
+
35
+ ### 2. Add ThemeProvider
36
+
37
+ ```astro frame="code" title="src/layouts/Layout.astro"
38
+ ---
39
+ import ThemeProvider from "astro-themes/ThemeProvider.astro";
40
+ ---
41
+
42
+ <!doctype html>
43
+ <html lang="en">
44
+ <head>
45
+ <ThemeProvider />
46
+ </head>
47
+ <body>
48
+ <slot />
49
+ </body>
50
+ </html>
51
+ ```
52
+
53
+ That's it! Your Astro app now supports dark mode with system preference detection and cross-tab sync.
54
+
55
+ ## Styling
56
+
57
+ ### CSS Variables
58
+
59
+ By default, `astro-themes` sets `data-theme` on the `<html>` element:
60
+
61
+ ```css frame="code" title="src/styles/global.css"
62
+ :root {
63
+ --background: white;
64
+ --foreground: black;
65
+ }
66
+
67
+ [data-theme='dark'] {
68
+ --background: black;
69
+ --foreground: white;
70
+ }
71
+ ```
72
+
73
+ ### TailwindCSS
74
+
75
+ For Tailwind's class-based dark mode, set `attribute="class"`:
76
+
77
+ ```astro
78
+ <ThemeProvider attribute="class" />
79
+ ```
80
+
81
+ Then configure Tailwind:
82
+
83
+ **Tailwind v4:**
84
+
85
+ ```css frame="code" title="src/styles/global.css" ins={3}
86
+ @import 'tailwindcss';
87
+
88
+ @custom-variant dark (&:is(.dark *));
89
+ ```
90
+
91
+ **Tailwind v3:**
92
+
93
+ ```js frame="code" title="tailwind.config.js" ins={2}
94
+ module.exports = {
95
+ darkMode: 'selector',
96
+ }
97
+ ```
98
+
99
+ Now use dark-mode classes:
100
+
101
+ ```html
102
+ <h1 class="text-black dark:text-white">Hello</h1>
103
+ ```
104
+
105
+ ## Changing the Theme
106
+
107
+ ### Using Client Helpers (Recommended)
108
+
109
+ ```astro
110
+ <script>
111
+ import { setTheme, toggleTheme, onThemeChange, getResolvedTheme } from 'astro-themes/client';
112
+
113
+ // Toggle between light and dark
114
+ toggleTheme();
115
+
116
+ // Set a specific theme
117
+ setTheme('dark');
118
+
119
+ // Get current resolved theme
120
+ const current = getResolvedTheme(); // 'light' | 'dark'
121
+
122
+ // Listen to theme changes
123
+ const unsubscribe = onThemeChange(({ theme, resolvedTheme }) => {
124
+ console.log('Theme changed:', resolvedTheme);
125
+ });
126
+ </script>
127
+ ```
128
+
129
+ ### Using the Global Object
130
+
131
+ ```html
132
+ <button id="theme-toggle">Toggle</button>
133
+
134
+ <script>
135
+ document.getElementById('theme-toggle').addEventListener('click', () => {
136
+ const { resolvedTheme, setTheme } = window.__ASTRO_THEMES__;
137
+ setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
138
+ });
139
+ </script>
140
+ ```
141
+
142
+ ## Configuration
143
+
144
+ ### ThemeProvider Props
145
+
146
+ | Prop | Type | Default | Description |
147
+ |------|------|---------|-------------|
148
+ | `storageKey` | `string` | `'theme'` | localStorage key for theme setting |
149
+ | `defaultTheme` | `string` | `'system'` | Default theme (`'light'`, `'dark'`, or `'system'`) |
150
+ | `themes` | `string[]` | `['light', 'dark']` | Available theme names |
151
+ | `attribute` | `string \| string[]` | `'data-theme'` | HTML attribute to set. Use `'class'` for Tailwind |
152
+ | `value` | `object` | - | Map theme names to custom attribute values |
153
+ | `forcedTheme` | `string` | - | Force a specific theme on this page |
154
+ | `enableSystem` | `boolean` | `true` | Enable system preference detection |
155
+ | `enableColorScheme` | `boolean` | `true` | Set `color-scheme` CSS property |
156
+ | `disableTransitionOnChange` | `boolean` | `false` | Disable transitions when switching |
157
+ | `nonce` | `string` | - | CSP nonce for the script tag |
158
+ | `scriptProps` | `object` | - | Additional props for the script tag |
159
+
160
+ ### Integration Options
161
+
162
+ For centralized configuration without using `ThemeProvider`, configure the integration directly:
163
+
164
+ ```js frame="code" title="astro.config.mjs"
165
+ import { defineConfig } from "astro/config";
166
+ import astroThemes from "astro-themes";
167
+
168
+ export default defineConfig({
169
+ integrations: [
170
+ astroThemes({
171
+ injectScript: true, // Auto-inject theme script
172
+ defaultProps: {
173
+ attribute: "class",
174
+ defaultTheme: "system",
175
+ themes: ["light", "dark"],
176
+ },
177
+ devToolbar: true, // Enable Dev Toolbar App
178
+ }),
179
+ ],
180
+ });
181
+ ```
182
+
183
+ | Option | Type | Default | Description |
184
+ |--------|------|---------|-------------|
185
+ | `injectScript` | `boolean` | `false` | Auto-inject theme script (no `ThemeProvider` needed) |
186
+ | `defaultProps` | `object` | `{}` | Default theme configuration (same options as **[ThemeProvider Props](#themeprovider-props)**) |
187
+ | `devToolbar` | `boolean` | `true` | Enable the Dev Toolbar App |
188
+
189
+ > **Tip:** Use `injectScript: true` for consistent settings across all pages. You can still use `ThemeProvider` with `forcedTheme` on specific pages.
190
+
191
+ ## Examples
192
+
193
+ ### Force a Page Theme
194
+
195
+ ```astro frame="code" title="src/pages/dark-only.astro"
196
+ ---
197
+ import ThemeProvider from "astro-themes/ThemeProvider.astro";
198
+ import Layout from "../layouts/Layout.astro";
199
+ ---
200
+
201
+ <Layout>
202
+ <ThemeProvider forcedTheme="dark" />
203
+ <!-- This page is always dark -->
204
+ </Layout>
205
+ ```
206
+
207
+ ### Multiple Themes
208
+
209
+ ```astro
210
+ <ThemeProvider themes={['light', 'dark', 'purple', 'pink']} />
211
+ ```
212
+
213
+ ### Custom Attribute Values
214
+
215
+ ```astro
216
+ <ThemeProvider
217
+ attribute="data-theme"
218
+ value={{ light: 'light-mode', dark: 'dark-mode' }}
219
+ />
220
+ ```
221
+
222
+ ### Disable Transitions on Change
223
+
224
+ ```astro
225
+ <ThemeProvider disableTransitionOnChange />
226
+ ```
227
+
228
+ ### Cloudflare Rocket Loader
229
+
230
+ ```astro
231
+ <ThemeProvider scriptProps={{ 'data-cfasync': 'false' }} />
232
+ ```
233
+
234
+ ## API Reference
235
+
236
+ ### Client Helpers
237
+
238
+ Import from `astro-themes/client`:
239
+
240
+ | Function | Description |
241
+ |----------|-------------|
242
+ | `getTheme()` | Get complete theme state object |
243
+ | `setTheme(theme)` | Set theme (string or callback function) |
244
+ | `toggleTheme()` | Toggle between light and dark |
245
+ | `getResolvedTheme()` | Get resolved theme (`'light'` or `'dark'`) |
246
+ | `getSystemTheme()` | Get system preference |
247
+ | `getThemes()` | Get list of available themes |
248
+ | `isForcedTheme()` | Check if current page has forced theme |
249
+ | `onThemeChange(callback)` | Subscribe to changes (returns unsubscribe) |
250
+
251
+ ### Theme State Object
252
+
253
+ Available via `window.__ASTRO_THEMES__`:
254
+
255
+ | Property | Type | Description |
256
+ |----------|------|-------------|
257
+ | `theme` | `string` | Current theme name |
258
+ | `resolvedTheme` | `string` | Resolved theme (`'light'` or `'dark'`) |
259
+ | `systemTheme` | `'light' \| 'dark'` | System preference |
260
+ | `forcedTheme` | `string \| undefined` | Forced theme if set |
261
+ | `themes` | `string[]` | Available themes |
262
+ | `setTheme(theme)` | `function` | Update the theme |
263
+
264
+ ## Avoiding Hydration Mismatch
265
+
266
+ Since the theme is unknown on the server, use CSS to conditionally show content:
267
+
268
+ ```css
269
+ [data-theme='dark'] .light-only { display: none; }
270
+ [data-theme='light'] .dark-only { display: none; }
271
+ ```
272
+
273
+ Or check in client-side scripts:
274
+
275
+ ```astro
276
+ <script>
277
+ if (window.__ASTRO_THEMES__) {
278
+ const { resolvedTheme } = window.__ASTRO_THEMES__;
279
+ // Update UI based on theme
280
+ }
281
+ </script>
282
+ ```
283
+
284
+ ## Contributing
285
+
286
+ This package is structured as a monorepo:
287
+
288
+ - `playground` – Development testing environment
289
+ - `packages/astro-themes` – The integration package
290
+
291
+ ```bash
292
+ pnpm i --frozen-lockfile
293
+ pnpm dev
294
+ ```
295
+
296
+ ## License
297
+
298
+ [MIT Licensed](https://github.com/LPdsgn/astro-themes/blob/main/LICENSE). Made with ❤️
299
+
300
+ ## Acknowledgements
301
+
302
+ - Inspired by [next-themes](https://github.com/pacocoursey/next-themes) by Paco Coursey
303
+ - Built with [astro-integration-kit](https://github.com/florian-lefebvre/astro-integration-kit) by Florian Lefebvre
@@ -0,0 +1,54 @@
1
+ import { ThemeState } from './types.js';
2
+
3
+ /**
4
+ * Client-side helpers for interacting with the theme
5
+ * These are meant to be used in client-side scripts
6
+ */
7
+
8
+ declare global {
9
+ interface Window {
10
+ __ASTRO_THEMES__?: ThemeState;
11
+ }
12
+ }
13
+ /**
14
+ * Get the current theme state
15
+ * Returns undefined if ThemeProvider is not mounted
16
+ */
17
+ declare function getTheme(): ThemeState | undefined;
18
+ /**
19
+ * Set the theme
20
+ * @param theme - Theme name or function that receives current theme and returns new theme
21
+ */
22
+ declare function setTheme(theme: string | ((prevTheme: string) => string)): void;
23
+ /**
24
+ * Get the resolved theme (system theme resolved to actual value)
25
+ */
26
+ declare function getResolvedTheme(): string | undefined;
27
+ /**
28
+ * Get the system theme preference
29
+ */
30
+ declare function getSystemTheme(): "dark" | "light" | undefined;
31
+ /**
32
+ * Check if theme is forced for the current page
33
+ */
34
+ declare function isForcedTheme(): boolean;
35
+ /**
36
+ * Get the list of available themes
37
+ */
38
+ declare function getThemes(): string[];
39
+ /**
40
+ * Subscribe to theme changes
41
+ * @param callback - Function called when theme changes
42
+ * @returns Cleanup function to unsubscribe
43
+ */
44
+ declare function onThemeChange(callback: (detail: {
45
+ theme: string;
46
+ resolvedTheme: string;
47
+ }) => void): () => void;
48
+ /**
49
+ * Toggle between light and dark themes
50
+ * If current theme is neither light nor dark, switches to light
51
+ */
52
+ declare function toggleTheme(): void;
53
+
54
+ export { getResolvedTheme, getSystemTheme, getTheme, getThemes, isForcedTheme, onThemeChange, setTheme, toggleTheme };
package/dist/client.js ADDED
@@ -0,0 +1,2 @@
1
+ function n(){if(!(typeof window>"u"))return window.__ASTRO_THEMES__}function o(e){let t=n();t&&t.setTheme(e)}function i(){return n()?.resolvedTheme}function s(){return n()?.systemTheme}function d(){return!!n()?.forcedTheme}function h(){return n()?.themes??[]}function m(e){if(typeof window>"u")return()=>{};let t=r=>{e(r.detail)};return window.addEventListener("astro-themes:change",t),()=>{window.removeEventListener("astro-themes:change",t)}}function u(){let e=n();if(!e)return;let t=e.resolvedTheme;e.setTheme(t==="light"?"dark":"light")}export{i as getResolvedTheme,s as getSystemTheme,n as getTheme,h as getThemes,d as isForcedTheme,m as onThemeChange,o as setTheme,u as toggleTheme};
2
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Client-side helpers for interacting with the theme\n * These are meant to be used in client-side scripts\n */\n\nimport type { ThemeState } from \"./types.js\";\n\ndeclare global {\n\tinterface Window {\n\t\t__ASTRO_THEMES__?: ThemeState;\n\t}\n}\n\n/**\n * Get the current theme state\n * Returns undefined if ThemeProvider is not mounted\n */\nexport function getTheme(): ThemeState | undefined {\n\tif (typeof window === \"undefined\") return undefined;\n\treturn window.__ASTRO_THEMES__;\n}\n\n/**\n * Set the theme\n * @param theme - Theme name or function that receives current theme and returns new theme\n */\nexport function setTheme(theme: string | ((prevTheme: string) => string)): void {\n\tconst state = getTheme();\n\tif (state) {\n\t\tstate.setTheme(theme);\n\t}\n}\n\n/**\n * Get the resolved theme (system theme resolved to actual value)\n */\nexport function getResolvedTheme(): string | undefined {\n\treturn getTheme()?.resolvedTheme;\n}\n\n/**\n * Get the system theme preference\n */\nexport function getSystemTheme(): \"dark\" | \"light\" | undefined {\n\treturn getTheme()?.systemTheme;\n}\n\n/**\n * Check if theme is forced for the current page\n */\nexport function isForcedTheme(): boolean {\n\treturn !!getTheme()?.forcedTheme;\n}\n\n/**\n * Get the list of available themes\n */\nexport function getThemes(): string[] {\n\treturn getTheme()?.themes ?? [];\n}\n\n/**\n * Subscribe to theme changes\n * @param callback - Function called when theme changes\n * @returns Cleanup function to unsubscribe\n */\nexport function onThemeChange(\n\tcallback: (detail: { theme: string; resolvedTheme: string }) => void\n): () => void {\n\tif (typeof window === \"undefined\") return () => {};\n\n\tconst handler = (e: CustomEvent<{ theme: string; resolvedTheme: string }>) => {\n\t\tcallback(e.detail);\n\t};\n\n\twindow.addEventListener(\"astro-themes:change\", handler as EventListener);\n\n\treturn () => {\n\t\twindow.removeEventListener(\"astro-themes:change\", handler as EventListener);\n\t};\n}\n\n/**\n * Toggle between light and dark themes\n * If current theme is neither light nor dark, switches to light\n */\nexport function toggleTheme(): void {\n\tconst state = getTheme();\n\tif (!state) return;\n\n\tconst resolved = state.resolvedTheme;\n\tstate.setTheme(resolved === \"light\" ? \"dark\" : \"light\");\n}\n"],"mappings":"AAiBO,SAASA,GAAmC,CAClD,GAAI,SAAO,OAAW,KACtB,OAAO,OAAO,gBACf,CAMO,SAASC,EAASC,EAAuD,CAC/E,IAAMC,EAAQH,EAAS,EACnBG,GACHA,EAAM,SAASD,CAAK,CAEtB,CAKO,SAASE,GAAuC,CACtD,OAAOJ,EAAS,GAAG,aACpB,CAKO,SAASK,GAA+C,CAC9D,OAAOL,EAAS,GAAG,WACpB,CAKO,SAASM,GAAyB,CACxC,MAAO,CAAC,CAACN,EAAS,GAAG,WACtB,CAKO,SAASO,GAAsB,CACrC,OAAOP,EAAS,GAAG,QAAU,CAAC,CAC/B,CAOO,SAASQ,EACfC,EACa,CACb,GAAI,OAAO,OAAW,IAAa,MAAO,IAAM,CAAC,EAEjD,IAAMC,EAAWC,GAA6D,CAC7EF,EAASE,EAAE,MAAM,CAClB,EAEA,cAAO,iBAAiB,sBAAuBD,CAAwB,EAEhE,IAAM,CACZ,OAAO,oBAAoB,sBAAuBA,CAAwB,CAC3E,CACD,CAMO,SAASE,GAAoB,CACnC,IAAMT,EAAQH,EAAS,EACvB,GAAI,CAACG,EAAO,OAEZ,IAAMU,EAAWV,EAAM,cACvBA,EAAM,SAASU,IAAa,QAAU,OAAS,OAAO,CACvD","names":["getTheme","setTheme","theme","state","getResolvedTheme","getSystemTheme","isForcedTheme","getThemes","onThemeChange","callback","handler","e","toggleTheme","resolved"]}
@@ -0,0 +1,223 @@
1
+ ---
2
+ import type { ThemeProviderProps } from "../types.js";
3
+ import { getMinifiedInlineScript } from "../script.js";
4
+
5
+ interface Props extends ThemeProviderProps {}
6
+
7
+ const {
8
+ storageKey = "theme",
9
+ defaultTheme: defaultThemeProp,
10
+ forcedTheme,
11
+ enableSystem = true,
12
+ enableColorScheme = true,
13
+ disableTransitionOnChange = false,
14
+ themes = ["light", "dark"],
15
+ attribute = "data-theme",
16
+ value,
17
+ nonce,
18
+ scriptProps = {},
19
+ } = Astro.props;
20
+
21
+ // If enableSystem is false and no defaultTheme is set, default to 'light'
22
+ const defaultTheme = defaultThemeProp ?? (enableSystem ? "system" : "light");
23
+
24
+ // Generate the inline script to prevent flash
25
+ const inlineScript = getMinifiedInlineScript(
26
+ attribute,
27
+ storageKey,
28
+ defaultTheme,
29
+ forcedTheme,
30
+ themes,
31
+ value,
32
+ enableSystem,
33
+ enableColorScheme
34
+ );
35
+
36
+ // Build script attributes
37
+ const scriptAttrs: Record<string, unknown> = {
38
+ ...scriptProps,
39
+ };
40
+
41
+ if (nonce) {
42
+ scriptAttrs.nonce = nonce;
43
+ }
44
+ ---
45
+
46
+ {/* Inline script to prevent flash - runs before page renders */}
47
+ <script is:inline set:html={inlineScript} {...scriptAttrs}></script>
48
+
49
+ {/* Client-side script for theme management */}
50
+ <script
51
+ is:inline
52
+ data-astro-themes-config
53
+ data-storage-key={storageKey}
54
+ data-default-theme={defaultTheme}
55
+ data-forced-theme={forcedTheme}
56
+ data-enable-system={enableSystem}
57
+ data-enable-color-scheme={enableColorScheme}
58
+ data-disable-transition-on-change={disableTransitionOnChange}
59
+ data-themes={JSON.stringify(themes)}
60
+ data-attribute={JSON.stringify(attribute)}
61
+ data-value={value ? JSON.stringify(value) : undefined}
62
+ >
63
+ (function() {
64
+ // Get configuration from script element
65
+ var configEl = document.querySelector('script[data-astro-themes-config]');
66
+ if (!configEl) return;
67
+
68
+ var storageKey = configEl.getAttribute('data-storage-key') || 'theme';
69
+ var defaultTheme = configEl.getAttribute('data-default-theme') || 'system';
70
+ var forcedTheme = configEl.getAttribute('data-forced-theme') || undefined;
71
+ var enableSystem = configEl.getAttribute('data-enable-system') === 'true';
72
+ var enableColorScheme = configEl.getAttribute('data-enable-color-scheme') === 'true';
73
+ var disableTransitionOnChange = configEl.getAttribute('data-disable-transition-on-change') === 'true';
74
+ var themes = JSON.parse(configEl.getAttribute('data-themes') || '["light","dark"]');
75
+ var attribute = JSON.parse(configEl.getAttribute('data-attribute') || '"data-theme"');
76
+ var value = configEl.getAttribute('data-value') ? JSON.parse(configEl.getAttribute('data-value')) : undefined;
77
+
78
+ var el = document.documentElement;
79
+ var systemThemes = ['light', 'dark'];
80
+ var MEDIA = '(prefers-color-scheme: dark)';
81
+
82
+ function getSystemTheme() {
83
+ return window.matchMedia(MEDIA).matches ? 'dark' : 'light';
84
+ }
85
+
86
+ function getStoredTheme() {
87
+ try {
88
+ return localStorage.getItem(storageKey) || defaultTheme;
89
+ } catch (e) {
90
+ return defaultTheme;
91
+ }
92
+ }
93
+
94
+ function saveTheme(theme) {
95
+ try {
96
+ localStorage.setItem(storageKey, theme);
97
+ } catch (e) {
98
+ // localStorage not available
99
+ }
100
+ }
101
+
102
+ function disableAnimation() {
103
+ var css = document.createElement('style');
104
+ css.appendChild(
105
+ document.createTextNode(
106
+ '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'
107
+ )
108
+ );
109
+ document.head.appendChild(css);
110
+
111
+ return function() {
112
+ // Force restyle
113
+ (function() { return window.getComputedStyle(document.body); })();
114
+ // Wait for next tick before removing
115
+ setTimeout(function() {
116
+ document.head.removeChild(css);
117
+ }, 1);
118
+ };
119
+ }
120
+
121
+ function applyTheme(theme) {
122
+ var resolved = theme;
123
+ if (theme === 'system' && enableSystem) {
124
+ resolved = getSystemTheme();
125
+ }
126
+
127
+ var enable = disableTransitionOnChange ? disableAnimation() : null;
128
+
129
+ var attributes = Array.isArray(attribute) ? attribute : [attribute];
130
+ var name = value && value[resolved] ? value[resolved] : resolved;
131
+ var attrs = !value ? themes : Object.values(value);
132
+
133
+ attributes.forEach(function(attr) {
134
+ if (attr === 'class') {
135
+ el.classList.remove.apply(el.classList, attrs);
136
+ if (name) el.classList.add(name);
137
+ } else if (attr.startsWith('data-')) {
138
+ if (name) {
139
+ el.setAttribute(attr, name);
140
+ } else {
141
+ el.removeAttribute(attr);
142
+ }
143
+ }
144
+ });
145
+
146
+ if (enableColorScheme) {
147
+ var colorScheme = systemThemes.indexOf(resolved) !== -1 ? resolved : null;
148
+ el.style.colorScheme = colorScheme || '';
149
+ }
150
+
151
+ if (enable) enable();
152
+ }
153
+
154
+ // Current state
155
+ var currentTheme = forcedTheme || getStoredTheme();
156
+ var resolvedTheme = currentTheme === 'system' ? getSystemTheme() : currentTheme;
157
+
158
+ // Create the theme state object
159
+ var themeState = {
160
+ get theme() { return forcedTheme || currentTheme; },
161
+ get resolvedTheme() { return forcedTheme || (currentTheme === 'system' ? getSystemTheme() : currentTheme); },
162
+ get systemTheme() { return getSystemTheme(); },
163
+ get forcedTheme() { return forcedTheme; },
164
+ get themes() { return enableSystem ? themes.concat(['system']) : themes; },
165
+ setTheme: function(newTheme) {
166
+ if (forcedTheme) return; // No-op when forced
167
+
168
+ if (typeof newTheme === 'function') {
169
+ newTheme = newTheme(currentTheme);
170
+ }
171
+
172
+ currentTheme = newTheme;
173
+ saveTheme(newTheme);
174
+ applyTheme(newTheme);
175
+
176
+ // Dispatch event for listeners
177
+ window.dispatchEvent(new CustomEvent('astro-themes:change', {
178
+ detail: { theme: newTheme, resolvedTheme: newTheme === 'system' ? getSystemTheme() : newTheme }
179
+ }));
180
+ }
181
+ };
182
+
183
+ // Expose to window
184
+ window.__ASTRO_THEMES__ = themeState;
185
+
186
+ // Listen for system theme changes
187
+ if (enableSystem) {
188
+ var media = window.matchMedia(MEDIA);
189
+ var handleMediaChange = function(e) {
190
+ if (currentTheme === 'system') {
191
+ applyTheme('system');
192
+ window.dispatchEvent(new CustomEvent('astro-themes:change', {
193
+ detail: { theme: 'system', resolvedTheme: getSystemTheme() }
194
+ }));
195
+ }
196
+ };
197
+
198
+ // Use modern API if available, fall back to deprecated one
199
+ if (media.addEventListener) {
200
+ media.addEventListener('change', handleMediaChange);
201
+ } else {
202
+ media.addListener(handleMediaChange);
203
+ }
204
+ }
205
+
206
+ // Listen for storage changes (sync across tabs)
207
+ window.addEventListener('storage', function(e) {
208
+ if (e.key !== storageKey) return;
209
+
210
+ if (!e.newValue) {
211
+ themeState.setTheme(defaultTheme);
212
+ } else if (e.newValue !== currentTheme) {
213
+ currentTheme = e.newValue;
214
+ applyTheme(e.newValue);
215
+ window.dispatchEvent(new CustomEvent('astro-themes:change', {
216
+ detail: { theme: e.newValue, resolvedTheme: e.newValue === 'system' ? getSystemTheme() : e.newValue }
217
+ }));
218
+ }
219
+ });
220
+ })();
221
+ </script>
222
+
223
+ <slot />
@@ -0,0 +1,21 @@
1
+ import * as astro from 'astro';
2
+ export { AstroThemesConfig, Attribute, ScriptProps, ThemeProviderProps, ThemeState, ValueObject } from './types.js';
3
+ export { getResolvedTheme, getSystemTheme, getTheme, getThemes, isForcedTheme, onThemeChange, setTheme, toggleTheme } from './client.js';
4
+
5
+ declare const integration: (options?: {
6
+ injectScript?: boolean | undefined;
7
+ defaultProps?: {
8
+ storageKey?: string | undefined;
9
+ defaultTheme?: string | undefined;
10
+ forcedTheme?: string | undefined;
11
+ enableSystem?: boolean | undefined;
12
+ enableColorScheme?: boolean | undefined;
13
+ disableTransitionOnChange?: boolean | undefined;
14
+ value?: Record<string, string> | undefined;
15
+ themes?: string[] | undefined;
16
+ attribute?: string | string[] | undefined;
17
+ } | undefined;
18
+ devToolbar?: boolean | undefined;
19
+ } | undefined) => astro.AstroIntegration & {};
20
+
21
+ export { integration as default, integration };
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ import{defineIntegration as g}from"astro-integration-kit";import{z as e}from"astro/zod";function c(r,t,n,o,a,s,m,h){return`
2
+ (function() {
3
+ var el = document.documentElement;
4
+ var attribute = ${JSON.stringify(r)};
5
+ var storageKey = ${JSON.stringify(t)};
6
+ var defaultTheme = ${JSON.stringify(n)};
7
+ var forcedTheme = ${JSON.stringify(o)};
8
+ var themes = ${JSON.stringify(a)};
9
+ var value = ${JSON.stringify(s)};
10
+ var enableSystem = ${JSON.stringify(m)};
11
+ var enableColorScheme = ${JSON.stringify(h)};
12
+ var systemThemes = ['light', 'dark'];
13
+
14
+ function updateDOM(theme) {
15
+ var attributes = Array.isArray(attribute) ? attribute : [attribute];
16
+ attributes.forEach(function(attr) {
17
+ var isClass = attr === 'class';
18
+ var classes = isClass && value ? themes.map(function(t) { return value[t] || t; }) : themes;
19
+ if (isClass) {
20
+ el.classList.remove.apply(el.classList, classes);
21
+ el.classList.add(value && value[theme] ? value[theme] : theme);
22
+ } else {
23
+ el.setAttribute(attr, value && value[theme] ? value[theme] : theme);
24
+ }
25
+ });
26
+ setColorScheme(theme);
27
+ }
28
+
29
+ function setColorScheme(theme) {
30
+ if (enableColorScheme && systemThemes.indexOf(theme) !== -1) {
31
+ el.style.colorScheme = theme;
32
+ }
33
+ }
34
+
35
+ function getSystemTheme() {
36
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
37
+ }
38
+
39
+ if (forcedTheme) {
40
+ updateDOM(forcedTheme);
41
+ } else {
42
+ try {
43
+ var themeName = localStorage.getItem(storageKey) || defaultTheme;
44
+ var isSystem = enableSystem && themeName === 'system';
45
+ var theme = isSystem ? getSystemTheme() : themeName;
46
+ updateDOM(theme);
47
+ } catch (e) {
48
+ updateDOM(defaultTheme === 'system' && enableSystem ? getSystemTheme() : defaultTheme);
49
+ }
50
+ }
51
+ })();
52
+ `.trim()}function l(r,t,n,o,a,s,m,h){return c(r,t,n,o,a,s,m,h).replace(/\s+/g," ").replace(/\s*{\s*/g,"{").replace(/\s*}\s*/g,"}").replace(/\s*;\s*/g,";").replace(/\s*,\s*/g,",").replace(/\s*\(\s*/g,"(").replace(/\s*\)\s*/g,")").trim()}var f="theme",u="system",p=["light","dark"],T="data-theme",v=e.object({injectScript:e.boolean().default(!1),defaultProps:e.object({storageKey:e.string().default(f),defaultTheme:e.string().default(u),forcedTheme:e.string().optional(),enableSystem:e.boolean().default(!0),enableColorScheme:e.boolean().default(!0),disableTransitionOnChange:e.boolean().default(!1),themes:e.array(e.string()).default(p),attribute:e.union([e.string(),e.array(e.string())]).default(T),value:e.record(e.string()).optional()}).default({}),devToolbar:e.boolean().default(!0)}).default({}),d=g({name:"astro-themes",optionsSchema:v,setup({options:r}){return{hooks:{"astro:config:setup":({logger:t,injectScript:n,addDevToolbarApp:o,command:a})=>{if(t.info("astro-themes initialized"),r.injectScript){let s=r.defaultProps,m=l(s.attribute,s.storageKey,s.defaultTheme,s.forcedTheme,s.themes,s.value,s.enableSystem,s.enableColorScheme);n("head-inline",m),t.info("Flash-prevention script injected via integration")}r.devToolbar&&a==="dev"&&(o({id:"astro-themes-toolbar",name:"Theme Switcher",icon:'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>',entrypoint:"astro-themes/toolbar"}),t.debug("Dev Toolbar App registered"))},"astro:config:done":({injectTypes:t})=>{t({filename:"types.d.ts",content:`declare global {
53
+ interface Window {
54
+ __ASTRO_THEMES__?: {
55
+ theme: string;
56
+ resolvedTheme: string;
57
+ systemTheme: "dark" | "light";
58
+ forcedTheme?: string;
59
+ themes: string[];
60
+ setTheme: (theme: string | ((prevTheme: string) => string)) => void;
61
+ };
62
+ }
63
+ }
64
+ export {};`})}}}}});function i(){if(!(typeof window>"u"))return window.__ASTRO_THEMES__}function y(r){let t=i();t&&t.setTheme(r)}function S(){return i()?.resolvedTheme}function b(){return i()?.systemTheme}function E(){return!!i()?.forcedTheme}function w(){return i()?.themes??[]}function x(r){if(typeof window>"u")return()=>{};let t=n=>{r(n.detail)};return window.addEventListener("astro-themes:change",t),()=>{window.removeEventListener("astro-themes:change",t)}}function _(){let r=i();if(!r)return;let t=r.resolvedTheme;r.setTheme(t==="light"?"dark":"light")}var D=d;export{D as default,S as getResolvedTheme,b as getSystemTheme,i as getTheme,w as getThemes,d as integration,E as isForcedTheme,x as onThemeChange,y as setTheme,_ as toggleTheme};
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/integration.ts","../src/script.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import { defineIntegration } from \"astro-integration-kit\";\nimport { z } from \"astro/zod\";\nimport type { AstroThemesConfig, ThemeProviderProps } from \"./types.js\";\nimport { getMinifiedInlineScript } from \"./script.js\";\n\n// Default configuration values\nconst DEFAULT_STORAGE_KEY = \"theme\";\nconst DEFAULT_THEME = \"system\";\nconst DEFAULT_THEMES = [\"light\", \"dark\"];\nconst DEFAULT_ATTRIBUTE = \"data-theme\";\n\n// Options schema for the integration\nconst optionsSchema = z\n\t.object({\n\t\t/**\n\t\t * Inject the flash-prevention script automatically via integration\n\t\t * When true, you don't need to use ThemeProvider component\n\t\t * @default false\n\t\t */\n\t\tinjectScript: z.boolean().default(false),\n\t\t/**\n\t\t * Default theme configuration used when injectScript is true\n\t\t */\n\t\tdefaultProps: z\n\t\t\t.object({\n\t\t\t\tstorageKey: z.string().default(DEFAULT_STORAGE_KEY),\n\t\t\t\tdefaultTheme: z.string().default(DEFAULT_THEME),\n\t\t\t\tforcedTheme: z.string().optional(),\n\t\t\t\tenableSystem: z.boolean().default(true),\n\t\t\t\tenableColorScheme: z.boolean().default(true),\n\t\t\t\tdisableTransitionOnChange: z.boolean().default(false),\n\t\t\t\tthemes: z.array(z.string()).default(DEFAULT_THEMES),\n\t\t\t\tattribute: z\n\t\t\t\t\t.union([z.string(), z.array(z.string())])\n\t\t\t\t\t.default(DEFAULT_ATTRIBUTE),\n\t\t\t\tvalue: z.record(z.string()).optional(),\n\t\t\t})\n\t\t\t.default({}),\n\t\t/**\n\t\t * Enable the Dev Toolbar App for theme switching during development\n\t\t * @default true\n\t\t */\n\t\tdevToolbar: z.boolean().default(true),\n\t})\n\t.default({});\n\nexport const integration = defineIntegration({\n\tname: \"astro-themes\",\n\toptionsSchema,\n\tsetup({ options }) {\n\t\treturn {\n\t\t\thooks: {\n\t\t\t\t\"astro:config:setup\": ({\n\t\t\t\t\tlogger,\n\t\t\t\t\tinjectScript,\n\t\t\t\t\taddDevToolbarApp,\n\t\t\t\t\tcommand,\n\t\t\t\t}) => {\n\t\t\t\t\tlogger.info(\"astro-themes initialized\");\n\n\t\t\t\t\t// Inject flash-prevention script if enabled\n\t\t\t\t\tif (options.injectScript) {\n\t\t\t\t\t\tconst props = options.defaultProps;\n\t\t\t\t\t\tconst script = getMinifiedInlineScript(\n\t\t\t\t\t\t\tprops.attribute,\n\t\t\t\t\t\t\tprops.storageKey,\n\t\t\t\t\t\t\tprops.defaultTheme,\n\t\t\t\t\t\t\tprops.forcedTheme,\n\t\t\t\t\t\t\tprops.themes,\n\t\t\t\t\t\t\tprops.value,\n\t\t\t\t\t\t\tprops.enableSystem,\n\t\t\t\t\t\t\tprops.enableColorScheme\n\t\t\t\t\t\t);\n\t\t\t\t\t\tinjectScript(\"head-inline\", script);\n\t\t\t\t\t\tlogger.info(\"Flash-prevention script injected via integration\");\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add Dev Toolbar App in dev mode\n\t\t\t\t\tif (options.devToolbar && command === \"dev\") {\n\t\t\t\t\t\taddDevToolbarApp({\n\t\t\t\t\t\t\tid: \"astro-themes-toolbar\",\n\t\t\t\t\t\t\tname: \"Theme Switcher\",\n\t\t\t\t\t\t\ticon: `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/></svg>`,\n\t\t\t\t\t\t\tentrypoint: \"astro-themes/toolbar\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlogger.debug(\"Dev Toolbar App registered\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"astro:config:done\": ({ injectTypes }) => {\n\t\t\t\t\t// Inject global types for window.__ASTRO_THEMES__\n\t\t\t\t\tinjectTypes({\n\t\t\t\t\t\tfilename: \"types.d.ts\",\n\t\t\t\t\t\tcontent: `declare global {\n interface Window {\n __ASTRO_THEMES__?: {\n theme: string;\n resolvedTheme: string;\n systemTheme: \"dark\" | \"light\";\n forcedTheme?: string;\n themes: string[];\n setTheme: (theme: string | ((prevTheme: string) => string)) => void;\n };\n }\n}\nexport {};`,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t},\n});\n\nexport type { AstroThemesConfig, ThemeProviderProps };\n","/**\n * Inline script that runs before page render to prevent flash\n * This script is injected into the head and executes immediately\n */\nexport function getInlineScript(\n\tattribute: string | string[],\n\tstorageKey: string,\n\tdefaultTheme: string,\n\tforcedTheme: string | undefined,\n\tthemes: string[],\n\tvalue: Record<string, string> | undefined,\n\tenableSystem: boolean,\n\tenableColorScheme: boolean\n): string {\n\t// The script must be minified and self-contained\n\tconst script = `\n(function() {\n var el = document.documentElement;\n var attribute = ${JSON.stringify(attribute)};\n var storageKey = ${JSON.stringify(storageKey)};\n var defaultTheme = ${JSON.stringify(defaultTheme)};\n var forcedTheme = ${JSON.stringify(forcedTheme)};\n var themes = ${JSON.stringify(themes)};\n var value = ${JSON.stringify(value)};\n var enableSystem = ${JSON.stringify(enableSystem)};\n var enableColorScheme = ${JSON.stringify(enableColorScheme)};\n var systemThemes = ['light', 'dark'];\n\n function updateDOM(theme) {\n var attributes = Array.isArray(attribute) ? attribute : [attribute];\n attributes.forEach(function(attr) {\n var isClass = attr === 'class';\n var classes = isClass && value ? themes.map(function(t) { return value[t] || t; }) : themes;\n if (isClass) {\n el.classList.remove.apply(el.classList, classes);\n el.classList.add(value && value[theme] ? value[theme] : theme);\n } else {\n el.setAttribute(attr, value && value[theme] ? value[theme] : theme);\n }\n });\n setColorScheme(theme);\n }\n\n function setColorScheme(theme) {\n if (enableColorScheme && systemThemes.indexOf(theme) !== -1) {\n el.style.colorScheme = theme;\n }\n }\n\n function getSystemTheme() {\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n if (forcedTheme) {\n updateDOM(forcedTheme);\n } else {\n try {\n var themeName = localStorage.getItem(storageKey) || defaultTheme;\n var isSystem = enableSystem && themeName === 'system';\n var theme = isSystem ? getSystemTheme() : themeName;\n updateDOM(theme);\n } catch (e) {\n updateDOM(defaultTheme === 'system' && enableSystem ? getSystemTheme() : defaultTheme);\n }\n }\n})();\n`.trim();\n\n\treturn script;\n}\n\n/**\n * Returns a minified version of the inline script\n */\nexport function getMinifiedInlineScript(\n\tattribute: string | string[],\n\tstorageKey: string,\n\tdefaultTheme: string,\n\tforcedTheme: string | undefined,\n\tthemes: string[],\n\tvalue: Record<string, string> | undefined,\n\tenableSystem: boolean,\n\tenableColorScheme: boolean\n): string {\n\t// Minified version for production\n\treturn getInlineScript(\n\t\tattribute,\n\t\tstorageKey,\n\t\tdefaultTheme,\n\t\tforcedTheme,\n\t\tthemes,\n\t\tvalue,\n\t\tenableSystem,\n\t\tenableColorScheme\n\t)\n\t\t.replace(/\\s+/g, \" \")\n\t\t.replace(/\\s*{\\s*/g, \"{\")\n\t\t.replace(/\\s*}\\s*/g, \"}\")\n\t\t.replace(/\\s*;\\s*/g, \";\")\n\t\t.replace(/\\s*,\\s*/g, \",\")\n\t\t.replace(/\\s*\\(\\s*/g, \"(\")\n\t\t.replace(/\\s*\\)\\s*/g, \")\")\n\t\t.trim();\n}\n","/**\n * Client-side helpers for interacting with the theme\n * These are meant to be used in client-side scripts\n */\n\nimport type { ThemeState } from \"./types.js\";\n\ndeclare global {\n\tinterface Window {\n\t\t__ASTRO_THEMES__?: ThemeState;\n\t}\n}\n\n/**\n * Get the current theme state\n * Returns undefined if ThemeProvider is not mounted\n */\nexport function getTheme(): ThemeState | undefined {\n\tif (typeof window === \"undefined\") return undefined;\n\treturn window.__ASTRO_THEMES__;\n}\n\n/**\n * Set the theme\n * @param theme - Theme name or function that receives current theme and returns new theme\n */\nexport function setTheme(theme: string | ((prevTheme: string) => string)): void {\n\tconst state = getTheme();\n\tif (state) {\n\t\tstate.setTheme(theme);\n\t}\n}\n\n/**\n * Get the resolved theme (system theme resolved to actual value)\n */\nexport function getResolvedTheme(): string | undefined {\n\treturn getTheme()?.resolvedTheme;\n}\n\n/**\n * Get the system theme preference\n */\nexport function getSystemTheme(): \"dark\" | \"light\" | undefined {\n\treturn getTheme()?.systemTheme;\n}\n\n/**\n * Check if theme is forced for the current page\n */\nexport function isForcedTheme(): boolean {\n\treturn !!getTheme()?.forcedTheme;\n}\n\n/**\n * Get the list of available themes\n */\nexport function getThemes(): string[] {\n\treturn getTheme()?.themes ?? [];\n}\n\n/**\n * Subscribe to theme changes\n * @param callback - Function called when theme changes\n * @returns Cleanup function to unsubscribe\n */\nexport function onThemeChange(\n\tcallback: (detail: { theme: string; resolvedTheme: string }) => void\n): () => void {\n\tif (typeof window === \"undefined\") return () => {};\n\n\tconst handler = (e: CustomEvent<{ theme: string; resolvedTheme: string }>) => {\n\t\tcallback(e.detail);\n\t};\n\n\twindow.addEventListener(\"astro-themes:change\", handler as EventListener);\n\n\treturn () => {\n\t\twindow.removeEventListener(\"astro-themes:change\", handler as EventListener);\n\t};\n}\n\n/**\n * Toggle between light and dark themes\n * If current theme is neither light nor dark, switches to light\n */\nexport function toggleTheme(): void {\n\tconst state = getTheme();\n\tif (!state) return;\n\n\tconst resolved = state.resolvedTheme;\n\tstate.setTheme(resolved === \"light\" ? \"dark\" : \"light\");\n}\n","// Main integration export\nimport { integration } from \"./integration.js\";\n\nexport default integration;\n\n// Re-export integration\nexport { integration };\n\n// Re-export types\nexport type {\n\tAttribute,\n\tValueObject,\n\tScriptProps,\n\tThemeProviderProps,\n\tThemeState,\n\tAstroThemesConfig,\n} from \"./types.js\";\n\n// Re-export client helpers\nexport {\n\tgetTheme,\n\tsetTheme,\n\tgetResolvedTheme,\n\tgetSystemTheme,\n\tisForcedTheme,\n\tgetThemes,\n\tonThemeChange,\n\ttoggleTheme,\n} from \"./client.js\";\n"],"mappings":"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","names":["defineIntegration","z","getInlineScript","attribute","storageKey","defaultTheme","forcedTheme","themes","value","enableSystem","enableColorScheme","getMinifiedInlineScript","DEFAULT_STORAGE_KEY","DEFAULT_THEME","DEFAULT_THEMES","DEFAULT_ATTRIBUTE","optionsSchema","z","integration","defineIntegration","options","logger","injectScript","addDevToolbarApp","command","props","script","getMinifiedInlineScript","injectTypes","getTheme","setTheme","theme","state","getResolvedTheme","getSystemTheme","isForcedTheme","getThemes","onThemeChange","callback","handler","e","toggleTheme","resolved","index_default","integration"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Inline script that runs before page render to prevent flash
3
+ * This script is injected into the head and executes immediately
4
+ */
5
+ declare function getInlineScript(attribute: string | string[], storageKey: string, defaultTheme: string, forcedTheme: string | undefined, themes: string[], value: Record<string, string> | undefined, enableSystem: boolean, enableColorScheme: boolean): string;
6
+ /**
7
+ * Returns a minified version of the inline script
8
+ */
9
+ declare function getMinifiedInlineScript(attribute: string | string[], storageKey: string, defaultTheme: string, forcedTheme: string | undefined, themes: string[], value: Record<string, string> | undefined, enableSystem: boolean, enableColorScheme: boolean): string;
10
+
11
+ export { getInlineScript, getMinifiedInlineScript };
package/dist/script.js ADDED
@@ -0,0 +1,53 @@
1
+ function l(e,t,s,r,a,i,n,m){return`
2
+ (function() {
3
+ var el = document.documentElement;
4
+ var attribute = ${JSON.stringify(e)};
5
+ var storageKey = ${JSON.stringify(t)};
6
+ var defaultTheme = ${JSON.stringify(s)};
7
+ var forcedTheme = ${JSON.stringify(r)};
8
+ var themes = ${JSON.stringify(a)};
9
+ var value = ${JSON.stringify(i)};
10
+ var enableSystem = ${JSON.stringify(n)};
11
+ var enableColorScheme = ${JSON.stringify(m)};
12
+ var systemThemes = ['light', 'dark'];
13
+
14
+ function updateDOM(theme) {
15
+ var attributes = Array.isArray(attribute) ? attribute : [attribute];
16
+ attributes.forEach(function(attr) {
17
+ var isClass = attr === 'class';
18
+ var classes = isClass && value ? themes.map(function(t) { return value[t] || t; }) : themes;
19
+ if (isClass) {
20
+ el.classList.remove.apply(el.classList, classes);
21
+ el.classList.add(value && value[theme] ? value[theme] : theme);
22
+ } else {
23
+ el.setAttribute(attr, value && value[theme] ? value[theme] : theme);
24
+ }
25
+ });
26
+ setColorScheme(theme);
27
+ }
28
+
29
+ function setColorScheme(theme) {
30
+ if (enableColorScheme && systemThemes.indexOf(theme) !== -1) {
31
+ el.style.colorScheme = theme;
32
+ }
33
+ }
34
+
35
+ function getSystemTheme() {
36
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
37
+ }
38
+
39
+ if (forcedTheme) {
40
+ updateDOM(forcedTheme);
41
+ } else {
42
+ try {
43
+ var themeName = localStorage.getItem(storageKey) || defaultTheme;
44
+ var isSystem = enableSystem && themeName === 'system';
45
+ var theme = isSystem ? getSystemTheme() : themeName;
46
+ updateDOM(theme);
47
+ } catch (e) {
48
+ updateDOM(defaultTheme === 'system' && enableSystem ? getSystemTheme() : defaultTheme);
49
+ }
50
+ }
51
+ })();
52
+ `.trim()}function o(e,t,s,r,a,i,n,m){return l(e,t,s,r,a,i,n,m).replace(/\s+/g," ").replace(/\s*{\s*/g,"{").replace(/\s*}\s*/g,"}").replace(/\s*;\s*/g,";").replace(/\s*,\s*/g,",").replace(/\s*\(\s*/g,"(").replace(/\s*\)\s*/g,")").trim()}export{l as getInlineScript,o as getMinifiedInlineScript};
53
+ //# sourceMappingURL=script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/script.ts"],"sourcesContent":["/**\n * Inline script that runs before page render to prevent flash\n * This script is injected into the head and executes immediately\n */\nexport function getInlineScript(\n\tattribute: string | string[],\n\tstorageKey: string,\n\tdefaultTheme: string,\n\tforcedTheme: string | undefined,\n\tthemes: string[],\n\tvalue: Record<string, string> | undefined,\n\tenableSystem: boolean,\n\tenableColorScheme: boolean\n): string {\n\t// The script must be minified and self-contained\n\tconst script = `\n(function() {\n var el = document.documentElement;\n var attribute = ${JSON.stringify(attribute)};\n var storageKey = ${JSON.stringify(storageKey)};\n var defaultTheme = ${JSON.stringify(defaultTheme)};\n var forcedTheme = ${JSON.stringify(forcedTheme)};\n var themes = ${JSON.stringify(themes)};\n var value = ${JSON.stringify(value)};\n var enableSystem = ${JSON.stringify(enableSystem)};\n var enableColorScheme = ${JSON.stringify(enableColorScheme)};\n var systemThemes = ['light', 'dark'];\n\n function updateDOM(theme) {\n var attributes = Array.isArray(attribute) ? attribute : [attribute];\n attributes.forEach(function(attr) {\n var isClass = attr === 'class';\n var classes = isClass && value ? themes.map(function(t) { return value[t] || t; }) : themes;\n if (isClass) {\n el.classList.remove.apply(el.classList, classes);\n el.classList.add(value && value[theme] ? value[theme] : theme);\n } else {\n el.setAttribute(attr, value && value[theme] ? value[theme] : theme);\n }\n });\n setColorScheme(theme);\n }\n\n function setColorScheme(theme) {\n if (enableColorScheme && systemThemes.indexOf(theme) !== -1) {\n el.style.colorScheme = theme;\n }\n }\n\n function getSystemTheme() {\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n if (forcedTheme) {\n updateDOM(forcedTheme);\n } else {\n try {\n var themeName = localStorage.getItem(storageKey) || defaultTheme;\n var isSystem = enableSystem && themeName === 'system';\n var theme = isSystem ? getSystemTheme() : themeName;\n updateDOM(theme);\n } catch (e) {\n updateDOM(defaultTheme === 'system' && enableSystem ? getSystemTheme() : defaultTheme);\n }\n }\n})();\n`.trim();\n\n\treturn script;\n}\n\n/**\n * Returns a minified version of the inline script\n */\nexport function getMinifiedInlineScript(\n\tattribute: string | string[],\n\tstorageKey: string,\n\tdefaultTheme: string,\n\tforcedTheme: string | undefined,\n\tthemes: string[],\n\tvalue: Record<string, string> | undefined,\n\tenableSystem: boolean,\n\tenableColorScheme: boolean\n): string {\n\t// Minified version for production\n\treturn getInlineScript(\n\t\tattribute,\n\t\tstorageKey,\n\t\tdefaultTheme,\n\t\tforcedTheme,\n\t\tthemes,\n\t\tvalue,\n\t\tenableSystem,\n\t\tenableColorScheme\n\t)\n\t\t.replace(/\\s+/g, \" \")\n\t\t.replace(/\\s*{\\s*/g, \"{\")\n\t\t.replace(/\\s*}\\s*/g, \"}\")\n\t\t.replace(/\\s*;\\s*/g, \";\")\n\t\t.replace(/\\s*,\\s*/g, \",\")\n\t\t.replace(/\\s*\\(\\s*/g, \"(\")\n\t\t.replace(/\\s*\\)\\s*/g, \")\")\n\t\t.trim();\n}\n"],"mappings":"AAIO,SAASA,EACfC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACS,CAuDT,MArDe;AAAA;AAAA;AAAA,oBAGI,KAAK,UAAUP,CAAS,CAAC;AAAA,qBACxB,KAAK,UAAUC,CAAU,CAAC;AAAA,uBACxB,KAAK,UAAUC,CAAY,CAAC;AAAA,sBAC7B,KAAK,UAAUC,CAAW,CAAC;AAAA,iBAChC,KAAK,UAAUC,CAAM,CAAC;AAAA,gBACvB,KAAK,UAAUC,CAAK,CAAC;AAAA,uBACd,KAAK,UAAUC,CAAY,CAAC;AAAA,4BACvB,KAAK,UAAUC,CAAiB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyC3D,KAAK,CAGP,CAKO,SAASC,EACfR,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACS,CAET,OAAOR,EACNC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,CACD,EACE,QAAQ,OAAQ,GAAG,EACnB,QAAQ,WAAY,GAAG,EACvB,QAAQ,WAAY,GAAG,EACvB,QAAQ,WAAY,GAAG,EACvB,QAAQ,WAAY,GAAG,EACvB,QAAQ,YAAa,GAAG,EACxB,QAAQ,YAAa,GAAG,EACxB,KAAK,CACR","names":["getInlineScript","attribute","storageKey","defaultTheme","forcedTheme","themes","value","enableSystem","enableColorScheme","getMinifiedInlineScript"]}
@@ -0,0 +1,21 @@
1
+ import * as astro from 'astro';
2
+
3
+ /**
4
+ * Astro Dev Toolbar App for theme switching
5
+ * Uses native Astro Dev Toolbar components for consistent UI
6
+ */
7
+ declare global {
8
+ interface Window {
9
+ __ASTRO_THEMES__?: {
10
+ theme: string;
11
+ resolvedTheme: string;
12
+ systemTheme: "dark" | "light";
13
+ forcedTheme?: string;
14
+ themes: string[];
15
+ setTheme: (theme: string | ((prevTheme: string) => string)) => void;
16
+ };
17
+ }
18
+ }
19
+ declare const _default: astro.DevToolbarApp;
20
+
21
+ export { _default as default };
@@ -0,0 +1,133 @@
1
+ import{defineToolbarApp as T}from"astro/toolbar";var C=T({init(v,u){let n=document.createElement("astro-dev-toolbar-window"),c=document.createElement("style");c.textContent=`
2
+ .theme-content {
3
+ display: flex;
4
+ flex-flow: row wrap;
5
+ }
6
+
7
+ .theme-section {
8
+ margin-bottom: 1rem;
9
+ }
10
+
11
+ .theme-section:last-child {
12
+ margin-bottom: 0;
13
+ }
14
+
15
+ .theme-label {
16
+ font-size: 0.75rem;
17
+ font-weight: 500;
18
+ color: rgba(255, 255, 255, 0.7);
19
+ margin-bottom: 0.5rem;
20
+ text-transform: uppercase;
21
+ letter-spacing: 0.05em;
22
+ }
23
+
24
+ .theme-status {
25
+ display: flex;
26
+ flex: 1 1 auto;
27
+ width: 100%;
28
+ align-items: center;
29
+ gap: 0.75rem;
30
+ padding: 1rem;
31
+ box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.10), 0px 4px 4px 0px rgba(0, 0, 0, 0.09), 0px 10px 6px 0px rgba(0, 0, 0, 0.05), 0px 17px 7px 0px rgba(0, 0, 0, 0.01), 0px 26px 7px 0px rgba(0, 0, 0, 0.00);
32
+ border: 1px solid rgba(35, 38, 45, 1);
33
+ border-radius: .5rem;
34
+ margin-bottom: 1rem;
35
+ }
36
+
37
+ .theme-status-icon {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ width: 24px;
42
+ height: 24px;
43
+ color: white;
44
+ }
45
+
46
+ .theme-status-icon svg {
47
+ width: 24px;
48
+ height: 24px;
49
+ }
50
+
51
+ .theme-status-text {
52
+ font-size: 0.875rem;
53
+ color: white;
54
+ }
55
+
56
+ .theme-status-resolved {
57
+ font-size: 0.75rem;
58
+ color: rgba(255, 255, 255, 0.5);
59
+ }
60
+
61
+ .theme-grid {
62
+ display: flex;
63
+ gap: 0.5rem;
64
+ }
65
+
66
+ .theme-grid astro-dev-toolbar-button svg {
67
+ width: 16px;
68
+ height: 16px;
69
+ }
70
+
71
+ .theme-divider {
72
+ width: 1px;
73
+ background: rgba(255, 255, 255, 0.1);
74
+ margin: 0 1rem;
75
+ }
76
+
77
+ @media (max-width: 562px) {
78
+ .theme-divider {
79
+ height: 1px;
80
+ margin: .2rem 0 1rem 0;
81
+ }
82
+ .theme-section, .theme-divider {
83
+ flex: 1 1 auto;
84
+ width: 100%;
85
+ }
86
+ }
87
+
88
+ .quick-actions {
89
+ display: flex;
90
+ gap: 0.5rem;
91
+ }
92
+
93
+ .quick-actions astro-dev-toolbar-button {
94
+ flex: 1;
95
+ }
96
+
97
+ .quick-actions astro-dev-toolbar-button svg {
98
+ width: 16px;
99
+ height: 16px;
100
+ }
101
+ `,n.appendChild(c);let x=`
102
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><!-- Icon from Carbon by IBM - undefined --><path fill="currentColor" d="M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z"/></svg>
103
+ `,w=`
104
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><!-- Icon from Carbon by IBM - undefined --><path fill="currentColor" d="M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3"/></svg>`,h=`
105
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><!-- Icon from Carbon by IBM - undefined --><path fill="currentColor" d="M10 30H4a2 2 0 0 1-2-2V16a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2M4 16v12h6V16Z"/><path fill="currentColor" d="M28 4H6a2 2 0 0 0-2 2v6h2V6h22v14H14v2h2v4h-2v2h9v-2h-5v-4h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2"/></svg>
106
+ `,f=`
107
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><!-- Icon from Carbon by IBM - undefined --><circle cx="10" cy="12" r="2" fill="currentColor"/><circle cx="16" cy="9" r="2" fill="currentColor"/><circle cx="22" cy="12" r="2" fill="currentColor"/><circle cx="23" cy="18" r="2" fill="currentColor"/><circle cx="19" cy="23" r="2" fill="currentColor"/><path fill="currentColor" d="M16.54 2A14 14 0 0 0 2 16a4.82 4.82 0 0 0 6.09 4.65l1.12-.31a3 3 0 0 1 3.79 2.9V27a3 3 0 0 0 3 3a14 14 0 0 0 14-14.54A14.05 14.05 0 0 0 16.54 2m8.11 22.31A11.93 11.93 0 0 1 16 28a1 1 0 0 1-1-1v-3.76a5 5 0 0 0-5-5a5 5 0 0 0-1.33.18l-1.12.31A2.82 2.82 0 0 1 4 16A12 12 0 0 1 16.47 4A12.18 12.18 0 0 1 28 15.53a11.9 11.9 0 0 1-3.35 8.79Z"/></svg>
108
+ `,r=document.createElement("div");r.className="theme-content",n.appendChild(r);function a(){return window.__ASTRO_THEMES__||{theme:"system",resolvedTheme:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light",systemTheme:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light",themes:["light","dark","system"],setTheme:e=>{let o=typeof e=="function"?e(localStorage.getItem("theme")||"system"):e;localStorage.setItem("theme",o),window.location.reload()}}}function m(e){switch(e){case"light":return x;case"dark":return w;case"system":return h;default:return f}}function i(){let e=a(),o=e.theme,g=e.resolvedTheme,b=e.themes.includes("system")?e.themes:[...e.themes,"system"];r.innerHTML=`
109
+ <div class="theme-status">
110
+ <span class="theme-status-icon">${m(g)}</span>
111
+ <div>
112
+ <div class="theme-status-text">${o.charAt(0).toUpperCase()+o.slice(1)}</div>
113
+ <div class="theme-status-resolved">Resolved: ${g}</div>
114
+ </div>
115
+ </div>
116
+
117
+ <div class="theme-section">
118
+ <div class="theme-label">Select Theme</div>
119
+ <div class="theme-grid" id="theme-grid"></div>
120
+ </div>
121
+
122
+ <div class="theme-divider"></div>
123
+
124
+ <div class="theme-section">
125
+ <div class="theme-label">Quick Actions</div>
126
+ <div class="theme-grid" id="quick-actions"></div>
127
+ </div>
128
+ `;let p=r.querySelector("#theme-grid");if(p)for(let t of b){let s=document.createElement("astro-dev-toolbar-button");s.innerHTML=`
129
+ <span style="display:inline-flex;align-items:center;gap:0.5rem;">
130
+ ${m(t).replace('width="32" height="32"','width="16" height="16"')} ${t.charAt(0).toUpperCase()+t.slice(1)}
131
+ </span>
132
+ `,s.size="medium",s.buttonStyle=t===o?"purple":"gray",s.dataset.theme=t,s.addEventListener("click",()=>{a().setTheme(t),setTimeout(i,50)}),p.appendChild(s)}let d=r.querySelector("#quick-actions");if(d){let t=document.createElement("astro-dev-toolbar-button");t.innerHTML='<span style="display:inline-flex;align-items:center;gap:0.5rem;"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 32 32"><path fill="currentColor" d="M26 18A10 10 0 1 1 16 8h6.182l-3.584 3.585L20 13l6-6l-6-6l-1.402 1.414L22.182 6H16a12 12 0 1 0 12 12Z"/></svg> Toggle</span>',t.size="medium",t.buttonStyle="outline",t.addEventListener("click",()=>{let l=a(),y=l.resolvedTheme==="dark"?"light":"dark";l.setTheme(y),setTimeout(i,50)}),d.appendChild(t);let s=document.createElement("astro-dev-toolbar-button");s.innerHTML=`<span style="display:inline-flex;align-items:center;gap:0.5rem;">${h.replace('width="32" height="32"','width="16" height="16"')} System</span>`,s.size="medium",s.buttonStyle="outline",s.addEventListener("click",()=>{a().setTheme("system"),setTimeout(i,50)}),d.appendChild(s)}}i(),v.appendChild(n),u.onToggled(({state:e})=>{n.style.display=e?"block":"none"}),n.style.display="none",window.addEventListener("astro-themes:change",()=>{i()}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{i()})}});export{C as default};
133
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/toolbar/app.ts"],"sourcesContent":["/**\n * Astro Dev Toolbar App for theme switching\n * Uses native Astro Dev Toolbar components for consistent UI\n */\n\nimport { defineToolbarApp } from \"astro/toolbar\";\n\n// Extend Window interface for this file\ndeclare global {\n\tinterface Window {\n\t\t__ASTRO_THEMES__?: {\n\t\t\ttheme: string;\n\t\t\tresolvedTheme: string;\n\t\t\tsystemTheme: \"dark\" | \"light\";\n\t\t\tforcedTheme?: string;\n\t\t\tthemes: string[];\n\t\t\tsetTheme: (theme: string | ((prevTheme: string) => string)) => void;\n\t\t};\n\t}\n}\n\nexport default defineToolbarApp({\n\tinit(canvas, app) {\n\t\t// Create native window component\n\t\tconst toolbarWindow = document.createElement(\n\t\t\t\"astro-dev-toolbar-window\"\n\t\t) as HTMLElement;\n\n\t\t// Add custom styles for our content\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.textContent = `\n\t\t\t.theme-content {\n\t\t\t\tdisplay: flex;\n \t\t\t\tflex-flow: row wrap;\n\t\t\t}\n\n\t\t\t.theme-section {\n\t\t\t\tmargin-bottom: 1rem;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-section:last-child {\n\t\t\t\tmargin-bottom: 0;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-label {\n\t\t\t\tfont-size: 0.75rem;\n\t\t\t\tfont-weight: 500;\n\t\t\t\tcolor: rgba(255, 255, 255, 0.7);\n\t\t\t\tmargin-bottom: 0.5rem;\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tletter-spacing: 0.05em;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-status {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex: 1 1 auto;\n\t\t\t\twidth: 100%;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 0.75rem;\n\t\t\t\tpadding: 1rem;\n\t\t\t\tbox-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.10), 0px 4px 4px 0px rgba(0, 0, 0, 0.09), 0px 10px 6px 0px rgba(0, 0, 0, 0.05), 0px 17px 7px 0px rgba(0, 0, 0, 0.01), 0px 26px 7px 0px rgba(0, 0, 0, 0.00);\n\t\t\t\tborder: 1px solid rgba(35, 38, 45, 1);\n\t\t\t\tborder-radius: .5rem;\n\t\t\t\tmargin-bottom: 1rem;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-status-icon {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\twidth: 24px;\n\t\t\t\theight: 24px;\n\t\t\t\tcolor: white;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-status-icon svg {\n\t\t\t\twidth: 24px;\n\t\t\t\theight: 24px;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-status-text {\n\t\t\t\tfont-size: 0.875rem;\n\t\t\t\tcolor: white;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-status-resolved {\n\t\t\t\tfont-size: 0.75rem;\n\t\t\t\tcolor: rgba(255, 255, 255, 0.5);\n\t\t\t}\n\t\t\t\n\t\t\t.theme-grid {\n\t\t\t\tdisplay: flex;\n\t\t\t\tgap: 0.5rem;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-grid astro-dev-toolbar-button svg {\n\t\t\t\twidth: 16px;\n\t\t\t\theight: 16px;\n\t\t\t}\n\t\t\t\n\t\t\t.theme-divider {\n\t\t\t\twidth: 1px;\n\t\t\t\tbackground: rgba(255, 255, 255, 0.1);\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\t\t\t\n\t\t\t@media (max-width: 562px) {\n\t\t\t\t.theme-divider {\n\t\t\t\t\theight: 1px;\n\t\t\t\t\tmargin: .2rem 0 1rem 0;\n\t\t\t\t}\n\t\t\t\t.theme-section, .theme-divider {\n\t\t\t\t\tflex: 1 1 auto;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t.quick-actions {\n\t\t\t\tdisplay: flex;\n\t\t\t\tgap: 0.5rem;\n\t\t\t}\n\t\t\t\n\t\t\t.quick-actions astro-dev-toolbar-button {\n\t\t\t\tflex: 1;\n\t\t\t}\n\t\t\t\n\t\t\t.quick-actions astro-dev-toolbar-button svg {\n\t\t\t\twidth: 16px;\n\t\t\t\theight: 16px;\n\t\t\t}\n\t\t`;\n\t\ttoolbarWindow.appendChild(style);\n\n\t\tconst lightModeIcon = `\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><!-- Icon from Carbon by IBM - undefined --><path fill=\"currentColor\" d=\"M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z\"/></svg>\n\t\t`;\n\t\tconst darkModeIcon = `\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><!-- Icon from Carbon by IBM - undefined --><path fill=\"currentColor\" d=\"M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3\"/></svg>`;\n\t\tconst systemModeIcon = `\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><!-- Icon from Carbon by IBM - undefined --><path fill=\"currentColor\" d=\"M10 30H4a2 2 0 0 1-2-2V16a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2M4 16v12h6V16Z\"/><path fill=\"currentColor\" d=\"M28 4H6a2 2 0 0 0-2 2v6h2V6h22v14H14v2h2v4h-2v2h9v-2h-5v-4h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2\"/></svg>\n\t\t`;\n\t\tconst defaultModeIcon = `\n\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\"><!-- Icon from Carbon by IBM - undefined --><circle cx=\"10\" cy=\"12\" r=\"2\" fill=\"currentColor\"/><circle cx=\"16\" cy=\"9\" r=\"2\" fill=\"currentColor\"/><circle cx=\"22\" cy=\"12\" r=\"2\" fill=\"currentColor\"/><circle cx=\"23\" cy=\"18\" r=\"2\" fill=\"currentColor\"/><circle cx=\"19\" cy=\"23\" r=\"2\" fill=\"currentColor\"/><path fill=\"currentColor\" d=\"M16.54 2A14 14 0 0 0 2 16a4.82 4.82 0 0 0 6.09 4.65l1.12-.31a3 3 0 0 1 3.79 2.9V27a3 3 0 0 0 3 3a14 14 0 0 0 14-14.54A14.05 14.05 0 0 0 16.54 2m8.11 22.31A11.93 11.93 0 0 1 16 28a1 1 0 0 1-1-1v-3.76a5 5 0 0 0-5-5a5 5 0 0 0-1.33.18l-1.12.31A2.82 2.82 0 0 1 4 16A12 12 0 0 1 16.47 4A12.18 12.18 0 0 1 28 15.53a11.9 11.9 0 0 1-3.35 8.79Z\"/></svg>\n\t\t`;\n\n\t\t// Create content container\n\t\tconst content = document.createElement(\"div\");\n\t\tcontent.className = \"theme-content\";\n\t\ttoolbarWindow.appendChild(content);\n\n\t\tfunction getThemeState() {\n\t\t\treturn (\n\t\t\t\twindow.__ASTRO_THEMES__ || {\n\t\t\t\t\ttheme: \"system\",\n\t\t\t\t\tresolvedTheme: window.matchMedia(\"(prefers-color-scheme: dark)\")\n\t\t\t\t\t\t.matches\n\t\t\t\t\t\t? \"dark\"\n\t\t\t\t\t\t: \"light\",\n\t\t\t\t\tsystemTheme: window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n\t\t\t\t\t\t? (\"dark\" as const)\n\t\t\t\t\t\t: (\"light\" as const),\n\t\t\t\t\tthemes: [\"light\", \"dark\", \"system\"],\n\t\t\t\t\tsetTheme: (theme: string | ((prev: string) => string)) => {\n\t\t\t\t\t\tconst themeValue =\n\t\t\t\t\t\t\ttypeof theme === \"function\"\n\t\t\t\t\t\t\t\t? theme(localStorage.getItem(\"theme\") || \"system\")\n\t\t\t\t\t\t\t\t: theme;\n\t\t\t\t\t\tlocalStorage.setItem(\"theme\", themeValue);\n\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\tfunction getThemeIcon(theme: string): string {\n\t\t\tswitch (theme) {\n\t\t\t\tcase \"light\":\n\t\t\t\t\treturn lightModeIcon;\n\t\t\t\tcase \"dark\":\n\t\t\t\t\treturn darkModeIcon;\n\t\t\t\tcase \"system\":\n\t\t\t\t\treturn systemModeIcon;\n\t\t\t\tdefault:\n\t\t\t\t\treturn defaultModeIcon;\n\t\t\t}\n\t\t}\n\n\t\tfunction render() {\n\t\t\tconst state = getThemeState();\n\t\t\tconst currentTheme = state.theme;\n\t\t\tconst resolvedTheme = state.resolvedTheme;\n\t\t\tconst themes = state.themes.includes(\"system\")\n\t\t\t\t? state.themes\n\t\t\t\t: [...state.themes, \"system\"];\n\n\t\t\tcontent.innerHTML = `\n\t\t\t\t<div class=\"theme-status\">\n\t\t\t\t\t<span class=\"theme-status-icon\">${getThemeIcon(resolvedTheme)}</span>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<div class=\"theme-status-text\">${currentTheme.charAt(0).toUpperCase() + currentTheme.slice(1)}</div>\n\t\t\t\t\t\t<div class=\"theme-status-resolved\">Resolved: ${resolvedTheme}</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"theme-section\">\n\t\t\t\t\t<div class=\"theme-label\">Select Theme</div>\n\t\t\t\t\t<div class=\"theme-grid\" id=\"theme-grid\"></div>\n\t\t\t\t</div>\n\t\t\t\t\n\t\t\t\t<div class=\"theme-divider\"></div>\n\t\t\t\t\n\t\t\t\t<div class=\"theme-section\">\n\t\t\t\t\t<div class=\"theme-label\">Quick Actions</div>\n\t\t\t\t\t<div class=\"theme-grid\" id=\"quick-actions\"></div>\n\t\t\t\t</div>\n\t\t\t`;\n\n\t\t\t// Create theme buttons using native components\n\t\t\tconst themeGrid = content.querySelector(\"#theme-grid\");\n\t\t\tif (themeGrid) {\n\t\t\t\tfor (const theme of themes) {\n\t\t\t\t\tconst btn = document.createElement(\n\t\t\t\t\t\t\"astro-dev-toolbar-button\"\n\t\t\t\t\t) as HTMLElement & {\n\t\t\t\t\t\tbuttonStyle: string;\n\t\t\t\t\t\tsize: string;\n\t\t\t\t\t};\n\t\t\t\t\tbtn.innerHTML = `\n\t\t\t\t\t\t<span style=\"display:inline-flex;align-items:center;gap:0.5rem;\">\n\t\t\t\t\t\t\t${getThemeIcon(theme).replace('width=\"32\" height=\"32\"', 'width=\"16\" height=\"16\"')} ${theme.charAt(0).toUpperCase() + theme.slice(1)}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t`;\n\t\t\t\t\tbtn.size = \"medium\";\n\t\t\t\t\tbtn.buttonStyle = theme === currentTheme ? \"purple\" : \"gray\";\n\t\t\t\t\tbtn.dataset.theme = theme;\n\t\t\t\t\tbtn.addEventListener(\"click\", () => {\n\t\t\t\t\t\tconst state = getThemeState();\n\t\t\t\t\t\tstate.setTheme(theme);\n\t\t\t\t\t\tsetTimeout(render, 50);\n\t\t\t\t\t});\n\t\t\t\t\tthemeGrid.appendChild(btn);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Create quick action buttons\n\t\t\tconst quickActions = content.querySelector(\"#quick-actions\");\n\t\t\tif (quickActions) {\n\t\t\t\t// Toggle button\n\t\t\t\tconst toggleBtn = document.createElement(\n\t\t\t\t\t\"astro-dev-toolbar-button\"\n\t\t\t\t) as HTMLElement & {\n\t\t\t\t\tbuttonStyle: string;\n\t\t\t\t\tsize: string;\n\t\t\t\t};\n\t\t\t\ttoggleBtn.innerHTML = `<span style=\"display:inline-flex;align-items:center;gap:0.5rem;\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 32 32\"><path fill=\"currentColor\" d=\"M26 18A10 10 0 1 1 16 8h6.182l-3.584 3.585L20 13l6-6l-6-6l-1.402 1.414L22.182 6H16a12 12 0 1 0 12 12Z\"/></svg> Toggle</span>`;\n\t\t\t\ttoggleBtn.size = \"medium\";\n\t\t\t\ttoggleBtn.buttonStyle = \"outline\";\n\t\t\t\ttoggleBtn.addEventListener(\"click\", () => {\n\t\t\t\t\tconst state = getThemeState();\n\t\t\t\t\tconst newTheme = state.resolvedTheme === \"dark\" ? \"light\" : \"dark\";\n\t\t\t\t\tstate.setTheme(newTheme);\n\t\t\t\t\tsetTimeout(render, 50);\n\t\t\t\t});\n\t\t\t\tquickActions.appendChild(toggleBtn);\n\n\t\t\t\t// System button\n\t\t\t\tconst systemBtn = document.createElement(\n\t\t\t\t\t\"astro-dev-toolbar-button\"\n\t\t\t\t) as HTMLElement & {\n\t\t\t\t\tbuttonStyle: string;\n\t\t\t\t\tsize: string;\n\t\t\t\t};\n\t\t\t\tsystemBtn.innerHTML = `<span style=\"display:inline-flex;align-items:center;gap:0.5rem;\">${systemModeIcon.replace('width=\"32\" height=\"32\"', 'width=\"16\" height=\"16\"')} System</span>`;\n\t\t\t\tsystemBtn.size = \"medium\";\n\t\t\t\tsystemBtn.buttonStyle = \"outline\";\n\t\t\t\tsystemBtn.addEventListener(\"click\", () => {\n\t\t\t\t\tconst state = getThemeState();\n\t\t\t\t\tstate.setTheme(\"system\");\n\t\t\t\t\tsetTimeout(render, 50);\n\t\t\t\t});\n\t\t\t\tquickActions.appendChild(systemBtn);\n\t\t\t}\n\t\t}\n\n\t\t// Initial render\n\t\trender();\n\n\t\t// Append window to canvas\n\t\tcanvas.appendChild(toolbarWindow);\n\n\t\t// Handle app toggle visibility\n\t\tapp.onToggled(({ state }) => {\n\t\t\ttoolbarWindow.style.display = state ? \"block\" : \"none\";\n\t\t});\n\n\t\t// Initially hidden until toggled\n\t\ttoolbarWindow.style.display = \"none\";\n\n\t\t// Listen for theme changes\n\t\twindow.addEventListener(\"astro-themes:change\", () => {\n\t\t\trender();\n\t\t});\n\n\t\t// Listen for system theme changes\n\t\twindow\n\t\t\t.matchMedia(\"(prefers-color-scheme: dark)\")\n\t\t\t.addEventListener(\"change\", () => {\n\t\t\t\trender();\n\t\t\t});\n\t},\n});\n"],"mappings":"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","names":["defineToolbarApp","app_default","canvas","app","toolbarWindow","style","lightModeIcon","darkModeIcon","systemModeIcon","defaultModeIcon","content","getThemeState","theme","themeValue","getThemeIcon","render","state","currentTheme","resolvedTheme","themes","themeGrid","btn","quickActions","toggleBtn","newTheme","systemBtn"]}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Type definitions for astro-themes
3
+ * Mirrors the next-themes API for Astro
4
+ */
5
+ /** Data attribute pattern for HTML attributes */
6
+ type DataAttribute = `data-${string}`;
7
+ /** Attribute type - can be 'class' or any data-* attribute */
8
+ type Attribute = DataAttribute | "class";
9
+ /** Mapping of theme name to HTML attribute value */
10
+ interface ValueObject {
11
+ [themeName: string]: string;
12
+ }
13
+ /** Props for the inline script element */
14
+ interface ScriptProps {
15
+ [key: string]: unknown;
16
+ }
17
+ /**
18
+ * ThemeProvider component props
19
+ * All your theme configuration is passed to ThemeProvider
20
+ */
21
+ interface ThemeProviderProps {
22
+ /**
23
+ * Key used to store theme setting in localStorage
24
+ * @default 'theme'
25
+ */
26
+ storageKey?: string;
27
+ /**
28
+ * Default theme name. If `enableSystem` is false, the default theme is 'light'
29
+ * @default 'system'
30
+ */
31
+ defaultTheme?: string;
32
+ /**
33
+ * Forced theme name for the current page (does not modify saved theme settings)
34
+ */
35
+ forcedTheme?: string;
36
+ /**
37
+ * Whether to switch between `dark` and `light` based on `prefers-color-scheme`
38
+ * @default true
39
+ */
40
+ enableSystem?: boolean;
41
+ /**
42
+ * Whether to indicate to browsers which color scheme is used (dark or light)
43
+ * for built-in UI like inputs and buttons
44
+ * @default true
45
+ */
46
+ enableColorScheme?: boolean;
47
+ /**
48
+ * Optionally disable all CSS transitions when switching themes
49
+ * @default false
50
+ */
51
+ disableTransitionOnChange?: boolean;
52
+ /**
53
+ * List of theme names
54
+ * @default ['light', 'dark']
55
+ */
56
+ themes?: string[];
57
+ /**
58
+ * HTML attribute modified based on the active theme
59
+ * Accepts `class` and `data-*` (meaning any data attribute, `data-mode`, `data-color`, etc.)
60
+ * Can also be an array to set multiple attributes
61
+ * @default 'data-theme'
62
+ */
63
+ attribute?: Attribute | Attribute[];
64
+ /**
65
+ * Optional mapping of theme name to attribute value
66
+ * value is an `object` where key is the theme name and value is the attribute value
67
+ */
68
+ value?: ValueObject;
69
+ /**
70
+ * Optional nonce passed to the injected `script` tag, used to allow-list the script in your CSP
71
+ */
72
+ nonce?: string;
73
+ /**
74
+ * Optional props to pass to the injected `script` tag
75
+ */
76
+ scriptProps?: ScriptProps;
77
+ }
78
+ /**
79
+ * Theme state exposed to client-side JavaScript
80
+ * Available via window.__ASTRO_THEMES__
81
+ */
82
+ interface ThemeState {
83
+ /** Active theme name */
84
+ theme: string;
85
+ /** If the active theme is "system", this returns whether the system preference resolved to "dark" or "light" */
86
+ resolvedTheme: string;
87
+ /** System theme preference ("dark" or "light") regardless of active theme */
88
+ systemTheme: "dark" | "light";
89
+ /** Forced page theme or undefined */
90
+ forcedTheme?: string;
91
+ /** The list of themes */
92
+ themes: string[];
93
+ /** Update the theme */
94
+ setTheme: (theme: string | ((prevTheme: string) => string)) => void;
95
+ }
96
+ /**
97
+ * Configuration for the astro-themes integration
98
+ */
99
+ interface AstroThemesConfig {
100
+ /**
101
+ * Inject the flash-prevention script automatically via integration
102
+ * When true, you don't need to use ThemeProvider component for basic setup
103
+ * @default false
104
+ */
105
+ injectScript?: boolean;
106
+ /**
107
+ * Default props for the ThemeProvider component or injected script
108
+ */
109
+ defaultProps?: Partial<ThemeProviderProps>;
110
+ /**
111
+ * Enable the Dev Toolbar App for theme switching during development
112
+ * @default true
113
+ */
114
+ devToolbar?: boolean;
115
+ }
116
+
117
+ export type { AstroThemesConfig, Attribute, ScriptProps, ThemeProviderProps, ThemeState, ValueObject };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@lpdsgn/astro-themes",
3
+ "version": "0.1.0",
4
+ "description": "Perfect dark mode in Astro with no flash. System preference, multiple themes, and sync across tabs.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./component": "./dist/components/ThemeProvider.astro",
13
+ "./ThemeProvider.astro": "./dist/components/ThemeProvider.astro",
14
+ "./client": {
15
+ "types": "./dist/client.d.ts",
16
+ "default": "./dist/client.js"
17
+ },
18
+ "./types": {
19
+ "types": "./dist/types.d.ts",
20
+ "default": "./dist/types.js"
21
+ },
22
+ "./toolbar": {
23
+ "types": "./dist/toolbar/app.d.ts",
24
+ "default": "./dist/toolbar/app.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "dev": "tsup --watch",
32
+ "build": "tsup",
33
+ "prepublishOnly": "pnpm build"
34
+ },
35
+ "keywords": [
36
+ "astro-integration",
37
+ "astro-component",
38
+ "withastro",
39
+ "astro",
40
+ "dark-mode",
41
+ "themes",
42
+ "theme-provider",
43
+ "next-themes"
44
+ ],
45
+ "author": "",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/LPdsgn/astro-themes.git"
49
+ },
50
+ "bugs": "https://github.com/LPdsgn/astro-themes/issues",
51
+ "homepage": "https://github.com/LPdsgn/astro-themes#readme",
52
+ "license": "MIT",
53
+ "peerDependencies": {
54
+ "astro": "^5.1.3"
55
+ },
56
+ "dependencies": {
57
+ "astro-integration-kit": "0.19.1",
58
+ "zod": "^4.3.5"
59
+ },
60
+ "devDependencies": {
61
+ "tsup": "8.5.1"
62
+ }
63
+ }