ngx-theme-stack 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # ngx-theme-stack
2
+
3
+ A robust, SSR-safe, and signal-based theme management library for Angular applications.
4
+
5
+ ## Features
6
+
7
+ - πŸŒ“ **SSR Safe**: Works perfectly with Angular Universal/SSR.
8
+ - 🚦 **Signal-based**: Built with modern Angular Signals for reactive theme management.
9
+ - πŸ› οΈ **Schematics Support**: Easy installation and configuration via `ng add`.
10
+ - 🧬 **Flexible**: Support for custom themes and system theme detection.
11
+
12
+ ## Quick Start
13
+
14
+ ### Installation
15
+
16
+ ```bash
17
+ ng add ngx-theme-stack
18
+ ```
19
+
20
+ ### Usage
21
+
22
+ Inject the `ThemeService` into your component or use the provided directives.
23
+
24
+ ```typescript
25
+ import { Component, inject } from '@angular/core';
26
+ import { ThemeService } from 'ngx-theme-stack';
27
+
28
+ @Component({
29
+ selector: 'app-root',
30
+ template: `
31
+ <button (click)="theme.toggleTheme()">Toggle Theme</button>
32
+ <p>Current theme: {{ theme.currentTheme() }}</p>
33
+ `,
34
+ imports: []
35
+ })
36
+ export class AppComponent {
37
+ theme = inject(ThemeService);
38
+ }
39
+ ```
40
+
41
+ ## Developing
42
+
43
+ ### Building
44
+
45
+ To build the library, run:
46
+
47
+ ```bash
48
+ ng build ngx-theme-stack
49
+ ```
50
+
51
+ The build artifacts will be placed in the `dist/ngx-theme-stack` directory.
52
+
53
+ ### Running unit tests
54
+
55
+ ```bash
56
+ ng test ngx-theme-stack
57
+ ```
58
+
59
+ ## License
60
+
61
+ MIT
@@ -0,0 +1,281 @@
1
+ import { isPlatformBrowser } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, inject, DestroyRef, DOCUMENT, RendererFactory2, PLATFORM_ID, signal, computed, effect, Injectable } from '@angular/core';
4
+
5
+ /** Built-in themes. All other values are considered custom themes. */
6
+ const DEFAULT_THEMES = ['light', 'dark', 'system'];
7
+
8
+ const DEFAULT_NG_CONFIG = {
9
+ theme: 'system',
10
+ storageKey: 'ngx-theme-stack-theme',
11
+ mode: 'class',
12
+ themes: [...DEFAULT_THEMES],
13
+ };
14
+ const NGX_THEME_STACK_CONFIG = new InjectionToken('NGX_THEME_STACK_CONFIG', {
15
+ factory: () => DEFAULT_NG_CONFIG,
16
+ });
17
+ /**
18
+ * Helper function to provide Theme Stack configuration.
19
+ */
20
+ function provideThemeStack(config = {}) {
21
+ return {
22
+ provide: NGX_THEME_STACK_CONFIG,
23
+ useValue: {
24
+ ...DEFAULT_NG_CONFIG,
25
+ ...config,
26
+ },
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Core service for managing the application's color theme.
32
+ *
33
+ * Handles theme persistence, system preference detection, and DOM updates.
34
+ * Supports built-in themes ('dark', 'light', 'system') and custom extensions.
35
+ */
36
+ class CoreThemeService {
37
+ // ── Dependencies ──────────────────────────────────────────────────────────
38
+ #config = inject(NGX_THEME_STACK_CONFIG);
39
+ #destroyRef = inject(DestroyRef);
40
+ #document = inject(DOCUMENT);
41
+ #renderer = inject(RendererFactory2).createRenderer(null, null);
42
+ #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
43
+ // ── Theme configuration ───────────────────────────────────────────────────
44
+ /** List of available themes for Select/Cycle services. Defaults to ['light', 'dark', 'system']. */
45
+ availableThemes = this.#config.themes;
46
+ /** Internal Set for O(1) existence checks. */
47
+ #validThemes = new Set(this.availableThemes);
48
+ // ── System preference ─────────────────────────────────────────────────────
49
+ /** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */
50
+ #mediaQuery = this.#isBrowser
51
+ ? (this.#document.defaultView?.matchMedia('(prefers-color-scheme: dark)') ?? null)
52
+ : null;
53
+ #systemPreference = signal(this.resolveSystemPreference(), ...(ngDevMode ? [{ debugName: "#systemPreference" }] : /* istanbul ignore next */ []));
54
+ // ── Theme state ───────────────────────────────────────────────────────────
55
+ #selectedTheme = signal(this.resolveInitialTheme(), ...(ngDevMode ? [{ debugName: "#selectedTheme" }] : /* istanbul ignore next */ []));
56
+ /** The theme explicitly selected by the user. May be `'system'`. */
57
+ selectedTheme = this.#selectedTheme.asReadonly();
58
+ /** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) β€” never `'system'`. */
59
+ userTheme = computed(() => {
60
+ const theme = this.#selectedTheme();
61
+ return theme === 'system' ? this.#systemPreference() : theme;
62
+ }, ...(ngDevMode ? [{ debugName: "userTheme" }] : /* istanbul ignore next */ []));
63
+ /** Whether the currently applied theme is dark. */
64
+ isDark = computed(() => this.userTheme() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
65
+ /** Whether the currently applied theme is light. */
66
+ isLight = computed(() => this.userTheme() === 'light', ...(ngDevMode ? [{ debugName: "isLight" }] : /* istanbul ignore next */ []));
67
+ // ── Event handler ─────────────────────────────────────────────────────────
68
+ #onSystemPreferenceChange = () => this.#systemPreference.set(this.resolveSystemPreference());
69
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
70
+ constructor() {
71
+ if (this.#isBrowser && this.#selectedTheme() === 'system') {
72
+ this.startSystemThemeListener();
73
+ }
74
+ effect(() => this.applyThemeToDOM(this.userTheme()));
75
+ this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());
76
+ }
77
+ // ── Public API ────────────────────────────────────────────────────────────
78
+ /**
79
+ * Changes the active theme.
80
+ *
81
+ * Persists the choice explicitly so that switching e.g. from `'system'` to
82
+ * `'light'` is saved even when the resolved `userTheme` did not change
83
+ * (system preference was already `'light'`).
84
+ *
85
+ * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.
86
+ * @throws If `theme` is not a valid theme according to library configuration.
87
+ */
88
+ setTheme(theme) {
89
+ if (!this.#isBrowser)
90
+ return;
91
+ if (!this.#validThemes.has(theme)) {
92
+ throw new Error(`[ngx-theme-stack] Invalid theme: "${theme}". Valid values are: ${[...this.#validThemes].join(', ')}.`);
93
+ }
94
+ if (theme === 'system') {
95
+ this.startSystemThemeListener();
96
+ }
97
+ else {
98
+ this.stopSystemThemeListener();
99
+ }
100
+ this.#selectedTheme.set(theme);
101
+ this.saveTheme(theme);
102
+ }
103
+ // ── Private ───────────────────────────────────────────────────────────────
104
+ resolveSystemPreference() {
105
+ return this.#mediaQuery?.matches ? 'dark' : 'light';
106
+ }
107
+ resolveInitialTheme() {
108
+ if (!this.#isBrowser)
109
+ return this.#config.theme;
110
+ const saved = this.readStoredTheme();
111
+ return saved && this.#validThemes.has(saved) ? saved : this.#config.theme;
112
+ }
113
+ startSystemThemeListener() {
114
+ if (!this.#mediaQuery)
115
+ return;
116
+ this.stopSystemThemeListener();
117
+ this.#mediaQuery.addEventListener('change', this.#onSystemPreferenceChange);
118
+ }
119
+ stopSystemThemeListener() {
120
+ this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);
121
+ }
122
+ applyThemeToDOM(userTheme) {
123
+ if (!this.#isBrowser)
124
+ return;
125
+ const host = this.#document.documentElement;
126
+ const { mode } = this.#config;
127
+ if (mode === 'attribute' || mode === 'both') {
128
+ this.#renderer.setAttribute(host, 'data-theme', userTheme);
129
+ }
130
+ if (mode === 'class' || mode === 'both') {
131
+ for (const theme of this.availableThemes) {
132
+ this.#renderer.removeClass(host, theme);
133
+ }
134
+ this.#renderer.addClass(host, userTheme);
135
+ }
136
+ if (userTheme === 'dark' || userTheme === 'light') {
137
+ this.#renderer.setStyle(host, 'color-scheme', userTheme);
138
+ return;
139
+ }
140
+ this.#renderer.removeStyle(host, 'color-scheme');
141
+ }
142
+ readStoredTheme() {
143
+ try {
144
+ return localStorage.getItem(this.#config.storageKey);
145
+ }
146
+ catch (e) {
147
+ console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);
148
+ return null;
149
+ }
150
+ }
151
+ saveTheme(theme) {
152
+ try {
153
+ localStorage.setItem(this.#config.storageKey, theme);
154
+ }
155
+ catch (e) {
156
+ console.warn('[ngx-theme-stack] Could not save theme to localStorage.', e);
157
+ }
158
+ }
159
+ static Ι΅fac = i0.Ι΅Ι΅ngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: CoreThemeService, deps: [], target: i0.Ι΅Ι΅FactoryTarget.Injectable });
160
+ static Ι΅prov = i0.Ι΅Ι΅ngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: CoreThemeService, providedIn: 'root' });
161
+ }
162
+ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: CoreThemeService, decorators: [{
163
+ type: Injectable,
164
+ args: [{ providedIn: 'root' }]
165
+ }], ctorParameters: () => [] });
166
+
167
+ /**
168
+ * Convenience service for cycling through themes in a fixed order.
169
+ *
170
+ * Default cycle: `'light'` β†’ `'dark'` β†’ `'system'` β†’ `'light'` β†’ ...
171
+ *
172
+ * Use this when you want to offer users a single button that rotates
173
+ * through all available theme options.
174
+ */
175
+ class ThemeCycleService {
176
+ #core = inject(CoreThemeService);
177
+ /** List of all configured themes for cycling. Defaults to ['light', 'dark', 'system']. */
178
+ #cycle = this.#core.availableThemes;
179
+ /** The theme explicitly selected by the user. May be `'system'`. */
180
+ selectedTheme = this.#core.selectedTheme;
181
+ /** Resolved theme applied to the DOM. Always concrete β€” never `'system'`. */
182
+ userTheme = this.#core.userTheme;
183
+ /** Whether the currently applied theme is dark. */
184
+ isDark = this.#core.isDark;
185
+ /** Whether the currently applied theme is light. */
186
+ isLight = this.#core.isLight;
187
+ /**
188
+ * Advances to the next theme in the cycle.
189
+ *
190
+ * Cycle order is determined by the configured `themes` property in `NgConfig`.
191
+ *
192
+ * If the current theme is not found in the cycle (e.g. set externally),
193
+ * the cycle restarts from the first theme.
194
+ */
195
+ cycle() {
196
+ const current = this.#core.selectedTheme();
197
+ const index = this.#cycle.indexOf(current);
198
+ const next = this.#cycle[(index + 1) % this.#cycle.length];
199
+ this.#core.setTheme(next);
200
+ }
201
+ static Ι΅fac = i0.Ι΅Ι΅ngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeCycleService, deps: [], target: i0.Ι΅Ι΅FactoryTarget.Injectable });
202
+ static Ι΅prov = i0.Ι΅Ι΅ngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeCycleService, providedIn: 'root' });
203
+ }
204
+ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeCycleService, decorators: [{
205
+ type: Injectable,
206
+ args: [{ providedIn: 'root' }]
207
+ }] });
208
+
209
+ /**
210
+ * Convenience service for selecting a theme from a list.
211
+ *
212
+ * Use this when you want to bind a `<select>` or a group of radio/tab
213
+ * buttons to the full set of available themes.
214
+ */
215
+ class ThemeSelectService {
216
+ #core = inject(CoreThemeService);
217
+ /** List of all configured themes. Defaults to ['light', 'dark', 'system']. */
218
+ availableThemes = this.#core.availableThemes;
219
+ /** The theme explicitly selected by the user. May be `'system'`. */
220
+ selectedTheme = this.#core.selectedTheme;
221
+ /** Resolved theme applied to the DOM. Always concrete β€” never `'system'`. */
222
+ userTheme = this.#core.userTheme;
223
+ /** Whether the currently applied theme is dark. */
224
+ isDark = this.#core.isDark;
225
+ /** Whether the currently applied theme is light. */
226
+ isLight = this.#core.isLight;
227
+ /**
228
+ * Applies the given theme.
229
+ *
230
+ * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or custom.
231
+ * @throws If `theme` is not a valid theme according to library configuration.
232
+ */
233
+ select(theme) {
234
+ this.#core.setTheme(theme);
235
+ }
236
+ static Ι΅fac = i0.Ι΅Ι΅ngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeSelectService, deps: [], target: i0.Ι΅Ι΅FactoryTarget.Injectable });
237
+ static Ι΅prov = i0.Ι΅Ι΅ngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeSelectService, providedIn: 'root' });
238
+ }
239
+ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeSelectService, decorators: [{
240
+ type: Injectable,
241
+ args: [{ providedIn: 'root' }]
242
+ }] });
243
+
244
+ /**
245
+ * Convenience service for toggling between `'dark'` and `'light'`.
246
+ *
247
+ * Use this when you only need a simple on/off switch and do not
248
+ * need to manage `'system'` or cycle through themes.
249
+ */
250
+ class ThemeToggleService {
251
+ #core = inject(CoreThemeService);
252
+ /** Resolved theme applied to the DOM. Always concrete β€” never `'system'`. */
253
+ userTheme = this.#core.userTheme;
254
+ /** Whether the currently applied theme is dark. */
255
+ isDark = this.#core.isDark;
256
+ /** Whether the currently applied theme is light. */
257
+ isLight = this.#core.isLight;
258
+ /**
259
+ * Toggles between `'dark'` and `'light'`.
260
+ *
261
+ * If the selected theme is explicitly `'dark'`, switches to `'light'`.
262
+ * Otherwise (including `'system'`), switches to `'dark'`.
263
+ */
264
+ toggle() {
265
+ const next = this.#core.selectedTheme() === 'dark' ? 'light' : 'dark';
266
+ this.#core.setTheme(next);
267
+ }
268
+ static Ι΅fac = i0.Ι΅Ι΅ngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeToggleService, deps: [], target: i0.Ι΅Ι΅FactoryTarget.Injectable });
269
+ static Ι΅prov = i0.Ι΅Ι΅ngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeToggleService, providedIn: 'root' });
270
+ }
271
+ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: ThemeToggleService, decorators: [{
272
+ type: Injectable,
273
+ args: [{ providedIn: 'root' }]
274
+ }] });
275
+
276
+ /**
277
+ * Generated bundle index. Do not edit.
278
+ */
279
+
280
+ export { CoreThemeService, DEFAULT_NG_CONFIG, DEFAULT_THEMES, NGX_THEME_STACK_CONFIG, ThemeCycleService, ThemeSelectService, ThemeToggleService, provideThemeStack };
281
+ //# sourceMappingURL=ngx-theme-stack.mjs.map
@@ -0,0 +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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"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "ngx-theme-stack",
3
+ "version": "0.0.1",
4
+ "peerDependencies": {
5
+ "@angular/common": "^21.2.0",
6
+ "@angular/core": "^21.2.0"
7
+ },
8
+ "dependencies": {
9
+ "tslib": "^2.3.0"
10
+ },
11
+ "license": "MIT",
12
+ "sideEffects": false,
13
+ "schematics": "./schematics/collection.json",
14
+ "module": "fesm2022/ngx-theme-stack.mjs",
15
+ "typings": "types/ngx-theme-stack.d.ts",
16
+ "exports": {
17
+ "./package.json": {
18
+ "default": "./package.json"
19
+ },
20
+ ".": {
21
+ "types": "./types/ngx-theme-stack.d.ts",
22
+ "default": "./fesm2022/ngx-theme-stack.mjs"
23
+ }
24
+ },
25
+ "type": "module"
26
+ }
@@ -0,0 +1,165 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken } from '@angular/core';
3
+ import * as ngx_theme_stack from 'ngx-theme-stack';
4
+
5
+ /** Built-in themes. All other values are considered custom themes. */
6
+ declare const DEFAULT_THEMES: readonly ["light", "dark", "system"];
7
+ /** String union with autocompletion for defaults + any string support for customization. */
8
+ type NgTheme = (typeof DEFAULT_THEMES)[number] | (string & {});
9
+ type NgSystemTheme = Exclude<NgTheme, 'system'>;
10
+ type NgMode = 'attribute' | 'class' | 'both';
11
+ interface NgConfig {
12
+ theme: NgTheme;
13
+ storageKey: string;
14
+ mode: NgMode;
15
+ themes: NgTheme[];
16
+ }
17
+
18
+ /**
19
+ * Core service for managing the application's color theme.
20
+ *
21
+ * Handles theme persistence, system preference detection, and DOM updates.
22
+ * Supports built-in themes ('dark', 'light', 'system') and custom extensions.
23
+ */
24
+ declare class CoreThemeService {
25
+ #private;
26
+ /** List of available themes for Select/Cycle services. Defaults to ['light', 'dark', 'system']. */
27
+ readonly availableThemes: NgTheme[];
28
+ /** The theme explicitly selected by the user. May be `'system'`. */
29
+ readonly selectedTheme: _angular_core.Signal<NgTheme>;
30
+ /** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) β€” never `'system'`. */
31
+ readonly userTheme: _angular_core.Signal<NgSystemTheme>;
32
+ /** Whether the currently applied theme is dark. */
33
+ readonly isDark: _angular_core.Signal<boolean>;
34
+ /** Whether the currently applied theme is light. */
35
+ readonly isLight: _angular_core.Signal<boolean>;
36
+ constructor();
37
+ /**
38
+ * Changes the active theme.
39
+ *
40
+ * Persists the choice explicitly so that switching e.g. from `'system'` to
41
+ * `'light'` is saved even when the resolved `userTheme` did not change
42
+ * (system preference was already `'light'`).
43
+ *
44
+ * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.
45
+ * @throws If `theme` is not a valid theme according to library configuration.
46
+ */
47
+ setTheme(theme: NgTheme): void;
48
+ private resolveSystemPreference;
49
+ private resolveInitialTheme;
50
+ private startSystemThemeListener;
51
+ private stopSystemThemeListener;
52
+ private applyThemeToDOM;
53
+ private readStoredTheme;
54
+ private saveTheme;
55
+ static Ι΅fac: _angular_core.Ι΅Ι΅FactoryDeclaration<CoreThemeService, never>;
56
+ static Ι΅prov: _angular_core.Ι΅Ι΅InjectableDeclaration<CoreThemeService>;
57
+ }
58
+
59
+ declare const DEFAULT_NG_CONFIG: {
60
+ theme: "system";
61
+ storageKey: string;
62
+ mode: "class";
63
+ themes: ("light" | "dark" | "system")[];
64
+ };
65
+ declare const NGX_THEME_STACK_CONFIG: InjectionToken<NgConfig>;
66
+ /**
67
+ * Helper function to provide Theme Stack configuration.
68
+ */
69
+ declare function provideThemeStack(config?: Partial<NgConfig>): {
70
+ provide: InjectionToken<NgConfig>;
71
+ useValue: {
72
+ theme: ngx_theme_stack.NgTheme;
73
+ storageKey: string;
74
+ mode: ngx_theme_stack.NgMode;
75
+ themes: ngx_theme_stack.NgTheme[];
76
+ };
77
+ };
78
+
79
+ /**
80
+ * Convenience service for cycling through themes in a fixed order.
81
+ *
82
+ * Default cycle: `'light'` β†’ `'dark'` β†’ `'system'` β†’ `'light'` β†’ ...
83
+ *
84
+ * Use this when you want to offer users a single button that rotates
85
+ * through all available theme options.
86
+ */
87
+ declare class ThemeCycleService {
88
+ #private;
89
+ /** The theme explicitly selected by the user. May be `'system'`. */
90
+ readonly selectedTheme: _angular_core.Signal<ngx_theme_stack.NgTheme>;
91
+ /** Resolved theme applied to the DOM. Always concrete β€” never `'system'`. */
92
+ readonly userTheme: _angular_core.Signal<ngx_theme_stack.NgSystemTheme>;
93
+ /** Whether the currently applied theme is dark. */
94
+ readonly isDark: _angular_core.Signal<boolean>;
95
+ /** Whether the currently applied theme is light. */
96
+ readonly isLight: _angular_core.Signal<boolean>;
97
+ /**
98
+ * Advances to the next theme in the cycle.
99
+ *
100
+ * Cycle order is determined by the configured `themes` property in `NgConfig`.
101
+ *
102
+ * If the current theme is not found in the cycle (e.g. set externally),
103
+ * the cycle restarts from the first theme.
104
+ */
105
+ cycle(): void;
106
+ static Ι΅fac: _angular_core.Ι΅Ι΅FactoryDeclaration<ThemeCycleService, never>;
107
+ static Ι΅prov: _angular_core.Ι΅Ι΅InjectableDeclaration<ThemeCycleService>;
108
+ }
109
+
110
+ /**
111
+ * Convenience service for selecting a theme from a list.
112
+ *
113
+ * Use this when you want to bind a `<select>` or a group of radio/tab
114
+ * buttons to the full set of available themes.
115
+ */
116
+ declare class ThemeSelectService {
117
+ #private;
118
+ /** List of all configured themes. Defaults to ['light', 'dark', 'system']. */
119
+ readonly availableThemes: NgTheme[];
120
+ /** The theme explicitly selected by the user. May be `'system'`. */
121
+ readonly selectedTheme: _angular_core.Signal<NgTheme>;
122
+ /** Resolved theme applied to the DOM. Always concrete β€” never `'system'`. */
123
+ readonly userTheme: _angular_core.Signal<ngx_theme_stack.NgSystemTheme>;
124
+ /** Whether the currently applied theme is dark. */
125
+ readonly isDark: _angular_core.Signal<boolean>;
126
+ /** Whether the currently applied theme is light. */
127
+ readonly isLight: _angular_core.Signal<boolean>;
128
+ /**
129
+ * Applies the given theme.
130
+ *
131
+ * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or custom.
132
+ * @throws If `theme` is not a valid theme according to library configuration.
133
+ */
134
+ select(theme: NgTheme): void;
135
+ static Ι΅fac: _angular_core.Ι΅Ι΅FactoryDeclaration<ThemeSelectService, never>;
136
+ static Ι΅prov: _angular_core.Ι΅Ι΅InjectableDeclaration<ThemeSelectService>;
137
+ }
138
+
139
+ /**
140
+ * Convenience service for toggling between `'dark'` and `'light'`.
141
+ *
142
+ * Use this when you only need a simple on/off switch and do not
143
+ * need to manage `'system'` or cycle through themes.
144
+ */
145
+ declare class ThemeToggleService {
146
+ #private;
147
+ /** Resolved theme applied to the DOM. Always concrete β€” never `'system'`. */
148
+ readonly userTheme: _angular_core.Signal<ngx_theme_stack.NgSystemTheme>;
149
+ /** Whether the currently applied theme is dark. */
150
+ readonly isDark: _angular_core.Signal<boolean>;
151
+ /** Whether the currently applied theme is light. */
152
+ readonly isLight: _angular_core.Signal<boolean>;
153
+ /**
154
+ * Toggles between `'dark'` and `'light'`.
155
+ *
156
+ * If the selected theme is explicitly `'dark'`, switches to `'light'`.
157
+ * Otherwise (including `'system'`), switches to `'dark'`.
158
+ */
159
+ toggle(): void;
160
+ static Ι΅fac: _angular_core.Ι΅Ι΅FactoryDeclaration<ThemeToggleService, never>;
161
+ static Ι΅prov: _angular_core.Ι΅Ι΅InjectableDeclaration<ThemeToggleService>;
162
+ }
163
+
164
+ export { CoreThemeService, DEFAULT_NG_CONFIG, DEFAULT_THEMES, NGX_THEME_STACK_CONFIG, ThemeCycleService, ThemeSelectService, ThemeToggleService, provideThemeStack };
165
+ export type { NgConfig, NgMode, NgSystemTheme, NgTheme };