ngx-theme-stack 1.0.1 β 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 +199 -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,35 +1,123 @@
|
|
|
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
|
+
});
|
|
30
112
|
const themes = config.themes
|
|
31
113
|
? Array.from(new Set([...DEFAULT_NG_CONFIG.themes, ...config.themes]))
|
|
32
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
|
+
}
|
|
33
121
|
return {
|
|
34
122
|
provide: NGX_THEME_STACK_CONFIG,
|
|
35
123
|
useValue: {
|
|
@@ -53,10 +141,17 @@ class CoreThemeService {
|
|
|
53
141
|
#document = inject(DOCUMENT);
|
|
54
142
|
#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
55
143
|
// ββ Theme configuration βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
56
|
-
/**
|
|
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']. */
|
|
57
150
|
availableThemes = this.#config.themes;
|
|
58
151
|
/** Internal Set for O(1) existence checks. */
|
|
59
152
|
#validThemes = new Set(this.availableThemes);
|
|
153
|
+
/** The anti-flash class to remove from the host element. */
|
|
154
|
+
#antiFlashClass = null;
|
|
60
155
|
// ββ System preference βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
61
156
|
/** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */
|
|
62
157
|
#mediaQuery = this.#isBrowser
|
|
@@ -68,22 +163,44 @@ class CoreThemeService {
|
|
|
68
163
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
69
164
|
selectedTheme = this.#selectedTheme.asReadonly();
|
|
70
165
|
/** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) β never `'system'`. */
|
|
71
|
-
|
|
166
|
+
resolvedTheme = computed(() => {
|
|
72
167
|
const theme = this.#selectedTheme();
|
|
73
168
|
return theme === 'system' ? this.#systemPreference() : theme;
|
|
74
|
-
}, ...(ngDevMode ? [{ debugName: "
|
|
169
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : /* istanbul ignore next */ []));
|
|
75
170
|
/** Whether the currently applied theme is dark. */
|
|
76
|
-
isDark = computed(() => this.
|
|
171
|
+
isDark = computed(() => this.resolvedTheme() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
|
|
77
172
|
/** Whether the currently applied theme is light. */
|
|
78
|
-
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 */ []));
|
|
79
194
|
// ββ Event handler βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
80
195
|
#onSystemPreferenceChange = () => this.#systemPreference.set(this.resolveSystemPreference());
|
|
81
196
|
// ββ Lifecycle βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
82
197
|
constructor() {
|
|
198
|
+
this.captureAntiFlashClass();
|
|
83
199
|
if (this.#isBrowser && this.#selectedTheme() === 'system') {
|
|
84
200
|
this.startSystemThemeListener();
|
|
85
201
|
}
|
|
86
|
-
effect(() => this.applyThemeToDOM(this.
|
|
202
|
+
effect(() => this.applyThemeToDOM(this.resolvedTheme()));
|
|
203
|
+
afterNextRender(() => this.isHydrated.set(true));
|
|
87
204
|
this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());
|
|
88
205
|
}
|
|
89
206
|
// ββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -91,18 +208,20 @@ class CoreThemeService {
|
|
|
91
208
|
* Changes the active theme.
|
|
92
209
|
*
|
|
93
210
|
* Persists the choice explicitly so that switching e.g. from `'system'` to
|
|
94
|
-
* `'light'` is saved even when the resolved
|
|
211
|
+
* `'light'` is saved even when the resolved theme did not change
|
|
95
212
|
* (system preference was already `'light'`).
|
|
96
213
|
*
|
|
97
214
|
* @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.
|
|
98
215
|
* @throws If `theme` is not a valid theme according to library configuration.
|
|
99
216
|
*/
|
|
100
217
|
setTheme(theme) {
|
|
101
|
-
if (!this.#isBrowser)
|
|
102
|
-
return;
|
|
103
218
|
if (!this.#validThemes.has(theme)) {
|
|
104
|
-
throw new
|
|
219
|
+
throw new NgxThemeStackError(`Invalid theme: "${theme}". Valid values are: ${[...this.#validThemes].join(', ')}.`);
|
|
105
220
|
}
|
|
221
|
+
if (!this.#isBrowser)
|
|
222
|
+
return;
|
|
223
|
+
if (theme === this.#selectedTheme())
|
|
224
|
+
return;
|
|
106
225
|
if (theme === 'system') {
|
|
107
226
|
this.#systemPreference.set(this.resolveSystemPreference());
|
|
108
227
|
this.startSystemThemeListener();
|
|
@@ -118,9 +237,11 @@ class CoreThemeService {
|
|
|
118
237
|
return this.#mediaQuery?.matches ? 'dark' : 'light';
|
|
119
238
|
}
|
|
120
239
|
resolveInitialTheme() {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
240
|
+
const theme = this.#initialStoredTheme;
|
|
241
|
+
if (theme && this.#validThemes.has(theme)) {
|
|
242
|
+
return theme;
|
|
243
|
+
}
|
|
244
|
+
return this.#config.defaultTheme;
|
|
124
245
|
}
|
|
125
246
|
startSystemThemeListener() {
|
|
126
247
|
if (!this.#mediaQuery)
|
|
@@ -131,26 +252,28 @@ class CoreThemeService {
|
|
|
131
252
|
stopSystemThemeListener() {
|
|
132
253
|
this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);
|
|
133
254
|
}
|
|
134
|
-
applyThemeToDOM(
|
|
255
|
+
applyThemeToDOM(theme) {
|
|
135
256
|
if (!this.#isBrowser)
|
|
136
257
|
return;
|
|
137
258
|
const host = this.#document.documentElement;
|
|
259
|
+
if (this.#antiFlashClass) {
|
|
260
|
+
host.classList.remove(this.#antiFlashClass);
|
|
261
|
+
this.#antiFlashClass = null;
|
|
262
|
+
}
|
|
138
263
|
const { mode } = this.#config;
|
|
139
264
|
if (mode === 'attribute' || mode === 'both') {
|
|
140
|
-
this.applyThemeAttribute(host,
|
|
265
|
+
this.applyThemeAttribute(host, theme);
|
|
141
266
|
}
|
|
142
267
|
if (mode === 'class' || mode === 'both') {
|
|
143
|
-
this.applyThemeClasses(host,
|
|
268
|
+
this.applyThemeClasses(host, theme);
|
|
144
269
|
}
|
|
145
|
-
this.applyColorSchemeHint(host,
|
|
270
|
+
this.applyColorSchemeHint(host, theme);
|
|
146
271
|
}
|
|
147
272
|
applyThemeAttribute(host, theme) {
|
|
148
273
|
host.setAttribute('data-theme', theme);
|
|
149
274
|
}
|
|
150
275
|
applyThemeClasses(host, theme) {
|
|
151
|
-
|
|
152
|
-
host.classList.remove(t);
|
|
153
|
-
}
|
|
276
|
+
host.classList.remove(...this.availableThemes);
|
|
154
277
|
host.classList.add(theme);
|
|
155
278
|
}
|
|
156
279
|
applyColorSchemeHint(host, theme) {
|
|
@@ -160,13 +283,24 @@ class CoreThemeService {
|
|
|
160
283
|
}
|
|
161
284
|
host.style.removeProperty('color-scheme');
|
|
162
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
|
+
}
|
|
163
299
|
readStoredTheme() {
|
|
164
|
-
|
|
165
|
-
const stored = localStorage.getItem(this.#config.storageKey);
|
|
166
|
-
if (stored && this.#validThemes.has(stored)) {
|
|
167
|
-
return stored;
|
|
168
|
-
}
|
|
300
|
+
if (!this.#isBrowser)
|
|
169
301
|
return null;
|
|
302
|
+
try {
|
|
303
|
+
return localStorage.getItem(this.#config.storageKey);
|
|
170
304
|
}
|
|
171
305
|
catch (e) {
|
|
172
306
|
console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);
|
|
@@ -174,6 +308,8 @@ class CoreThemeService {
|
|
|
174
308
|
}
|
|
175
309
|
}
|
|
176
310
|
saveTheme(theme) {
|
|
311
|
+
if (!this.#isBrowser)
|
|
312
|
+
return;
|
|
177
313
|
try {
|
|
178
314
|
localStorage.setItem(this.#config.storageKey, theme);
|
|
179
315
|
}
|
|
@@ -192,7 +328,7 @@ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
192
328
|
/**
|
|
193
329
|
* Convenience service for cycling through themes in a fixed order.
|
|
194
330
|
*
|
|
195
|
-
* Default cycle: `'
|
|
331
|
+
* Default cycle: `'system'` β `'light'` β `'dark'` β `'system'` β ...
|
|
196
332
|
*
|
|
197
333
|
* Use this when you want to offer users a single button that rotates
|
|
198
334
|
* through all available theme options.
|
|
@@ -204,11 +340,18 @@ class ThemeCycleService {
|
|
|
204
340
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
205
341
|
selectedTheme = this.#core.selectedTheme;
|
|
206
342
|
/** Resolved theme applied to the DOM. Always concrete β never `'system'`. */
|
|
207
|
-
|
|
343
|
+
resolvedTheme = this.#core.resolvedTheme;
|
|
208
344
|
/** Whether the currently applied theme is dark. */
|
|
209
345
|
isDark = this.#core.isDark;
|
|
210
346
|
/** Whether the currently applied theme is light. */
|
|
211
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();
|
|
212
355
|
/**
|
|
213
356
|
* Advances to the next theme in the cycle.
|
|
214
357
|
*
|
|
@@ -244,11 +387,18 @@ class ThemeSelectService {
|
|
|
244
387
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
245
388
|
selectedTheme = this.#core.selectedTheme;
|
|
246
389
|
/** Resolved theme applied to the DOM. Always concrete β never `'system'`. */
|
|
247
|
-
|
|
390
|
+
resolvedTheme = this.#core.resolvedTheme;
|
|
248
391
|
/** Whether the currently applied theme is dark. */
|
|
249
392
|
isDark = this.#core.isDark;
|
|
250
393
|
/** Whether the currently applied theme is light. */
|
|
251
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();
|
|
252
402
|
/**
|
|
253
403
|
* Applies the given theme.
|
|
254
404
|
*
|
|
@@ -275,11 +425,21 @@ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
275
425
|
class ThemeToggleService {
|
|
276
426
|
#core = inject(CoreThemeService);
|
|
277
427
|
/** Resolved theme applied to the DOM. Always concrete β never `'system'`. */
|
|
278
|
-
|
|
428
|
+
resolvedTheme = this.#core.resolvedTheme;
|
|
429
|
+
selectedTheme = this.#core.selectedTheme;
|
|
279
430
|
/** Whether the currently applied theme is dark. */
|
|
280
431
|
isDark = this.#core.isDark;
|
|
281
432
|
/** Whether the currently applied theme is light. */
|
|
282
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();
|
|
283
443
|
/**
|
|
284
444
|
* Toggles between `'dark'` and `'light'`.
|
|
285
445
|
*
|
|
@@ -287,7 +447,7 @@ class ThemeToggleService {
|
|
|
287
447
|
* Otherwise (including `'system'`), switches to `'dark'`.
|
|
288
448
|
*/
|
|
289
449
|
toggle() {
|
|
290
|
-
const next = this.#core.
|
|
450
|
+
const next = this.#core.resolvedTheme() === 'dark' ? 'light' : 'dark';
|
|
291
451
|
this.#core.setTheme(next);
|
|
292
452
|
}
|
|
293
453
|
static Ι΅fac = i0.Ι΅Ι΅ngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeToggleService, deps: [], target: i0.Ι΅Ι΅FactoryTarget.Injectable });
|
|
@@ -306,5 +466,5 @@ i0.Ι΅Ι΅ngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
306
466
|
* Generated bundle index. Do not edit.
|
|
307
467
|
*/
|
|
308
468
|
|
|
309
|
-
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 };
|
|
310
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/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":["/** 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 const themes = config.themes\n ? Array.from(new Set([...DEFAULT_NG_CONFIG.themes, ...config.themes]))\n : DEFAULT_NG_CONFIG.themes;\n\n return {\n provide: NGX_THEME_STACK_CONFIG,\n useValue: {\n ...DEFAULT_NG_CONFIG,\n ...config,\n themes,\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 '../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/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":";;;;AAAA;AACO,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM;;ACExD;;;;;;;;AAQG;AACI,MAAM,iBAAiB,GAAG;AAC/B,IAAA,KAAK,EAAE,QAAQ;AACf,IAAA,UAAU,EAAE,uBAAuB;AACnC,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,MAAM,EAAE,CAAC,GAAG,cAAc,CAAC;;MAGhB,sBAAsB,GAAG,IAAI,cAAc,CAAW,wBAAwB,EAAE;AAC3F,IAAA,OAAO,EAAE,MAAM,iBAAiB;AACjC,CAAA;AAED;;AAEG;AACG,SAAU,iBAAiB,CAAC,MAAA,GAA4B,EAAE,EAAA;AAC9D,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC;UAClB,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,iBAAiB,CAAC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AACrE,UAAE,iBAAiB,CAAC,MAAM;IAE5B,OAAO;AACL,QAAA,OAAO,EAAE,sBAAsB;AAC/B,QAAA,QAAQ,EAAE;AACR,YAAA,GAAG,iBAAiB;AACpB,YAAA,GAAG,MAAM;YACT,MAAM;AACY,SAAA;KACrB;AACH;;ACxBA;;;;;AAKG;MAEU,gBAAgB,CAAA;;AAGlB,IAAA,OAAO,GAAG,MAAM,CAAC,sBAAsB,CAAC;AACxC,IAAA,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC;AAChC,IAAA,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC5B,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;;;AAKnD,IAAA,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM;;IAGrC,YAAY,GAAG,IAAI,GAAG,CAAU,IAAI,CAAC,eAAe,CAAC;;;IAKrD,WAAW,GAA0B,IAAI,CAAC;AACjD,WAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,UAAU,CAAC,8BAA8B,CAAC,IAAI,IAAI;UAC/E,IAAI;IAEC,iBAAiB,GAAG,MAAM,CAAgB,IAAI,CAAC,uBAAuB,EAAE,wFAAC;;IAIzE,cAAc,GAAG,MAAM,CAAU,IAAI,CAAC,mBAAmB,EAAE,qFAAC;;AAG5D,IAAA,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE;;AAGhD,IAAA,SAAS,GAAG,QAAQ,CAAC,MAAK;AACjC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE;AACnC,QAAA,OAAO,KAAK,KAAK,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,GAAG,KAAK;AAC9D,IAAA,CAAC,gFAAC;;AAGO,IAAA,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,MAAM,6EAAC;;AAGpD,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,OAAO,8EAAC;;AAItD,IAAA,yBAAyB,GAAG,MACnC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC;;AAI5D,IAAA,WAAA,GAAA;QACE,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,cAAc,EAAE,KAAK,QAAQ,EAAE;YACzD,IAAI,CAAC,wBAAwB,EAAE;QACjC;AAEA,QAAA,MAAM,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;AAEpD,QAAA,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,uBAAuB,EAAE,CAAC;IAClE;;AAIA;;;;;;;;;AASG;AACI,IAAA,QAAQ,CAAC,KAAc,EAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE;QAEtB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;AACjC,YAAA,MAAM,IAAI,KAAK,CACb,qCAAqC,KAAK,CAAA,qBAAA,EAAwB,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAA,CAAG,CACvG;QACH;AAEA,QAAA,IAAI,KAAK,KAAK,QAAQ,EAAE;YACtB,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC1D,IAAI,CAAC,wBAAwB,EAAE;QACjC;aAAO;YACL,IAAI,CAAC,uBAAuB,EAAE;QAChC;AAEA,QAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC;AAC9B,QAAA,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;IACvB;;IAIQ,uBAAuB,GAAA;AAC7B,QAAA,OAAO,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO;IACrD;IAEQ,mBAAmB,GAAA;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU;AAAE,YAAA,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK;QAC/C,OAAO,IAAI,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK;IACrD;IAEQ,wBAAwB,GAAA;QAC9B,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE;QACvB,IAAI,CAAC,uBAAuB,EAAE;QAC9B,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,yBAAyB,CAAC;IAC7E;IAEQ,uBAAuB,GAAA;QAC7B,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,yBAAyB,CAAC;IACjF;AAEQ,IAAA,eAAe,CAAC,SAAkB,EAAA;QACxC,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE;AAEtB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe;AAC3C,QAAA,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO;QAE7B,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,MAAM,EAAE;AAC3C,YAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,SAAS,CAAC;QAC3C;QAEA,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE;AACvC,YAAA,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,SAAS,CAAC;QACzC;AAEA,QAAA,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,SAAS,CAAC;IAC5C;IAEQ,mBAAmB,CAAC,IAAiB,EAAE,KAAc,EAAA;AAC3D,QAAA,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC;IACxC;IAEQ,iBAAiB,CAAC,IAAiB,EAAE,KAAc,EAAA;AACzD,QAAA,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE;AACpC,YAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1B;AAEA,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;IAC3B;IAEQ,oBAAoB,CAAC,IAAiB,EAAE,KAAc,EAAA;QAC5D,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,EAAE;YACzC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,cAAc,EAAE,KAAK,CAAC;YAC7C;QACF;AAEA,QAAA,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC3C;IAEQ,eAAe,GAAA;AACrB,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;YAC5D,IAAI,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAiB,CAAC,EAAE;AACtD,gBAAA,OAAO,MAAiB;YAC1B;AACA,YAAA,OAAO,IAAI;QACb;QAAE,OAAO,CAAC,EAAE;AACV,YAAA,OAAO,CAAC,IAAI,CAAC,2DAA2D,EAAE,CAAC,CAAC;AAC5E,YAAA,OAAO,IAAI;QACb;IACF;AAEQ,IAAA,SAAS,CAAC,KAAc,EAAA;AAC9B,QAAA,IAAI;YACF,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC;QACtD;QAAE,OAAO,CAAC,EAAE;AACV,YAAA,OAAO,CAAC,IAAI,CAAC,yDAAyD,EAAE,CAAC,CAAC;QAC5E;IACF;uGA3KW,gBAAgB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAhB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,gBAAgB,cADH,MAAM,EAAA,CAAA;;2FACnB,gBAAgB,EAAA,UAAA,EAAA,CAAA;kBAD5B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;AClBlC;;;;;;;AAOG;MAEU,iBAAiB,CAAA;AAEnB,IAAA,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC;;AAGhC,IAAA,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe;;AAGnC,IAAA,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa;;AAGxC,IAAA,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS;;AAGhC,IAAA,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM;;AAG1B,IAAA,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO;AAErC;;;;;;;AAOG;IACH,KAAK,GAAA;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;AAC1C,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;AAC1D,QAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC3B;uGAhCW,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAjB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,iBAAiB,cADJ,MAAM,EAAA,CAAA;;2FACnB,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAD7B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACNlC;;;;;AAKG;MAEU,kBAAkB,CAAA;AAEpB,IAAA,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC;;AAGhC,IAAA,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe;;AAG5C,IAAA,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa;;AAGxC,IAAA,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS;;AAGhC,IAAA,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM;;AAG1B,IAAA,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO;AAErC;;;;;AAKG;AACH,IAAA,MAAM,CAAC,KAAc,EAAA;AACnB,QAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;IAC5B;uGA3BW,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAlB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,kBAAkB,cADL,MAAM,EAAA,CAAA;;2FACnB,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACRlC;;;;;AAKG;MAEU,kBAAkB,CAAA;AAEpB,IAAA,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC;;AAGhC,IAAA,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS;;AAGhC,IAAA,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM;;AAG1B,IAAA,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO;AAErC;;;;;AAKG;IACH,MAAM,GAAA;AACJ,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,MAAM,GAAG,OAAO,GAAG,MAAM;AACrE,QAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC3B;uGAtBW,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAlB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,kBAAkB,cADL,MAAM,EAAA,CAAA;;2FACnB,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAD9B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACTlC;;AAEG;;ACFH;;AAEG;;;;"}
|
|
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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"}
|