ngx-theme-stack 1.0.0 β 2.0.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 +39 -22
- package/fesm2022/ngx-theme-stack.mjs +203 -39
- package/fesm2022/ngx-theme-stack.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/collection.json +6 -1
- package/schematics/ng-add/anti-flash.d.ts +24 -0
- package/schematics/ng-add/anti-flash.js +98 -0
- package/schematics/ng-add/anti-flash.js.map +1 -0
- package/schematics/ng-add/app-config.d.ts +3 -3
- package/schematics/ng-add/app-config.js +4 -4
- package/schematics/ng-add/app-config.js.map +1 -1
- package/schematics/ng-add/constants.d.ts +4 -5
- package/schematics/ng-add/constants.js +4 -5
- package/schematics/ng-add/constants.js.map +1 -1
- package/schematics/ng-add/index.js +35 -12
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/utils.d.ts +1 -1
- package/schematics/ng-add/utils.js +6 -6
- package/schematics/ng-add/utils.js.map +1 -1
- package/schematics/sync/index.d.ts +12 -0
- package/schematics/sync/index.js +153 -0
- package/schematics/sync/index.js.map +1 -0
- package/schematics/sync/schema.d.ts +4 -0
- package/schematics/sync/schema.js +3 -0
- package/schematics/sync/schema.js.map +1 -0
- package/schematics/sync/schema.json +16 -0
- package/types/ngx-theme-stack.d.ts +168 -33
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# ngx-theme-stack π¨
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A simple and powerful headless theme manager for **Angular**. Built for performance and SSR support.
|
|
4
6
|
|
|
5
7
|
## π Features
|
|
6
8
|
|
|
@@ -10,6 +12,7 @@ A complete and lightweight solution for managing themes (light, dark, system, an
|
|
|
10
12
|
- π οΈ **Highly Customizable**: Support for custom themes, class prefixes, and configurable storage.
|
|
11
13
|
- π§± **Modern Architecture**: Powered by Angular Signals for maximum reactivity and performance.
|
|
12
14
|
- π **SSR Ready**: Safe to use in Server-Side Rendering environments.
|
|
15
|
+
- π« **Zero Flicker**: Includes an optimized anti-flash script to prevent theme jumps on load.
|
|
13
16
|
|
|
14
17
|
## π¦ Installation
|
|
15
18
|
|
|
@@ -23,7 +26,7 @@ ng add ngx-theme-stack
|
|
|
23
26
|
|
|
24
27
|
When running `ng add`, you will be presented with two configuration options:
|
|
25
28
|
|
|
26
|
-
1. **Quick Mode**:
|
|
29
|
+
1. **Quick Mode**:
|
|
27
30
|
- Applies default configuration instantly.
|
|
28
31
|
- Initial theme: `system`.
|
|
29
32
|
- Apply mode: `class` (adds the theme class to the `<html>` element).
|
|
@@ -39,10 +42,11 @@ When running `ng add`, you will be presented with two configuration options:
|
|
|
39
42
|
|
|
40
43
|
The library is designed to be flexible. The **`CoreThemeService`** is the foundation:
|
|
41
44
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
45
|
+
- **Solid Base:** Manages state (`Signal`), persistence (`localStorage`), system detection (`matchMedia`), and safe DOM manipulation (SSR compatible).
|
|
46
|
+
- **Extensibility:** You can inject `CoreThemeService` to build your own custom services or components with specific business logic.
|
|
44
47
|
|
|
45
48
|
### Utility Services (Ready to Use)
|
|
49
|
+
|
|
46
50
|
For common use cases, we include three services with predefined logic:
|
|
47
51
|
|
|
48
52
|
1. **`ThemeToggleService`**: A simple binary switch between `light` and `dark`.
|
|
@@ -53,18 +57,18 @@ For common use cases, we include three services with predefined logic:
|
|
|
53
57
|
|
|
54
58
|
## βοΈ Supported Versions
|
|
55
59
|
|
|
56
|
-
| Angular Version | Support
|
|
57
|
-
|
|
|
58
|
-
| **Angular 21**
|
|
59
|
-
| **Angular 20**
|
|
60
|
-
| **Angular 19**
|
|
61
|
-
| **Angular 18**
|
|
60
|
+
| Angular Version | Support |
|
|
61
|
+
| :-------------- | :-------- |
|
|
62
|
+
| **Angular 21** | β
Stable |
|
|
63
|
+
| **Angular 20** | β
Stable |
|
|
64
|
+
| **Angular 19** | β
Stable |
|
|
65
|
+
| **Angular 18** | β
Stable |
|
|
62
66
|
|
|
63
67
|
## π οΈ Basic Usage
|
|
64
68
|
|
|
65
|
-
### CoreThemeService
|
|
69
|
+
### CoreThemeService API
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
The foundational service managing the theme state. It exposes pure Angular Signals and a solid minimal API.
|
|
68
72
|
|
|
69
73
|
```typescript
|
|
70
74
|
import { inject } from '@angular/core';
|
|
@@ -72,14 +76,29 @@ import { CoreThemeService } from 'ngx-theme-stack';
|
|
|
72
76
|
|
|
73
77
|
@Component({ ... })
|
|
74
78
|
export class MyComponent {
|
|
75
|
-
|
|
79
|
+
themeService = inject(CoreThemeService);
|
|
80
|
+
|
|
81
|
+
/* --- π Reactive Signals --- */
|
|
82
|
+
|
|
83
|
+
// The exact theme chosen by the user ('dark', 'light', 'system', etc.)
|
|
84
|
+
selectedTheme = this.themeService.selectedTheme;
|
|
85
|
+
|
|
86
|
+
// The theme finally applied to the DOM (resolves 'system' to 'dark' or 'light')
|
|
87
|
+
resolvedTheme = this.themeService.resolvedTheme;
|
|
88
|
+
|
|
89
|
+
// Helper boolean signals evaluating the applied theme
|
|
90
|
+
isDark = this.themeService.isDark;
|
|
91
|
+
isLight = this.themeService.isLight;
|
|
92
|
+
isSystem = this.themeService.isSystem;
|
|
93
|
+
|
|
94
|
+
// True after the first browser render. Great for preventing SSR flickering!
|
|
95
|
+
isHydrated = this.themeService.isHydrated;
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
isDark = this.themeService.isDark; // boolean (true/false)
|
|
79
|
-
selectedTheme = this.themeService.selectedTheme; // 'light' | 'dark' | 'system' | ...
|
|
97
|
+
/* --- π οΈ Methods --- */
|
|
80
98
|
|
|
81
|
-
changeTheme(
|
|
82
|
-
|
|
99
|
+
changeTheme(newTheme: string) {
|
|
100
|
+
// Validates, applies to the DOM, and saves to localStorage
|
|
101
|
+
this.themeService.setTheme(newTheme);
|
|
83
102
|
}
|
|
84
103
|
}
|
|
85
104
|
```
|
|
@@ -94,10 +113,8 @@ import { ThemeToggleService } from 'ngx-theme-stack';
|
|
|
94
113
|
@Component({
|
|
95
114
|
selector: 'app-theme-toggle',
|
|
96
115
|
template: `
|
|
97
|
-
<button (click)="toggle.toggle()">
|
|
98
|
-
|
|
99
|
-
</button>
|
|
100
|
-
`
|
|
116
|
+
<button (click)="toggle.toggle()">Switch to {{ toggle.isDark() ? 'Light' : 'Dark' }}</button>
|
|
117
|
+
`,
|
|
101
118
|
})
|
|
102
119
|
export class ThemeToggleComponent {
|
|
103
120
|
protected toggle = inject(ThemeToggleService);
|
|
@@ -1,37 +1,129 @@
|
|
|
1
1
|
import { isPlatformBrowser } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { InjectionToken, inject, DestroyRef, DOCUMENT, PLATFORM_ID, signal, computed, effect, Injectable } from '@angular/core';
|
|
3
|
+
import { InjectionToken, inject, DestroyRef, DOCUMENT, PLATFORM_ID, signal, computed, effect, afterNextRender, Injectable } from '@angular/core';
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Base error class for `ngx-theme-stack`.
|
|
7
|
+
*
|
|
8
|
+
* Thrown when the library configuration is invalid.
|
|
9
|
+
* Consumers can use `instanceof NgxThemeStackError` to catch only
|
|
10
|
+
* errors originating from this library.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* try {
|
|
14
|
+
* bootstrapApplication(AppComponent, appConfig);
|
|
15
|
+
* } catch (e) {
|
|
16
|
+
* if (e instanceof NgxThemeStackError) {
|
|
17
|
+
* console.error('Bad ngx-theme-stack config:', e.message);
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
class NgxThemeStackError extends Error {
|
|
22
|
+
name = 'NgxThemeStackError';
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(`[ngx-theme-stack] ${message}`);
|
|
25
|
+
// Restore prototype chain (required when targeting ES5 or older)
|
|
26
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Runtime list of built-in themes.
|
|
32
|
+
*
|
|
33
|
+
* Lives here (and not in config/index.ts) because it defines a type:
|
|
34
|
+
* config/index.ts already imports from types.ts, so placing DEFAULT_THEMES
|
|
35
|
+
* here avoids any circular dependency.
|
|
36
|
+
*
|
|
37
|
+
* β KEEP IN SYNC with the duplicate in:
|
|
38
|
+
* projects/ngx-theme-stack/schematics/ng-add/constants.ts β DEFAULT_THEMES
|
|
39
|
+
*
|
|
40
|
+
* Schematics compile to CommonJS and cannot import from this ESM file,
|
|
41
|
+
* so the values are intentionally duplicated. Change both at the same time.
|
|
42
|
+
*/
|
|
6
43
|
const DEFAULT_THEMES = ['system', 'light', 'dark'];
|
|
7
44
|
|
|
8
45
|
/**
|
|
9
46
|
* β ATTENTION: SHARED CONFIGURATION VALUES
|
|
10
47
|
*
|
|
11
|
-
* These
|
|
12
|
-
* projects/ngx-theme-stack/schematics/ng-add/
|
|
48
|
+
* These defaults MUST match the schematic defaults in:
|
|
49
|
+
* projects/ngx-theme-stack/schematics/ng-add/constants.ts β DEFAULTS
|
|
13
50
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
51
|
+
* Schematics compile to CommonJS and cannot import from this ESM file,
|
|
52
|
+
* so the values are intentionally duplicated. Change both at the same time.
|
|
53
|
+
*
|
|
54
|
+
* If you change defaults here, also update:
|
|
55
|
+
* schematics/ng-add/constants.ts β DEFAULTS + DEFAULT_THEMES
|
|
16
56
|
*/
|
|
17
57
|
const DEFAULT_NG_CONFIG = {
|
|
18
|
-
|
|
58
|
+
defaultTheme: 'system',
|
|
19
59
|
storageKey: 'ngx-theme-stack-theme',
|
|
20
60
|
mode: 'class',
|
|
21
61
|
themes: [...DEFAULT_THEMES],
|
|
22
62
|
};
|
|
63
|
+
// The token uses NgConfig<string> because Angular DI resolves types at runtime
|
|
64
|
+
// and cannot carry generic parameters. Type-safety is enforced at the
|
|
65
|
+
// provideThemeStack() call site instead.
|
|
23
66
|
const NGX_THEME_STACK_CONFIG = new InjectionToken('NGX_THEME_STACK_CONFIG', {
|
|
24
67
|
factory: () => DEFAULT_NG_CONFIG,
|
|
25
68
|
});
|
|
26
69
|
/**
|
|
27
|
-
*
|
|
70
|
+
* Provides Theme Stack configuration to Angular's DI system.
|
|
71
|
+
*
|
|
72
|
+
* Custom `themes` are **merged** with the built-in defaults
|
|
73
|
+
* (`'light'`, `'dark'`, `'system'`), so you never lose the base themes.
|
|
74
|
+
*
|
|
75
|
+
* The type parameter `T` is **inferred automatically** from the `themes` array
|
|
76
|
+
* when passed as a `const` β no need to specify it manually.
|
|
77
|
+
*
|
|
78
|
+
* @typeParam T - Custom theme string literals, inferred from the `themes` option.
|
|
79
|
+
*
|
|
80
|
+
* @param config - Optional partial configuration. Omitted fields fall back to
|
|
81
|
+
* {@link DEFAULT_NG_CONFIG}.
|
|
82
|
+
*
|
|
83
|
+
* @throws {@link NgxThemeStackError}
|
|
84
|
+
* - If any entry in `themes` is an empty or whitespace-only string.
|
|
85
|
+
* - If `defaultTheme` is not present in the resolved (merged) themes array.
|
|
86
|
+
* - If `storageKey` is an empty or whitespace-only string.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // Default β uses built-in themes and sensible defaults
|
|
90
|
+
* provideThemeStack()
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Closed union: TypeScript infers 'sepia' | 'ocean' from the array
|
|
94
|
+
* provideThemeStack({
|
|
95
|
+
* themes: ['sepia', 'ocean'] as const,
|
|
96
|
+
* defaultTheme: 'sepia', // β
in resolved themes
|
|
97
|
+
* // defaultTheme: 'nope', // β throws NgxThemeStackError at runtime
|
|
98
|
+
* })
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // Custom storage key and mode
|
|
102
|
+
* provideThemeStack({
|
|
103
|
+
* storageKey: 'my-app-theme',
|
|
104
|
+
* mode: 'class',
|
|
105
|
+
* })
|
|
28
106
|
*/
|
|
29
107
|
function provideThemeStack(config = {}) {
|
|
108
|
+
config.themes?.forEach((t) => {
|
|
109
|
+
if (t.trim() === '')
|
|
110
|
+
throw new NgxThemeStackError('Theme cannot be empty or whitespace.');
|
|
111
|
+
});
|
|
112
|
+
const themes = config.themes
|
|
113
|
+
? Array.from(new Set([...DEFAULT_NG_CONFIG.themes, ...config.themes]))
|
|
114
|
+
: DEFAULT_NG_CONFIG.themes;
|
|
115
|
+
if (config.defaultTheme && !themes.includes(config.defaultTheme)) {
|
|
116
|
+
throw new NgxThemeStackError(`"defaultTheme" must be one of the resolved themes: [${themes.join(', ')}].`);
|
|
117
|
+
}
|
|
118
|
+
if (config.storageKey !== undefined && config.storageKey.trim() === '') {
|
|
119
|
+
throw new NgxThemeStackError('"storageKey" cannot be empty or whitespace.');
|
|
120
|
+
}
|
|
30
121
|
return {
|
|
31
122
|
provide: NGX_THEME_STACK_CONFIG,
|
|
32
123
|
useValue: {
|
|
33
124
|
...DEFAULT_NG_CONFIG,
|
|
34
125
|
...config,
|
|
126
|
+
themes,
|
|
35
127
|
},
|
|
36
128
|
};
|
|
37
129
|
}
|
|
@@ -49,10 +141,17 @@ class CoreThemeService {
|
|
|
49
141
|
#document = inject(DOCUMENT);
|
|
50
142
|
#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
51
143
|
// ββ Theme configuration βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
52
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* The initial stored theme read from localStorage.
|
|
146
|
+
* This is used to determine the initial theme of the application.
|
|
147
|
+
*/
|
|
148
|
+
#initialStoredTheme = this.readStoredTheme();
|
|
149
|
+
/** List of available themes for Select/Cycle services. Defaults to ['system', 'light', 'dark']. */
|
|
53
150
|
availableThemes = this.#config.themes;
|
|
54
151
|
/** Internal Set for O(1) existence checks. */
|
|
55
152
|
#validThemes = new Set(this.availableThemes);
|
|
153
|
+
/** The anti-flash class to remove from the host element. */
|
|
154
|
+
#antiFlashClass = null;
|
|
56
155
|
// ββ System preference βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
57
156
|
/** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */
|
|
58
157
|
#mediaQuery = this.#isBrowser
|
|
@@ -64,22 +163,44 @@ class CoreThemeService {
|
|
|
64
163
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
65
164
|
selectedTheme = this.#selectedTheme.asReadonly();
|
|
66
165
|
/** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) β never `'system'`. */
|
|
67
|
-
|
|
166
|
+
resolvedTheme = computed(() => {
|
|
68
167
|
const theme = this.#selectedTheme();
|
|
69
168
|
return theme === 'system' ? this.#systemPreference() : theme;
|
|
70
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
169
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : /* istanbul ignore next */ []));
|
|
71
170
|
/** Whether the currently applied theme is dark. */
|
|
72
|
-
isDark = computed(() => this.
|
|
171
|
+
isDark = computed(() => this.resolvedTheme() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
|
|
73
172
|
/** Whether the currently applied theme is light. */
|
|
74
|
-
isLight = computed(() => this.
|
|
173
|
+
isLight = computed(() => this.resolvedTheme() === 'light', ...(ngDevMode ? [{ debugName: "isLight" }] : /* istanbul ignore next */ []));
|
|
174
|
+
/** Whether the currently applied theme is system. */
|
|
175
|
+
isSystem = computed(() => this.selectedTheme() === 'system', ...(ngDevMode ? [{ debugName: "isSystem" }] : /* istanbul ignore next */ []));
|
|
176
|
+
/**
|
|
177
|
+
* Whether the service has completed client-side initialization.
|
|
178
|
+
*
|
|
179
|
+
* `false` during SSR and on the very first render pass. Becomes `true`
|
|
180
|
+
* immediately after the first browser render, once the real persisted theme
|
|
181
|
+
* has been read from `localStorage`.
|
|
182
|
+
*
|
|
183
|
+
* Use this to guard any template logic that depends on `selectedTheme` or
|
|
184
|
+
* `resolvedTheme` to avoid an SSR hydration-mismatch flash: the server
|
|
185
|
+
* renders the default (`'system'`) while the browser may have a different
|
|
186
|
+
* value stored.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```html
|
|
190
|
+
* {{ themeService.isHydrated() ? selectedTheme() : 'β' }}
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
isHydrated = signal(false, ...(ngDevMode ? [{ debugName: "isHydrated" }] : /* istanbul ignore next */ []));
|
|
75
194
|
// ββ Event handler βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
76
195
|
#onSystemPreferenceChange = () => this.#systemPreference.set(this.resolveSystemPreference());
|
|
77
196
|
// ββ Lifecycle βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
78
197
|
constructor() {
|
|
198
|
+
this.captureAntiFlashClass();
|
|
79
199
|
if (this.#isBrowser && this.#selectedTheme() === 'system') {
|
|
80
200
|
this.startSystemThemeListener();
|
|
81
201
|
}
|
|
82
|
-
effect(() => this.applyThemeToDOM(this.
|
|
202
|
+
effect(() => this.applyThemeToDOM(this.resolvedTheme()));
|
|
203
|
+
afterNextRender(() => this.isHydrated.set(true));
|
|
83
204
|
this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());
|
|
84
205
|
}
|
|
85
206
|
// ββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -87,18 +208,20 @@ class CoreThemeService {
|
|
|
87
208
|
* Changes the active theme.
|
|
88
209
|
*
|
|
89
210
|
* Persists the choice explicitly so that switching e.g. from `'system'` to
|
|
90
|
-
* `'light'` is saved even when the resolved
|
|
211
|
+
* `'light'` is saved even when the resolved theme did not change
|
|
91
212
|
* (system preference was already `'light'`).
|
|
92
213
|
*
|
|
93
214
|
* @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.
|
|
94
215
|
* @throws If `theme` is not a valid theme according to library configuration.
|
|
95
216
|
*/
|
|
96
217
|
setTheme(theme) {
|
|
97
|
-
if (!this.#isBrowser)
|
|
98
|
-
return;
|
|
99
218
|
if (!this.#validThemes.has(theme)) {
|
|
100
|
-
throw new
|
|
219
|
+
throw new NgxThemeStackError(`Invalid theme: "${theme}". Valid values are: ${[...this.#validThemes].join(', ')}.`);
|
|
101
220
|
}
|
|
221
|
+
if (!this.#isBrowser)
|
|
222
|
+
return;
|
|
223
|
+
if (theme === this.#selectedTheme())
|
|
224
|
+
return;
|
|
102
225
|
if (theme === 'system') {
|
|
103
226
|
this.#systemPreference.set(this.resolveSystemPreference());
|
|
104
227
|
this.startSystemThemeListener();
|
|
@@ -114,9 +237,11 @@ class CoreThemeService {
|
|
|
114
237
|
return this.#mediaQuery?.matches ? 'dark' : 'light';
|
|
115
238
|
}
|
|
116
239
|
resolveInitialTheme() {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
240
|
+
const theme = this.#initialStoredTheme;
|
|
241
|
+
if (theme && this.#validThemes.has(theme)) {
|
|
242
|
+
return theme;
|
|
243
|
+
}
|
|
244
|
+
return this.#config.defaultTheme;
|
|
120
245
|
}
|
|
121
246
|
startSystemThemeListener() {
|
|
122
247
|
if (!this.#mediaQuery)
|
|
@@ -127,26 +252,28 @@ class CoreThemeService {
|
|
|
127
252
|
stopSystemThemeListener() {
|
|
128
253
|
this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);
|
|
129
254
|
}
|
|
130
|
-
applyThemeToDOM(
|
|
255
|
+
applyThemeToDOM(theme) {
|
|
131
256
|
if (!this.#isBrowser)
|
|
132
257
|
return;
|
|
133
258
|
const host = this.#document.documentElement;
|
|
259
|
+
if (this.#antiFlashClass) {
|
|
260
|
+
host.classList.remove(this.#antiFlashClass);
|
|
261
|
+
this.#antiFlashClass = null;
|
|
262
|
+
}
|
|
134
263
|
const { mode } = this.#config;
|
|
135
264
|
if (mode === 'attribute' || mode === 'both') {
|
|
136
|
-
this.applyThemeAttribute(host,
|
|
265
|
+
this.applyThemeAttribute(host, theme);
|
|
137
266
|
}
|
|
138
267
|
if (mode === 'class' || mode === 'both') {
|
|
139
|
-
this.applyThemeClasses(host,
|
|
268
|
+
this.applyThemeClasses(host, theme);
|
|
140
269
|
}
|
|
141
|
-
this.applyColorSchemeHint(host,
|
|
270
|
+
this.applyColorSchemeHint(host, theme);
|
|
142
271
|
}
|
|
143
272
|
applyThemeAttribute(host, theme) {
|
|
144
273
|
host.setAttribute('data-theme', theme);
|
|
145
274
|
}
|
|
146
275
|
applyThemeClasses(host, theme) {
|
|
147
|
-
|
|
148
|
-
host.classList.remove(t);
|
|
149
|
-
}
|
|
276
|
+
host.classList.remove(...this.availableThemes);
|
|
150
277
|
host.classList.add(theme);
|
|
151
278
|
}
|
|
152
279
|
applyColorSchemeHint(host, theme) {
|
|
@@ -156,13 +283,24 @@ class CoreThemeService {
|
|
|
156
283
|
}
|
|
157
284
|
host.style.removeProperty('color-scheme');
|
|
158
285
|
}
|
|
286
|
+
captureAntiFlashClass() {
|
|
287
|
+
if (!this.#isBrowser || !this.#initialStoredTheme)
|
|
288
|
+
return;
|
|
289
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(this.#initialStoredTheme)) {
|
|
290
|
+
this.#antiFlashClass = null;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (this.#initialStoredTheme === 'system') {
|
|
294
|
+
this.#antiFlashClass = this.resolveSystemPreference();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.#antiFlashClass = this.#initialStoredTheme;
|
|
298
|
+
}
|
|
159
299
|
readStoredTheme() {
|
|
160
|
-
|
|
161
|
-
const stored = localStorage.getItem(this.#config.storageKey);
|
|
162
|
-
if (stored && this.#validThemes.has(stored)) {
|
|
163
|
-
return stored;
|
|
164
|
-
}
|
|
300
|
+
if (!this.#isBrowser)
|
|
165
301
|
return null;
|
|
302
|
+
try {
|
|
303
|
+
return localStorage.getItem(this.#config.storageKey);
|
|
166
304
|
}
|
|
167
305
|
catch (e) {
|
|
168
306
|
console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);
|
|
@@ -170,6 +308,8 @@ class CoreThemeService {
|
|
|
170
308
|
}
|
|
171
309
|
}
|
|
172
310
|
saveTheme(theme) {
|
|
311
|
+
if (!this.#isBrowser)
|
|
312
|
+
return;
|
|
173
313
|
try {
|
|
174
314
|
localStorage.setItem(this.#config.storageKey, theme);
|
|
175
315
|
}
|
|
@@ -188,7 +328,7 @@ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
188
328
|
/**
|
|
189
329
|
* Convenience service for cycling through themes in a fixed order.
|
|
190
330
|
*
|
|
191
|
-
* Default cycle: `'
|
|
331
|
+
* Default cycle: `'system'` β `'light'` β `'dark'` β `'system'` β ...
|
|
192
332
|
*
|
|
193
333
|
* Use this when you want to offer users a single button that rotates
|
|
194
334
|
* through all available theme options.
|
|
@@ -200,11 +340,18 @@ class ThemeCycleService {
|
|
|
200
340
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
201
341
|
selectedTheme = this.#core.selectedTheme;
|
|
202
342
|
/** Resolved theme applied to the DOM. Always concrete β never `'system'`. */
|
|
203
|
-
|
|
343
|
+
resolvedTheme = this.#core.resolvedTheme;
|
|
204
344
|
/** Whether the currently applied theme is dark. */
|
|
205
345
|
isDark = this.#core.isDark;
|
|
206
346
|
/** Whether the currently applied theme is light. */
|
|
207
347
|
isLight = this.#core.isLight;
|
|
348
|
+
/** Whether the currently applied theme is system. */
|
|
349
|
+
isSystem = this.#core.isSystem;
|
|
350
|
+
/**
|
|
351
|
+
* Whether the service has completed client-side initialization.
|
|
352
|
+
* `false` during SSR. Becomes `true` after the first browser render.
|
|
353
|
+
*/
|
|
354
|
+
isHydrated = this.#core.isHydrated.asReadonly();
|
|
208
355
|
/**
|
|
209
356
|
* Advances to the next theme in the cycle.
|
|
210
357
|
*
|
|
@@ -240,11 +387,18 @@ class ThemeSelectService {
|
|
|
240
387
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
241
388
|
selectedTheme = this.#core.selectedTheme;
|
|
242
389
|
/** Resolved theme applied to the DOM. Always concrete β never `'system'`. */
|
|
243
|
-
|
|
390
|
+
resolvedTheme = this.#core.resolvedTheme;
|
|
244
391
|
/** Whether the currently applied theme is dark. */
|
|
245
392
|
isDark = this.#core.isDark;
|
|
246
393
|
/** Whether the currently applied theme is light. */
|
|
247
394
|
isLight = this.#core.isLight;
|
|
395
|
+
/** Whether the currently applied theme is system. */
|
|
396
|
+
isSystem = this.#core.isSystem;
|
|
397
|
+
/**
|
|
398
|
+
* Whether the service has completed client-side initialization.
|
|
399
|
+
* `false` during SSR. Becomes `true` after the first browser render.
|
|
400
|
+
*/
|
|
401
|
+
isHydrated = this.#core.isHydrated.asReadonly();
|
|
248
402
|
/**
|
|
249
403
|
* Applies the given theme.
|
|
250
404
|
*
|
|
@@ -271,11 +425,21 @@ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
271
425
|
class ThemeToggleService {
|
|
272
426
|
#core = inject(CoreThemeService);
|
|
273
427
|
/** Resolved theme applied to the DOM. Always concrete β never `'system'`. */
|
|
274
|
-
|
|
428
|
+
resolvedTheme = this.#core.resolvedTheme;
|
|
429
|
+
selectedTheme = this.#core.selectedTheme;
|
|
275
430
|
/** Whether the currently applied theme is dark. */
|
|
276
431
|
isDark = this.#core.isDark;
|
|
277
432
|
/** Whether the currently applied theme is light. */
|
|
278
433
|
isLight = this.#core.isLight;
|
|
434
|
+
/** Whether the currently applied theme is system. */
|
|
435
|
+
isSystem = this.#core.isSystem;
|
|
436
|
+
/**
|
|
437
|
+
* Whether the service has completed client-side initialization.
|
|
438
|
+
* `false` during SSR. Becomes `true` after the first browser render.
|
|
439
|
+
* Guard any template logic that shows `selectedTheme` or `resolvedTheme`
|
|
440
|
+
* behind this signal to avoid a hydration-mismatch flash.
|
|
441
|
+
*/
|
|
442
|
+
isHydrated = this.#core.isHydrated.asReadonly();
|
|
279
443
|
/**
|
|
280
444
|
* Toggles between `'dark'` and `'light'`.
|
|
281
445
|
*
|
|
@@ -283,7 +447,7 @@ class ThemeToggleService {
|
|
|
283
447
|
* Otherwise (including `'system'`), switches to `'dark'`.
|
|
284
448
|
*/
|
|
285
449
|
toggle() {
|
|
286
|
-
const next = this.#core.
|
|
450
|
+
const next = this.#core.resolvedTheme() === 'dark' ? 'light' : 'dark';
|
|
287
451
|
this.#core.setTheme(next);
|
|
288
452
|
}
|
|
289
453
|
static Ι΅fac = i0.Ι΅Ι΅ngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeToggleService, deps: [], target: i0.Ι΅Ι΅FactoryTarget.Injectable });
|
|
@@ -302,5 +466,5 @@ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
302
466
|
* Generated bundle index. Do not edit.
|
|
303
467
|
*/
|
|
304
468
|
|
|
305
|
-
export { CoreThemeService, DEFAULT_NG_CONFIG, DEFAULT_THEMES, NGX_THEME_STACK_CONFIG, ThemeCycleService, ThemeSelectService, ThemeToggleService, provideThemeStack };
|
|
469
|
+
export { CoreThemeService, DEFAULT_NG_CONFIG, DEFAULT_THEMES, NGX_THEME_STACK_CONFIG, NgxThemeStackError, ThemeCycleService, ThemeSelectService, ThemeToggleService, provideThemeStack };
|
|
306
470
|
//# sourceMappingURL=ngx-theme-stack.mjs.map
|
|
@@ -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/services/theme-stack.config.ts","../../../projects/ngx-theme-stack/src/lib/core/core-theme.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-cycle.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-select.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-toggle.service.ts","../../../projects/ngx-theme-stack/src/public-api.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 = ['system', 'light', 'dark'] 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\n/**\n * β ATTENTION: SHARED CONFIGURATION VALUES\n *\n * These values MUST match the schematic defaults in:\n * projects/ngx-theme-stack/schematics/ng-add/index.ts\n *\n * If you change any of these, you MUST also update the schematic's DEFAULTS\n * constant so 'ng add' continues to provide correct hints and clean code.\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 '../services/theme-stack.config';\nimport { NgTheme, NgSystemTheme } from '../types';\n\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/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/core-theme.service';\nimport { NgTheme } from '../types';\n\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/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 * Public API Surface of ngx-theme-stack\n */\n\nexport * from './lib/core/core-theme.service';\nexport * from './lib/services/theme-stack.config';\nexport * from './lib/services/theme-cycle.service';\nexport * from './lib/services/theme-select.service';\nexport * from './lib/services/theme-toggle.service';\nexport * from './lib/types';\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/errors.ts","../../../projects/ngx-theme-stack/src/lib/types.ts","../../../projects/ngx-theme-stack/src/lib/config/index.ts","../../../projects/ngx-theme-stack/src/lib/core/core-theme.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-cycle.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-select.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-toggle.service.ts","../../../projects/ngx-theme-stack/src/public-api.ts","../../../projects/ngx-theme-stack/src/ngx-theme-stack.ts"],"sourcesContent":["/**\n * Base error class for `ngx-theme-stack`.\n *\n * Thrown when the library configuration is invalid.\n * Consumers can use `instanceof NgxThemeStackError` to catch only\n * errors originating from this library.\n *\n * @example\n * try {\n * bootstrapApplication(AppComponent, appConfig);\n * } catch (e) {\n * if (e instanceof NgxThemeStackError) {\n * console.error('Bad ngx-theme-stack config:', e.message);\n * }\n * }\n */\nexport class NgxThemeStackError extends Error {\n override readonly name = 'NgxThemeStackError';\n\n constructor(message: string) {\n super(`[ngx-theme-stack] ${message}`);\n // Restore prototype chain (required when targeting ES5 or older)\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Runtime list of built-in themes.\n *\n * Lives here (and not in config/index.ts) because it defines a type:\n * config/index.ts already imports from types.ts, so placing DEFAULT_THEMES\n * here avoids any circular dependency.\n *\n * β KEEP IN SYNC with the duplicate in:\n * projects/ngx-theme-stack/schematics/ng-add/constants.ts β DEFAULT_THEMES\n *\n * Schematics compile to CommonJS and cannot import from this ESM file,\n * so the values are intentionally duplicated. Change both at the same time.\n */\nexport const DEFAULT_THEMES = ['system', 'light', 'dark'] as const;\n\n/** Literal union of built-in themes: `'system' | 'light' | 'dark'`. */\nexport type DefaultNgTheme = (typeof DEFAULT_THEMES)[number];\n\n/**\n * Theme type.\n *\n * - **Without** `T`: open union β accepts any `string` with IDE autocomplete\n * hints for the built-in themes (`'system' | 'light' | 'dark'`).\n * - **With** `T`: closed union β exactly `DefaultNgTheme | T`, enabling\n * full type-safety for custom theme sets.\n *\n * @example\n * NgTheme // 'system' | 'light' | 'dark' | (string & {})\n * NgTheme<'sepia'> // 'system' | 'light' | 'dark' | 'sepia'\n */\nexport type NgTheme<T extends string = string & {}> = DefaultNgTheme | T;\n\n/**\n * Resolved theme β always `'light'` or `'dark'`, never `'system'`.\n * Represents the value that comes from `matchMedia`, not user selection.\n */\nexport type NgSystemTheme = Exclude<DefaultNgTheme, 'system'>;\n\nexport type NgMode = 'attribute' | 'class' | 'both';\n\n/**\n * Library configuration.\n *\n * @typeParam T - Custom theme literals. Defaults to open `string`, preserving\n * backwards compatibility. Pass specific literals (e.g. `'sepia' | 'ocean'`)\n * via {@link provideThemeStack} to get a closed, type-safe theme union.\n */\nexport interface NgConfig<T extends string = string & {}> {\n defaultTheme: NgTheme<T>;\n storageKey: string;\n mode: NgMode;\n themes: NgTheme<T>[];\n}\n","import { InjectionToken } from '@angular/core';\nimport { NgxThemeStackError } from '../errors';\nimport { DEFAULT_THEMES, DefaultNgTheme, NgConfig } from '../types';\n\n/**\n * β ATTENTION: SHARED CONFIGURATION VALUES\n *\n * These defaults MUST match the schematic defaults in:\n * projects/ngx-theme-stack/schematics/ng-add/constants.ts β DEFAULTS\n *\n * Schematics compile to CommonJS and cannot import from this ESM file,\n * so the values are intentionally duplicated. Change both at the same time.\n *\n * If you change defaults here, also update:\n * schematics/ng-add/constants.ts β DEFAULTS + DEFAULT_THEMES\n */\n\nexport const DEFAULT_NG_CONFIG = {\n defaultTheme: 'system',\n storageKey: 'ngx-theme-stack-theme',\n mode: 'class',\n themes: [...DEFAULT_THEMES],\n} satisfies NgConfig;\n\n// The token uses NgConfig<string> because Angular DI resolves types at runtime\n// and cannot carry generic parameters. Type-safety is enforced at the\n// provideThemeStack() call site instead.\nexport const NGX_THEME_STACK_CONFIG = new InjectionToken<NgConfig<string>>(\n 'NGX_THEME_STACK_CONFIG',\n {\n factory: () => DEFAULT_NG_CONFIG,\n },\n);\n\n/**\n * Provides Theme Stack configuration to Angular's DI system.\n *\n * Custom `themes` are **merged** with the built-in defaults\n * (`'light'`, `'dark'`, `'system'`), so you never lose the base themes.\n *\n * The type parameter `T` is **inferred automatically** from the `themes` array\n * when passed as a `const` β no need to specify it manually.\n *\n * @typeParam T - Custom theme string literals, inferred from the `themes` option.\n *\n * @param config - Optional partial configuration. Omitted fields fall back to\n * {@link DEFAULT_NG_CONFIG}.\n *\n * @throws {@link NgxThemeStackError}\n * - If any entry in `themes` is an empty or whitespace-only string.\n * - If `defaultTheme` is not present in the resolved (merged) themes array.\n * - If `storageKey` is an empty or whitespace-only string.\n *\n * @example\n * // Default β uses built-in themes and sensible defaults\n * provideThemeStack()\n *\n * @example\n * // Closed union: TypeScript infers 'sepia' | 'ocean' from the array\n * provideThemeStack({\n * themes: ['sepia', 'ocean'] as const,\n * defaultTheme: 'sepia', // β
in resolved themes\n * // defaultTheme: 'nope', // β throws NgxThemeStackError at runtime\n * })\n *\n * @example\n * // Custom storage key and mode\n * provideThemeStack({\n * storageKey: 'my-app-theme',\n * mode: 'class',\n * })\n */\nexport function provideThemeStack<const T extends string = DefaultNgTheme>(\n config: Partial<NgConfig<T>> = {},\n) {\n config.themes?.forEach((t) => {\n if (t.trim() === '') throw new NgxThemeStackError('Theme cannot be empty or whitespace.');\n });\n\n const themes = config.themes\n ? Array.from(new Set([...DEFAULT_NG_CONFIG.themes, ...config.themes]))\n : DEFAULT_NG_CONFIG.themes;\n\n if (config.defaultTheme && !(themes as string[]).includes(config.defaultTheme as string)) {\n throw new NgxThemeStackError(\n `\"defaultTheme\" must be one of the resolved themes: [${themes.join(', ')}].`,\n );\n }\n\n if (config.storageKey !== undefined && config.storageKey.trim() === '') {\n throw new NgxThemeStackError('\"storageKey\" cannot be empty or whitespace.');\n }\n\n return {\n provide: NGX_THEME_STACK_CONFIG,\n useValue: {\n ...DEFAULT_NG_CONFIG,\n ...config,\n themes,\n } as NgConfig<string>,\n };\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\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 '../config';\nimport { NgxThemeStackError } from '../errors';\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 /**\n * The initial stored theme read from localStorage.\n * This is used to determine the initial theme of the application.\n */\n readonly #initialStoredTheme = this.readStoredTheme();\n\n /** List of available themes for Select/Cycle services. Defaults to ['system', 'light', 'dark']. */\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 /** The anti-flash class to remove from the host element. */\n #antiFlashClass: string | null = null;\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 resolvedTheme = 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.resolvedTheme() === 'dark');\n\n /** Whether the currently applied theme is light. */\n readonly isLight = computed(() => this.resolvedTheme() === 'light');\n\n /** Whether the currently applied theme is system. */\n readonly isSystem = computed(() => this.selectedTheme() === 'system');\n\n /**\n * Whether the service has completed client-side initialization.\n *\n * `false` during SSR and on the very first render pass. Becomes `true`\n * immediately after the first browser render, once the real persisted theme\n * has been read from `localStorage`.\n *\n * Use this to guard any template logic that depends on `selectedTheme` or\n * `resolvedTheme` to avoid an SSR hydration-mismatch flash: the server\n * renders the default (`'system'`) while the browser may have a different\n * value stored.\n *\n * @example\n * ```html\n * {{ themeService.isHydrated() ? selectedTheme() : 'β' }}\n * ```\n */\n readonly isHydrated = signal(false);\n\n // ββ Event handler βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n readonly #onSystemPreferenceChange = () =>\n this.#systemPreference.set(this.resolveSystemPreference());\n\n // ββ Lifecycle βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n constructor() {\n this.captureAntiFlashClass();\n\n if (this.#isBrowser && this.#selectedTheme() === 'system') {\n this.startSystemThemeListener();\n }\n\n effect(() => this.applyThemeToDOM(this.resolvedTheme()));\n afterNextRender(() => this.isHydrated.set(true));\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 theme 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.#validThemes.has(theme)) {\n throw new NgxThemeStackError(\n `Invalid theme: \"${theme}\". Valid values are: ${[...this.#validThemes].join(', ')}.`,\n );\n }\n if (!this.#isBrowser) return;\n if (theme === this.#selectedTheme()) return;\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 const theme = this.#initialStoredTheme;\n if (theme && this.#validThemes.has(theme as NgTheme)) {\n return theme as NgTheme;\n }\n return this.#config.defaultTheme;\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(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n const host = this.#document.documentElement;\n\n if (this.#antiFlashClass) {\n host.classList.remove(this.#antiFlashClass);\n this.#antiFlashClass = null;\n }\n\n const { mode } = this.#config;\n\n if (mode === 'attribute' || mode === 'both') {\n this.applyThemeAttribute(host, theme);\n }\n\n if (mode === 'class' || mode === 'both') {\n this.applyThemeClasses(host, theme);\n }\n\n this.applyColorSchemeHint(host, theme);\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 host.classList.remove(...this.availableThemes);\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 captureAntiFlashClass(): void {\n if (!this.#isBrowser || !this.#initialStoredTheme) return;\n\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(this.#initialStoredTheme)) {\n this.#antiFlashClass = null;\n return;\n }\n\n if (this.#initialStoredTheme === 'system') {\n this.#antiFlashClass = this.resolveSystemPreference();\n return;\n }\n\n this.#antiFlashClass = this.#initialStoredTheme;\n }\n\n private readStoredTheme(): string | null {\n if (!this.#isBrowser) return null;\n\n try {\n return localStorage.getItem(this.#config.storageKey);\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 if (!this.#isBrowser) return;\n\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/core-theme.service';\n\n/**\n * Convenience service for cycling through themes in a fixed order.\n *\n * Default cycle: `'system'` β `'light'` β `'dark'` β `'system'` β ...\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 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 resolvedTheme = this.#core.resolvedTheme;\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 /** Whether the currently applied theme is system. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization.\n * `false` during SSR. Becomes `true` after the first browser render.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\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/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 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 resolvedTheme = this.#core.resolvedTheme;\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 /** Whether the currently applied theme is system. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization.\n * `false` during SSR. Becomes `true` after the first browser render.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\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/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 readonly #core = inject(CoreThemeService);\n\n /** Resolved theme applied to the DOM. Always concrete β never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n readonly selectedTheme = this.#core.selectedTheme;\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 /** Whether the currently applied theme is system. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization.\n * `false` during SSR. Becomes `true` after the first browser render.\n * Guard any template logic that shows `selectedTheme` or `resolvedTheme`\n * behind this signal to avoid a hydration-mismatch flash.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\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.resolvedTheme() === 'dark' ? 'light' : 'dark';\n this.#core.setTheme(next);\n }\n}\n","/*\n * Public API Surface of ngx-theme-stack\n */\n\nexport * from './lib/core/core-theme.service';\nexport * from './lib/config';\nexport * from './lib/services/theme-cycle.service';\nexport * from './lib/services/theme-select.service';\nexport * from './lib/services/theme-toggle.service';\nexport * from './lib/types';\nexport * from './lib/errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|