svelte-os-themes 0.0.2

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,59 @@
1
+ # Svelte OS Themes
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install svelte-os-themes
7
+ ```
8
+
9
+ ## Usage
10
+
11
+ ```svelte
12
+ <!-- +layout.svelte -->
13
+ <script>
14
+ import { ThemeProvider } from 'svelte-os-themes';
15
+
16
+ let { children } = $props();
17
+ </script>
18
+
19
+ <ThemeProvider>
20
+ {@render children()}
21
+ </ThemeProvider>
22
+ ```
23
+
24
+ ```svelte
25
+ <!-- +page.svelte -->
26
+ <script>
27
+ import { useTheme } from 'svelte-os-themes';
28
+
29
+ let theme = useTheme();
30
+ </script>
31
+
32
+ <button
33
+ type="button"
34
+ onclick={function () {
35
+ theme.value = 'light';
36
+ }}
37
+ data-selected={theme.value === 'light'}
38
+ >
39
+ Light
40
+ </button>
41
+ <button
42
+ type="button"
43
+ onclick={function () {
44
+ theme.value = 'dark';
45
+ }}
46
+ data-selected={theme.value === 'dark'}
47
+ >
48
+ Dark
49
+ </button>
50
+ <button
51
+ type="button"
52
+ onclick={function () {
53
+ theme.value = 'system';
54
+ }}
55
+ data-selected={theme.value === 'system'}
56
+ >
57
+ System
58
+ </button>
59
+ ```
@@ -0,0 +1,17 @@
1
+ export type Theme = 'dark' | 'light' | 'system';
2
+ export interface CreateThemeContextConfig {
3
+ attribute?: 'class' | `data-${string}`;
4
+ storageKey?: string;
5
+ systemPreference?: boolean;
6
+ nonce?: string;
7
+ }
8
+ export type CreateThemeContextReturn = ReturnType<typeof createThemeContext>;
9
+ export declare function createThemeContext(config?: CreateThemeContextConfig): {
10
+ theme: Theme;
11
+ readonly effects: {
12
+ setup(): void;
13
+ themeChanged(): void;
14
+ osThemeChanged(): (() => void) | undefined;
15
+ };
16
+ readonly script: string;
17
+ };
@@ -0,0 +1,121 @@
1
+ export function createThemeContext(config) {
2
+ const {
3
+ /**/
4
+ attribute, storageKey, systemPreference, nonce, } = $derived.by(() => {
5
+ const attribute = config?.attribute ?? 'class';
6
+ const storageKey = config?.storageKey ?? 'theme';
7
+ const systemPreference = config?.systemPreference ?? true;
8
+ const nonce = config?.nonce ?? '';
9
+ return {
10
+ attribute,
11
+ storageKey,
12
+ systemPreference,
13
+ nonce,
14
+ };
15
+ });
16
+ let theme = $state('system');
17
+ const script = $derived(buildScript({
18
+ nonce,
19
+ attribute,
20
+ storageKey,
21
+ }));
22
+ const effects = $derived({
23
+ setup() {
24
+ theme = getLocalStorageTheme(storageKey);
25
+ },
26
+ themeChanged() {
27
+ const html = document.documentElement;
28
+ const head = document.head;
29
+ const style = document.createElement('style');
30
+ style.innerHTML = noTransitionStyle;
31
+ head.appendChild(style);
32
+ const originalTheme = theme;
33
+ const resolvedTheme = originalTheme === 'system'
34
+ ? window.matchMedia('(prefers-color-scheme: dark)').matches
35
+ ? 'dark'
36
+ : 'light'
37
+ : originalTheme;
38
+ if (attribute === 'class') {
39
+ const removeClass = resolvedTheme === 'dark' ? 'light' : 'dark';
40
+ html.classList.remove(removeClass);
41
+ html.classList.add(resolvedTheme);
42
+ }
43
+ else {
44
+ html.setAttribute(attribute, resolvedTheme);
45
+ }
46
+ localStorage.setItem(storageKey, originalTheme);
47
+ html.style.colorScheme = resolvedTheme;
48
+ setTimeout(() => {
49
+ head.removeChild(style);
50
+ }, 1);
51
+ },
52
+ osThemeChanged() {
53
+ if (!systemPreference)
54
+ return;
55
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
56
+ function handler(e) {
57
+ theme = e.matches ? 'dark' : 'light';
58
+ }
59
+ mediaQuery.addEventListener('change', handler);
60
+ return () => {
61
+ mediaQuery.removeEventListener('change', handler);
62
+ };
63
+ },
64
+ });
65
+ return {
66
+ get theme() {
67
+ return theme;
68
+ },
69
+ set theme(value) {
70
+ theme = value;
71
+ },
72
+ get effects() {
73
+ return effects;
74
+ },
75
+ get script() {
76
+ return script;
77
+ },
78
+ };
79
+ }
80
+ function getLocalStorageTheme(key) {
81
+ const value = localStorage.getItem(key);
82
+ if (value?.toLocaleLowerCase().trim() === 'dark')
83
+ return 'dark';
84
+ if (value?.toLocaleLowerCase().trim() === 'light')
85
+ return 'light';
86
+ return 'system';
87
+ }
88
+ function buildScript({ nonce, attribute, storageKey, }) {
89
+ return `
90
+ <script nonce="${nonce}">
91
+ (function(k, a) {
92
+ let h = document.documentElement;
93
+ let q = window.matchMedia('(prefers-color-scheme: dark)')
94
+ let s = localStorage.getItem(k)?.toLowerCase().trim();
95
+
96
+ let l = [
97
+ 'dark',
98
+ 'light',
99
+ 'system'
100
+ ];
101
+
102
+ let v = l.includes(s) ? s : 'system';
103
+ let t = v === 'system' ? q.matches ? 'dark' : 'light' : v;
104
+
105
+ if (a === 'class') {
106
+ h.classList.remove(t === 'dark' ? 'light' : 'dark');
107
+ h.classList.add(t);
108
+ } else {
109
+ h.setAttribute(a, t);
110
+ }
111
+
112
+ localStorage.setItem(k, v);
113
+ h.style.colorScheme = t;
114
+ })(
115
+ '${storageKey}',
116
+ '${attribute}',
117
+ );
118
+ </script>
119
+ `;
120
+ }
121
+ const noTransitionStyle = '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important;}';
@@ -0,0 +1,2 @@
1
+ export { default as ThemeProvider } from './theme-provider.svelte';
2
+ export { default as useTheme } from './use-theme.svelte.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default as ThemeProvider } from './theme-provider.svelte';
2
+ export { default as useTheme } from './use-theme.svelte.js';
@@ -0,0 +1,17 @@
1
+ <script lang="ts">import { setContext } from "svelte";
2
+ import {
3
+ createThemeContext
4
+ } from "./create-theme-context.svelte.js";
5
+ let { children, ...props } = $props();
6
+ let context = createThemeContext(props);
7
+ $effect(context.effects.setup);
8
+ $effect(context.effects.themeChanged);
9
+ $effect(context.effects.osThemeChanged);
10
+ setContext("theme", context);
11
+ </script>
12
+
13
+ {@render children?.()}
14
+
15
+ <svelte:head>
16
+ {@html context.script}
17
+ </svelte:head>
@@ -0,0 +1,19 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type CreateThemeContextConfig } from './create-theme-context.svelte.js';
3
+ declare const __propDef: {
4
+ props: CreateThemeContextConfig & {
5
+ children?: ((this: void) => typeof import("svelte").SnippetReturn & {
6
+ _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
7
+ }) | undefined;
8
+ };
9
+ events: {
10
+ [evt: string]: CustomEvent<any>;
11
+ };
12
+ slots: {};
13
+ };
14
+ export type ThemeProviderProps = typeof __propDef.props;
15
+ export type ThemeProviderEvents = typeof __propDef.events;
16
+ export type ThemeProviderSlots = typeof __propDef.slots;
17
+ export default class ThemeProvider extends SvelteComponent<ThemeProviderProps, ThemeProviderEvents, ThemeProviderSlots> {
18
+ }
19
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { Theme } from './create-theme-context.svelte.js';
2
+ export default function useTheme(): {
3
+ value: Theme;
4
+ };
@@ -0,0 +1,14 @@
1
+ import { getContext } from 'svelte';
2
+ export default function useTheme() {
3
+ const context = getContext('theme');
4
+ return {
5
+ get value() {
6
+ return context.theme;
7
+ },
8
+ set value(theme) {
9
+ if (context) {
10
+ context.theme = theme;
11
+ }
12
+ },
13
+ };
14
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "svelte-os-themes",
3
+ "type": "module",
4
+ "version": "0.0.2",
5
+ "svelte": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "svelte": "./dist/index.js",
11
+ "package.json": "./package.json"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "!dist/**/*.test.*",
17
+ "!dist/**/*.spec.*"
18
+ ],
19
+ "keywords": [
20
+ "Svelte",
21
+ "SvelteKit",
22
+ "theme provider",
23
+ "theme",
24
+ "themes",
25
+ "dark mode"
26
+ ],
27
+ "repository": "https://github.com/calvo-jp/svelte-themes",
28
+ "bugs": {
29
+ "url": "https://github.com/calvo-jp/svelte-themes/issues"
30
+ },
31
+ "author": {
32
+ "name": "John Paul Calvo",
33
+ "email": "calvojp92@gmail.com"
34
+ },
35
+ "peerDependencies": {
36
+ "svelte": ">=5.0.0"
37
+ }
38
+ }