ngx-theme-stack 0.0.1 → 0.0.3
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 +2 -2
- package/fesm2022/ngx-theme-stack.mjs +25 -13
- package/fesm2022/ngx-theme-stack.mjs.map +1 -1
- package/package.json +4 -1
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/index.d.ts +13 -0
- package/schematics/ng-add/index.js +137 -0
- package/schematics/ng-add/index.js.map +1 -0
- package/schematics/ng-add/schema.json +42 -0
- package/types/ngx-theme-stack.d.ts +3 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A robust, SSR-safe, and signal-based theme management library for Angular applic
|
|
|
14
14
|
### Installation
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
ng add ngx-theme-stack
|
|
17
|
+
ng add @wanderleedev/ngx-theme-stack
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
### Usage
|
|
@@ -31,7 +31,7 @@ import { ThemeService } from 'ngx-theme-stack';
|
|
|
31
31
|
<button (click)="theme.toggleTheme()">Toggle Theme</button>
|
|
32
32
|
<p>Current theme: {{ theme.currentTheme() }}</p>
|
|
33
33
|
`,
|
|
34
|
-
imports: []
|
|
34
|
+
imports: [],
|
|
35
35
|
})
|
|
36
36
|
export class AppComponent {
|
|
37
37
|
theme = inject(ThemeService);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isPlatformBrowser } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { InjectionToken, inject, DestroyRef, DOCUMENT,
|
|
3
|
+
import { InjectionToken, inject, DestroyRef, DOCUMENT, PLATFORM_ID, signal, computed, effect, Injectable } from '@angular/core';
|
|
4
4
|
|
|
5
5
|
/** Built-in themes. All other values are considered custom themes. */
|
|
6
6
|
const DEFAULT_THEMES = ['light', 'dark', 'system'];
|
|
@@ -38,7 +38,6 @@ class CoreThemeService {
|
|
|
38
38
|
#config = inject(NGX_THEME_STACK_CONFIG);
|
|
39
39
|
#destroyRef = inject(DestroyRef);
|
|
40
40
|
#document = inject(DOCUMENT);
|
|
41
|
-
#renderer = inject(RendererFactory2).createRenderer(null, null);
|
|
42
41
|
#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
43
42
|
// ── Theme configuration ───────────────────────────────────────────────────
|
|
44
43
|
/** List of available themes for Select/Cycle services. Defaults to ['light', 'dark', 'system']. */
|
|
@@ -92,6 +91,7 @@ class CoreThemeService {
|
|
|
92
91
|
throw new Error(`[ngx-theme-stack] Invalid theme: "${theme}". Valid values are: ${[...this.#validThemes].join(', ')}.`);
|
|
93
92
|
}
|
|
94
93
|
if (theme === 'system') {
|
|
94
|
+
this.#systemPreference.set(this.resolveSystemPreference());
|
|
95
95
|
this.startSystemThemeListener();
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
@@ -107,8 +107,7 @@ class CoreThemeService {
|
|
|
107
107
|
resolveInitialTheme() {
|
|
108
108
|
if (!this.#isBrowser)
|
|
109
109
|
return this.#config.theme;
|
|
110
|
-
|
|
111
|
-
return saved && this.#validThemes.has(saved) ? saved : this.#config.theme;
|
|
110
|
+
return this.readStoredTheme() ?? this.#config.theme;
|
|
112
111
|
}
|
|
113
112
|
startSystemThemeListener() {
|
|
114
113
|
if (!this.#mediaQuery)
|
|
@@ -125,23 +124,36 @@ class CoreThemeService {
|
|
|
125
124
|
const host = this.#document.documentElement;
|
|
126
125
|
const { mode } = this.#config;
|
|
127
126
|
if (mode === 'attribute' || mode === 'both') {
|
|
128
|
-
this
|
|
127
|
+
this.applyThemeAttribute(host, userTheme);
|
|
129
128
|
}
|
|
130
129
|
if (mode === 'class' || mode === 'both') {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
this.applyThemeClasses(host, userTheme);
|
|
131
|
+
}
|
|
132
|
+
this.applyColorSchemeHint(host, userTheme);
|
|
133
|
+
}
|
|
134
|
+
applyThemeAttribute(host, theme) {
|
|
135
|
+
host.setAttribute('data-theme', theme);
|
|
136
|
+
}
|
|
137
|
+
applyThemeClasses(host, theme) {
|
|
138
|
+
for (const t of this.availableThemes) {
|
|
139
|
+
host.classList.remove(t);
|
|
135
140
|
}
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
host.classList.add(theme);
|
|
142
|
+
}
|
|
143
|
+
applyColorSchemeHint(host, theme) {
|
|
144
|
+
if (theme === 'dark' || theme === 'light') {
|
|
145
|
+
host.style.setProperty('color-scheme', theme);
|
|
138
146
|
return;
|
|
139
147
|
}
|
|
140
|
-
|
|
148
|
+
host.style.removeProperty('color-scheme');
|
|
141
149
|
}
|
|
142
150
|
readStoredTheme() {
|
|
143
151
|
try {
|
|
144
|
-
|
|
152
|
+
const stored = localStorage.getItem(this.#config.storageKey);
|
|
153
|
+
if (stored && this.#validThemes.has(stored)) {
|
|
154
|
+
return stored;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
145
157
|
}
|
|
146
158
|
catch (e) {
|
|
147
159
|
console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-theme-stack.mjs","sources":["../../../projects/ngx-theme-stack/src/lib/types.ts","../../../projects/ngx-theme-stack/src/lib/theme-stack.config.ts","../../../projects/ngx-theme-stack/src/lib/core-theme.service.ts","../../../projects/ngx-theme-stack/src/lib/theme-cycle.service.ts","../../../projects/ngx-theme-stack/src/lib/theme-select.service.ts","../../../projects/ngx-theme-stack/src/lib/theme-toggle.service.ts","../../../projects/ngx-theme-stack/src/ngx-theme-stack.ts"],"sourcesContent":["/** Built-in themes. All other values are considered custom themes. */\nexport const DEFAULT_THEMES = ['light', 'dark', 'system'] as const;\n\n/** String union with autocompletion for defaults + any string support for customization. */\nexport type NgTheme = (typeof DEFAULT_THEMES)[number] | (string & {});\n\nexport type NgSystemTheme = Exclude<NgTheme, 'system'>;\nexport type NgMode = 'attribute' | 'class' | 'both';\n\nexport interface NgConfig {\n theme: NgTheme;\n storageKey: string;\n mode: NgMode;\n themes: NgTheme[];\n}\n","import { InjectionToken } from '@angular/core';\nimport { DEFAULT_THEMES, NgConfig } from './types';\n\nexport const DEFAULT_NG_CONFIG = {\n theme: 'system',\n storageKey: 'ngx-theme-stack-theme',\n mode: 'class',\n themes: [...DEFAULT_THEMES],\n} satisfies NgConfig;\n\nexport const NGX_THEME_STACK_CONFIG = new InjectionToken<NgConfig>('NGX_THEME_STACK_CONFIG', {\n factory: () => DEFAULT_NG_CONFIG,\n});\n\n/**\n * Helper function to provide Theme Stack configuration.\n */\nexport function provideThemeStack(config: Partial<NgConfig> = {}) {\n return {\n provide: NGX_THEME_STACK_CONFIG,\n useValue: {\n ...DEFAULT_NG_CONFIG,\n ...config,\n } satisfies NgConfig,\n };\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n DOCUMENT,\n effect,\n inject,\n Injectable,\n PLATFORM_ID,\n RendererFactory2,\n signal,\n} from '@angular/core';\nimport { NGX_THEME_STACK_CONFIG } from './theme-stack.config';\nimport { NgSystemTheme, NgTheme } from './types';\n\n/**\n * Core service for managing the application's color theme.\n *\n * Handles theme persistence, system preference detection, and DOM updates.\n * Supports built-in themes ('dark', 'light', 'system') and custom extensions.\n */\n@Injectable({ providedIn: 'root' })\nexport class CoreThemeService {\n // ── Dependencies ──────────────────────────────────────────────────────────\n\n readonly #config = inject(NGX_THEME_STACK_CONFIG);\n readonly #destroyRef = inject(DestroyRef);\n readonly #document = inject(DOCUMENT);\n readonly #renderer = inject(RendererFactory2).createRenderer(null, null);\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n // ── Theme configuration ───────────────────────────────────────────────────\n\n /** List of available themes for Select/Cycle services. Defaults to ['light', 'dark', 'system']. */\n readonly availableThemes = this.#config.themes;\n\n /** Internal Set for O(1) existence checks. */\n readonly #validThemes = new Set<NgTheme>(this.availableThemes);\n\n // ── System preference ─────────────────────────────────────────────────────\n\n /** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */\n readonly #mediaQuery: MediaQueryList | null = this.#isBrowser\n ? (this.#document.defaultView?.matchMedia('(prefers-color-scheme: dark)') ?? null)\n : null;\n\n readonly #systemPreference = signal<NgSystemTheme>(this.resolveSystemPreference());\n\n // ── Theme state ───────────────────────────────────────────────────────────\n\n readonly #selectedTheme = signal<NgTheme>(this.resolveInitialTheme());\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#selectedTheme.asReadonly();\n\n /** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) — never `'system'`. */\n readonly userTheme = computed(() => {\n const theme = this.#selectedTheme();\n return theme === 'system' ? this.#systemPreference() : theme;\n });\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = computed(() => this.userTheme() === 'dark');\n\n /** Whether the currently applied theme is light. */\n readonly isLight = computed(() => this.userTheme() === 'light');\n\n // ── Event handler ─────────────────────────────────────────────────────────\n\n readonly #onSystemPreferenceChange = () =>\n this.#systemPreference.set(this.resolveSystemPreference());\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n\n constructor() {\n if (this.#isBrowser && this.#selectedTheme() === 'system') {\n this.startSystemThemeListener();\n }\n\n effect(() => this.applyThemeToDOM(this.userTheme()));\n\n this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Changes the active theme.\n *\n * Persists the choice explicitly so that switching e.g. from `'system'` to\n * `'light'` is saved even when the resolved `userTheme` did not change\n * (system preference was already `'light'`).\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n public setTheme(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n if (!this.#validThemes.has(theme)) {\n throw new Error(\n `[ngx-theme-stack] Invalid theme: \"${theme}\". Valid values are: ${[...this.#validThemes].join(', ')}.`,\n );\n }\n\n if (theme === 'system') {\n this.startSystemThemeListener();\n } else {\n this.stopSystemThemeListener();\n }\n\n this.#selectedTheme.set(theme);\n this.saveTheme(theme);\n }\n\n // ── Private ───────────────────────────────────────────────────────────────\n\n private resolveSystemPreference(): NgSystemTheme {\n return this.#mediaQuery?.matches ? 'dark' : 'light';\n }\n\n private resolveInitialTheme(): NgTheme {\n if (!this.#isBrowser) return this.#config.theme;\n const saved = this.readStoredTheme();\n return saved && this.#validThemes.has(saved) ? saved : this.#config.theme;\n }\n\n private startSystemThemeListener(): void {\n if (!this.#mediaQuery) return;\n this.stopSystemThemeListener();\n this.#mediaQuery.addEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private stopSystemThemeListener(): void {\n this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private applyThemeToDOM(userTheme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n const host = this.#document.documentElement;\n const { mode } = this.#config;\n\n if (mode === 'attribute' || mode === 'both') {\n this.#renderer.setAttribute(host, 'data-theme', userTheme);\n }\n\n if (mode === 'class' || mode === 'both') {\n for (const theme of this.availableThemes) {\n this.#renderer.removeClass(host, theme);\n }\n this.#renderer.addClass(host, userTheme);\n }\n\n if (userTheme === 'dark' || userTheme === 'light') {\n this.#renderer.setStyle(host, 'color-scheme', userTheme);\n return;\n }\n\n this.#renderer.removeStyle(host, 'color-scheme');\n }\n\n private readStoredTheme(): NgTheme | null {\n try {\n return localStorage.getItem(this.#config.storageKey) as NgTheme;\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);\n return null;\n }\n }\n\n private saveTheme(theme: NgTheme): void {\n try {\n localStorage.setItem(this.#config.storageKey, theme);\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not save theme to localStorage.', e);\n }\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from './core-theme.service';\n\n/**\n * Convenience service for cycling through themes in a fixed order.\n *\n * Default cycle: `'light'` → `'dark'` → `'system'` → `'light'` → ...\n *\n * Use this when you want to offer users a single button that rotates\n * through all available theme options.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeCycleService {\n\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes for cycling. Defaults to ['light', 'dark', 'system']. */\n readonly #cycle = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme applied to the DOM. Always concrete — never `'system'`. */\n readonly userTheme = this.#core.userTheme;\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is light. */\n readonly isLight = this.#core.isLight;\n\n /**\n * Advances to the next theme in the cycle.\n *\n * Cycle order is determined by the configured `themes` property in `NgConfig`.\n *\n * If the current theme is not found in the cycle (e.g. set externally),\n * the cycle restarts from the first theme.\n */\n cycle(): void {\n const current = this.#core.selectedTheme();\n const index = this.#cycle.indexOf(current);\n const next = this.#cycle[(index + 1) % this.#cycle.length];\n this.#core.setTheme(next);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from './core-theme.service';\nimport { NgTheme } from './types';\n\n/**\n * Convenience service for selecting a theme from a list.\n *\n * Use this when you want to bind a `<select>` or a group of radio/tab\n * buttons to the full set of available themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeSelectService {\n\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes. Defaults to ['light', 'dark', 'system']. */\n readonly availableThemes = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme applied to the DOM. Always concrete — never `'system'`. */\n readonly userTheme = this.#core.userTheme;\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is light. */\n readonly isLight = this.#core.isLight;\n\n /**\n * Applies the given theme.\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or custom.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n select(theme: NgTheme): void {\n this.#core.setTheme(theme);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from './core-theme.service';\n\n/**\n * Convenience service for toggling between `'dark'` and `'light'`.\n *\n * Use this when you only need a simple on/off switch and do not\n * need to manage `'system'` or cycle through themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeToggleService {\n\n readonly #core = inject(CoreThemeService);\n\n /** Resolved theme applied to the DOM. Always concrete — never `'system'`. */\n readonly userTheme = this.#core.userTheme;\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is light. */\n readonly isLight = this.#core.isLight;\n\n /**\n * Toggles between `'dark'` and `'light'`.\n *\n * If the selected theme is explicitly `'dark'`, switches to `'light'`.\n * Otherwise (including `'system'`), switches to `'dark'`.\n */\n toggle(): void {\n const next = this.#core.selectedTheme() === 'dark' ? 'light' : 'dark';\n this.#core.setTheme(next);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-theme-stack.mjs","sources":["../../../projects/ngx-theme-stack/src/lib/types.ts","../../../projects/ngx-theme-stack/src/lib/theme-stack.config.ts","../../../projects/ngx-theme-stack/src/lib/core-theme.service.ts","../../../projects/ngx-theme-stack/src/lib/theme-cycle.service.ts","../../../projects/ngx-theme-stack/src/lib/theme-select.service.ts","../../../projects/ngx-theme-stack/src/lib/theme-toggle.service.ts","../../../projects/ngx-theme-stack/src/ngx-theme-stack.ts"],"sourcesContent":["/** Built-in themes. All other values are considered custom themes. */\nexport const DEFAULT_THEMES = ['light', 'dark', 'system'] as const;\n\n/** String union with autocompletion for defaults + any string support for customization. */\nexport type NgTheme = (typeof DEFAULT_THEMES)[number] | (string & {});\n\nexport type NgSystemTheme = Exclude<NgTheme, 'system'>;\nexport type NgMode = 'attribute' | 'class' | 'both';\n\nexport interface NgConfig {\n theme: NgTheme;\n storageKey: string;\n mode: NgMode;\n themes: NgTheme[];\n}\n","import { InjectionToken } from '@angular/core';\nimport { DEFAULT_THEMES, NgConfig } from './types';\n\nexport const DEFAULT_NG_CONFIG = {\n theme: 'system',\n storageKey: 'ngx-theme-stack-theme',\n mode: 'class',\n themes: [...DEFAULT_THEMES],\n} satisfies NgConfig;\n\nexport const NGX_THEME_STACK_CONFIG = new InjectionToken<NgConfig>('NGX_THEME_STACK_CONFIG', {\n factory: () => DEFAULT_NG_CONFIG,\n});\n\n/**\n * Helper function to provide Theme Stack configuration.\n */\nexport function provideThemeStack(config: Partial<NgConfig> = {}) {\n return {\n provide: NGX_THEME_STACK_CONFIG,\n useValue: {\n ...DEFAULT_NG_CONFIG,\n ...config,\n } satisfies NgConfig,\n };\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n computed,\n DestroyRef,\n DOCUMENT,\n effect,\n inject,\n Injectable,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\nimport { NGX_THEME_STACK_CONFIG } from './theme-stack.config';\nimport { NgSystemTheme, NgTheme } from './types';\n\n/**\n * Core service for managing the application's color theme.\n *\n * Handles theme persistence, system preference detection, and DOM updates.\n * Supports built-in themes ('dark', 'light', 'system') and custom extensions.\n */\n@Injectable({ providedIn: 'root' })\nexport class CoreThemeService {\n // ── Dependencies ──────────────────────────────────────────────────────────\n\n readonly #config = inject(NGX_THEME_STACK_CONFIG);\n readonly #destroyRef = inject(DestroyRef);\n readonly #document = inject(DOCUMENT);\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n // ── Theme configuration ───────────────────────────────────────────────────\n\n /** List of available themes for Select/Cycle services. Defaults to ['light', 'dark', 'system']. */\n readonly availableThemes = this.#config.themes;\n\n /** Internal Set for O(1) existence checks. */\n readonly #validThemes = new Set<NgTheme>(this.availableThemes);\n\n // ── System preference ─────────────────────────────────────────────────────\n\n /** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */\n readonly #mediaQuery: MediaQueryList | null = this.#isBrowser\n ? (this.#document.defaultView?.matchMedia('(prefers-color-scheme: dark)') ?? null)\n : null;\n\n readonly #systemPreference = signal<NgSystemTheme>(this.resolveSystemPreference());\n\n // ── Theme state ───────────────────────────────────────────────────────────\n\n readonly #selectedTheme = signal<NgTheme>(this.resolveInitialTheme());\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#selectedTheme.asReadonly();\n\n /** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) — never `'system'`. */\n readonly userTheme = computed(() => {\n const theme = this.#selectedTheme();\n return theme === 'system' ? this.#systemPreference() : theme;\n });\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = computed(() => this.userTheme() === 'dark');\n\n /** Whether the currently applied theme is light. */\n readonly isLight = computed(() => this.userTheme() === 'light');\n\n // ── Event handler ─────────────────────────────────────────────────────────\n\n readonly #onSystemPreferenceChange = () =>\n this.#systemPreference.set(this.resolveSystemPreference());\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n\n constructor() {\n if (this.#isBrowser && this.#selectedTheme() === 'system') {\n this.startSystemThemeListener();\n }\n\n effect(() => this.applyThemeToDOM(this.userTheme()));\n\n this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Changes the active theme.\n *\n * Persists the choice explicitly so that switching e.g. from `'system'` to\n * `'light'` is saved even when the resolved `userTheme` did not change\n * (system preference was already `'light'`).\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n public setTheme(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n if (!this.#validThemes.has(theme)) {\n throw new Error(\n `[ngx-theme-stack] Invalid theme: \"${theme}\". Valid values are: ${[...this.#validThemes].join(', ')}.`,\n );\n }\n\n if (theme === 'system') {\n this.#systemPreference.set(this.resolveSystemPreference());\n this.startSystemThemeListener();\n } else {\n this.stopSystemThemeListener();\n }\n\n this.#selectedTheme.set(theme);\n this.saveTheme(theme);\n }\n\n // ── Private ───────────────────────────────────────────────────────────────\n\n private resolveSystemPreference(): NgSystemTheme {\n return this.#mediaQuery?.matches ? 'dark' : 'light';\n }\n\n private resolveInitialTheme(): NgTheme {\n if (!this.#isBrowser) return this.#config.theme;\n return this.readStoredTheme() ?? this.#config.theme;\n }\n\n private startSystemThemeListener(): void {\n if (!this.#mediaQuery) return;\n this.stopSystemThemeListener();\n this.#mediaQuery.addEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private stopSystemThemeListener(): void {\n this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private applyThemeToDOM(userTheme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n const host = this.#document.documentElement;\n const { mode } = this.#config;\n\n if (mode === 'attribute' || mode === 'both') {\n this.applyThemeAttribute(host, userTheme);\n }\n\n if (mode === 'class' || mode === 'both') {\n this.applyThemeClasses(host, userTheme);\n }\n\n this.applyColorSchemeHint(host, userTheme);\n }\n\n private applyThemeAttribute(host: HTMLElement, theme: NgTheme): void {\n host.setAttribute('data-theme', theme);\n }\n\n private applyThemeClasses(host: HTMLElement, theme: NgTheme): void {\n for (const t of this.availableThemes) {\n host.classList.remove(t);\n }\n\n host.classList.add(theme);\n }\n\n private applyColorSchemeHint(host: HTMLElement, theme: NgTheme): void {\n if (theme === 'dark' || theme === 'light') {\n host.style.setProperty('color-scheme', theme);\n return;\n }\n\n host.style.removeProperty('color-scheme');\n }\n\n private readStoredTheme(): NgTheme | null {\n try {\n const stored = localStorage.getItem(this.#config.storageKey);\n if (stored && this.#validThemes.has(stored as NgTheme)) {\n return stored as NgTheme;\n }\n return null;\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);\n return null;\n }\n }\n\n private saveTheme(theme: NgTheme): void {\n try {\n localStorage.setItem(this.#config.storageKey, theme);\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not save theme to localStorage.', e);\n }\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from './core-theme.service';\n\n/**\n * Convenience service for cycling through themes in a fixed order.\n *\n * Default cycle: `'light'` → `'dark'` → `'system'` → `'light'` → ...\n *\n * Use this when you want to offer users a single button that rotates\n * through all available theme options.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeCycleService {\n\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes for cycling. Defaults to ['light', 'dark', 'system']. */\n readonly #cycle = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme applied to the DOM. Always concrete — never `'system'`. */\n readonly userTheme = this.#core.userTheme;\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is light. */\n readonly isLight = this.#core.isLight;\n\n /**\n * Advances to the next theme in the cycle.\n *\n * Cycle order is determined by the configured `themes` property in `NgConfig`.\n *\n * If the current theme is not found in the cycle (e.g. set externally),\n * the cycle restarts from the first theme.\n */\n cycle(): void {\n const current = this.#core.selectedTheme();\n const index = this.#cycle.indexOf(current);\n const next = this.#cycle[(index + 1) % this.#cycle.length];\n this.#core.setTheme(next);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from './core-theme.service';\nimport { NgTheme } from './types';\n\n/**\n * Convenience service for selecting a theme from a list.\n *\n * Use this when you want to bind a `<select>` or a group of radio/tab\n * buttons to the full set of available themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeSelectService {\n\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes. Defaults to ['light', 'dark', 'system']. */\n readonly availableThemes = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme applied to the DOM. Always concrete — never `'system'`. */\n readonly userTheme = this.#core.userTheme;\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is light. */\n readonly isLight = this.#core.isLight;\n\n /**\n * Applies the given theme.\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or custom.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n select(theme: NgTheme): void {\n this.#core.setTheme(theme);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from './core-theme.service';\n\n/**\n * Convenience service for toggling between `'dark'` and `'light'`.\n *\n * Use this when you only need a simple on/off switch and do not\n * need to manage `'system'` or cycle through themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeToggleService {\n\n readonly #core = inject(CoreThemeService);\n\n /** Resolved theme applied to the DOM. Always concrete — never `'system'`. */\n readonly userTheme = this.#core.userTheme;\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is light. */\n readonly isLight = this.#core.isLight;\n\n /**\n * Toggles between `'dark'` and `'light'`.\n *\n * If the selected theme is explicitly `'dark'`, switches to `'light'`.\n * Otherwise (including `'system'`), switches to `'dark'`.\n */\n toggle(): void {\n const next = this.#core.selectedTheme() === 'dark' ? 'light' : 'dark';\n this.#core.setTheme(next);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-theme-stack",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/common": "^21.2.0",
|
|
6
6
|
"@angular/core": "^21.2.0"
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"sideEffects": false,
|
|
13
13
|
"schematics": "./schematics/collection.json",
|
|
14
|
+
"ng-add": {
|
|
15
|
+
"save": "dependencies"
|
|
16
|
+
},
|
|
14
17
|
"module": "fesm2022/ngx-theme-stack.mjs",
|
|
15
18
|
"typings": "types/ngx-theme-stack.d.ts",
|
|
16
19
|
"exports": {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../../node_modules/@angular-devkit/schematics/collection-schema.json",
|
|
3
|
+
"schematics": {
|
|
4
|
+
"ng-add": {
|
|
5
|
+
"factory": "./ng-add/index#ngAdd",
|
|
6
|
+
"schema": "./ng-add/schema.json",
|
|
7
|
+
"description": "Configure ngx-theme-stack in the Angular project"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Rule } from '@angular-devkit/schematics';
|
|
2
|
+
interface SchemaOptions {
|
|
3
|
+
setupMode: string;
|
|
4
|
+
storageKey: string;
|
|
5
|
+
defaultTheme: string;
|
|
6
|
+
mode: string;
|
|
7
|
+
themes: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Main schematic rule for ng-add.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ngAdd(options: SchemaOptions): Rule;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ngAdd = ngAdd;
|
|
4
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
5
|
+
const workspace_1 = require("@schematics/angular/utility/workspace");
|
|
6
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
7
|
+
/**
|
|
8
|
+
* Returns the minified script to be injected into index.html.
|
|
9
|
+
* Inspired by next-themes to prevent flickering.
|
|
10
|
+
*/
|
|
11
|
+
function getBlockingScript(options) {
|
|
12
|
+
const allThemes = Array.from(new Set(['light', 'dark', 'system', ...(options.themes || [])]));
|
|
13
|
+
const themesJson = JSON.stringify(allThemes);
|
|
14
|
+
const script = `
|
|
15
|
+
(function() {
|
|
16
|
+
try {
|
|
17
|
+
var k = '${options.storageKey}';
|
|
18
|
+
var d = '${options.defaultTheme}';
|
|
19
|
+
var m = '${options.mode}';
|
|
20
|
+
var ths = ${themesJson};
|
|
21
|
+
var s = localStorage.getItem(k);
|
|
22
|
+
var t = s || d;
|
|
23
|
+
if (t === 'system') {
|
|
24
|
+
t = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
25
|
+
}
|
|
26
|
+
var el = document.documentElement;
|
|
27
|
+
if (m === 'class' || m === 'both') {
|
|
28
|
+
for (var i = 0; i < ths.length; i++) {
|
|
29
|
+
el.classList.remove(ths[i]);
|
|
30
|
+
}
|
|
31
|
+
el.classList.add(t);
|
|
32
|
+
}
|
|
33
|
+
if (m === 'attribute' || m === 'both') {
|
|
34
|
+
el.setAttribute('data-theme', t);
|
|
35
|
+
}
|
|
36
|
+
if (t === 'dark' || t === 'light') {
|
|
37
|
+
el.style.setProperty('color-scheme', t);
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {}
|
|
40
|
+
})();
|
|
41
|
+
`;
|
|
42
|
+
return script.replace(/\n\s*/g, '').trim();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Injects the theme detection script into the index.html file of the project.
|
|
46
|
+
*/
|
|
47
|
+
function injectScript(options) {
|
|
48
|
+
return async (tree) => {
|
|
49
|
+
const workspace = await (0, workspace_1.getWorkspace)(tree);
|
|
50
|
+
const projects = workspace.projects;
|
|
51
|
+
projects.forEach((project) => {
|
|
52
|
+
if (project.extensions['projectType'] !== 'application') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const buildTarget = project.targets.get('build');
|
|
56
|
+
const options_ = buildTarget?.options;
|
|
57
|
+
if (!options_) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const indexHtmlPath = options_['index'];
|
|
61
|
+
if (!indexHtmlPath || !tree.exists(indexHtmlPath)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const content = tree.readText(indexHtmlPath);
|
|
65
|
+
const scriptId = 'ngx-theme-stack-initial';
|
|
66
|
+
if (content.includes(scriptId)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const scriptTag = `<script id="${scriptId}">${getBlockingScript(options)}</script>`;
|
|
70
|
+
const headEndIndex = content.indexOf('</head>');
|
|
71
|
+
if (headEndIndex === -1) {
|
|
72
|
+
throw new schematics_1.SchematicsException(`Could not find </head> tag in ${indexHtmlPath}`);
|
|
73
|
+
}
|
|
74
|
+
const updatedContent = content.slice(0, headEndIndex) + scriptTag + content.slice(headEndIndex);
|
|
75
|
+
tree.overwrite(indexHtmlPath, updatedContent);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Main schematic rule for ng-add.
|
|
81
|
+
*/
|
|
82
|
+
function ngAdd(options) {
|
|
83
|
+
return async () => {
|
|
84
|
+
await askQuestions(options);
|
|
85
|
+
return (0, schematics_1.chain)([injectScript(options)]);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Asks interactive questions only in "custom" mode.
|
|
90
|
+
* In "quick" mode, schema.json defaults are used as-is.
|
|
91
|
+
*
|
|
92
|
+
* Note: `@inquirer/prompts` is a transitive dependency of `@angular/cli`,
|
|
93
|
+
* so it is available in any project using `ng add`.
|
|
94
|
+
*/
|
|
95
|
+
async function askQuestions(options) {
|
|
96
|
+
if (options.setupMode === 'custom') {
|
|
97
|
+
options.storageKey = await (0, prompts_1.input)({
|
|
98
|
+
message: 'Which key would you like to use for localStorage?',
|
|
99
|
+
default: options.storageKey || 'ngx-theme-stack-theme',
|
|
100
|
+
});
|
|
101
|
+
options.mode = await (0, prompts_1.select)({
|
|
102
|
+
message: 'How should the theme be applied to the DOM?',
|
|
103
|
+
choices: [
|
|
104
|
+
{ value: 'class', name: 'CSS Class (.dark/.light)' },
|
|
105
|
+
{ value: 'attribute', name: 'Data Attribute (data-theme)' },
|
|
106
|
+
{ value: 'both', name: 'Both' },
|
|
107
|
+
],
|
|
108
|
+
default: options.mode || 'class',
|
|
109
|
+
});
|
|
110
|
+
const customThemes = await (0, prompts_1.input)({
|
|
111
|
+
message: 'Add custom themes? (comma-separated, e.g. "sepia, ocean") — press Enter to skip:',
|
|
112
|
+
default: '',
|
|
113
|
+
});
|
|
114
|
+
// Parse and merge custom themes with defaults
|
|
115
|
+
const parsed = customThemes
|
|
116
|
+
.split(',')
|
|
117
|
+
.map((t) => t.trim())
|
|
118
|
+
.filter((t) => t.length > 0);
|
|
119
|
+
options.themes = Array.from(new Set(['light', 'dark', 'system', ...parsed]));
|
|
120
|
+
// Build choices for defaultTheme including any custom themes
|
|
121
|
+
const defaultChoices = [
|
|
122
|
+
{ value: 'system', name: 'System (OS Preference)' },
|
|
123
|
+
{ value: 'dark', name: 'Dark' },
|
|
124
|
+
{ value: 'light', name: 'Light' },
|
|
125
|
+
...parsed.map((t) => ({ value: t, name: t.charAt(0).toUpperCase() + t.slice(1) })),
|
|
126
|
+
];
|
|
127
|
+
options.defaultTheme = await (0, prompts_1.select)({
|
|
128
|
+
message: 'Which should be the default theme?',
|
|
129
|
+
choices: defaultChoices,
|
|
130
|
+
default: options.defaultTheme || 'system',
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Quick mode: merge themes with defaults
|
|
135
|
+
options.themes = Array.from(new Set(['light', 'dark', 'system', ...(options.themes || [])]));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "NgxThemeStackNgAdd",
|
|
4
|
+
"title": "ngx-theme-stack Add Options",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"setupMode": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"default": "quick",
|
|
10
|
+
"x-prompt": {
|
|
11
|
+
"message": "Choose setup mode:",
|
|
12
|
+
"type": "list",
|
|
13
|
+
"items": [
|
|
14
|
+
{ "value": "quick", "label": "Quick (Default settings - Class mode, 'ngx-theme-stack-theme' key)" },
|
|
15
|
+
{ "value": "custom", "label": "Custom (Configure storage, themes, mode)" }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"storageKey": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "LocalStorage key to persist the theme",
|
|
22
|
+
"default": "ngx-theme-stack-theme"
|
|
23
|
+
},
|
|
24
|
+
"defaultTheme": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Default theme to be applied",
|
|
27
|
+
"default": "system"
|
|
28
|
+
},
|
|
29
|
+
"themes": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"description": "All supported themes (including custom ones)",
|
|
32
|
+
"items": { "type": "string" },
|
|
33
|
+
"default": ["dark", "light", "system"]
|
|
34
|
+
},
|
|
35
|
+
"mode": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "How the theme should be applied to the DOM",
|
|
38
|
+
"enum": ["class", "attribute", "both"],
|
|
39
|
+
"default": "class"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -50,6 +50,9 @@ declare class CoreThemeService {
|
|
|
50
50
|
private startSystemThemeListener;
|
|
51
51
|
private stopSystemThemeListener;
|
|
52
52
|
private applyThemeToDOM;
|
|
53
|
+
private applyThemeAttribute;
|
|
54
|
+
private applyThemeClasses;
|
|
55
|
+
private applyColorSchemeHint;
|
|
53
56
|
private readStoredTheme;
|
|
54
57
|
private saveTheme;
|
|
55
58
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CoreThemeService, never>;
|