ngx-theme-stack 3.1.0 → 3.2.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 CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  A simple and powerful headless theme manager for **Angular**. Built for performance and SSR support.
6
6
 
7
+ [**🌐 Live Demo**](https://demo-ngx-theme-stack.wanderlee.site/) | [**📚 Documentation**](https://ngx-theme-stack-docs.wanderlee.site/) | [**⭐ Star on GitHub**](https://github.com/WanderleeDev/ngx-theme-stack)
8
+
7
9
  ## 🚀 Features
8
10
 
9
11
  - ⚡ **Single Command Installation**: Automatic configuration via `ng add`.
@@ -24,6 +26,14 @@ ng add ngx-theme-stack
24
26
 
25
27
  ### Installation Modes
26
28
 
29
+ > [!TIP]
30
+ > **🚀 Using Bun?**
31
+ > Since `ng add` is currently not supported for Bun environments, please use this two-step process:
32
+ > 1. **Install:** `bun add ngx-theme-stack`
33
+ > 2. **Configure:** `ng generate ngx-theme-stack:ng-add`
34
+ >
35
+ > This ensures Bun handles the dependency management while the schematic automates the code configuration (providers, index.html, tokens, etc.).
36
+
27
37
  When running `ng add`, you will be presented with two configuration options:
28
38
 
29
39
  1. **Quick Mode**:
@@ -44,12 +54,14 @@ When running `ng add`, you will be presented with two configuration options:
44
54
 
45
55
  To provide a "Zero Config" experience, the installation command automates the following:
46
56
 
47
- 1. **`package.json`**: Adds a `"prebuild"` script that executes the synchronization automatically before every build.
48
- 2. **`angular.json`**:
49
- - Adds `src/themes.css` to the global styles list.
50
- - Configures the `inlineCritical` optimization based on your selected strategy.
51
- 3. **`index.html`**: Injects the marker and the blocking anti-flash script into the `<head>`.
52
- 4. **`themes.css`**: Creates a base file with selectors ready for you to define your variables.
57
+ 1. **`app.config.ts` (or `main.ts`)**: Injects `provideThemeStack()` into your providers array. It's **Smart**: it uses AST to follow imports and find your providers even if they are delegated to external files.
58
+ 2. **`index.html`**: Injects the blocking anti-flash script into the `<head>` to ensure a seamless theme experience.
59
+ 3. **`package.json`**: Adds a `"prebuild"` script to automate theme synchronization.
60
+ 4. **`angular.json`**: Registers `themes.css` and optimizes build configurations.
61
+ 5. **`themes.css`**: Scaffolds your base theme tokens if they don't exist.
62
+
63
+ > [!TIP]
64
+ > **Re-configuration support:** You can run `ng add` multiple times. If you change your mind about the `storageKey` or the `mode`, the schematic will update your existing code and script automatically without duplicating them.
53
65
 
54
66
  ## 🏗️ Architecture & Extensibility
55
67
 
@@ -64,7 +76,7 @@ For common use cases, we include three services with predefined logic:
64
76
 
65
77
  1. **`ThemeToggleService`**: A simple binary switch between `light` and `dark`.
66
78
  2. **`ThemeSelectService`**: Exposes the full list of themes and methods to select them.
67
- 3. **`ThemeCycleService`**: A circular function to cycle through all available themes with a single click.
79
+ 3. **`ThemeCycleService`**: A circular function to cycle through all available themes; exposes `upcoming`, `preceding`, and `cycleIndex` signals for UI feedback.
68
80
 
69
81
  ---
70
82
 
@@ -89,7 +101,7 @@ export const appConfig: ApplicationConfig = {
89
101
  defaultTheme: 'system', // Initial fallback ('system' resolves via matchMedia)
90
102
  mode: 'class', // 'class', 'attribute' or 'both'
91
103
  strategy: 'critters', // 'critters' (SSR) or 'blocking' (Standard SPA)
92
- storageKey: 'ngx-theme-stack-theme' // LocalStorage key
104
+ storageKey: 'ngx-theme-stack' // LocalStorage key
93
105
  })
94
106
  ]
95
107
  };
@@ -101,23 +113,98 @@ export const appConfig: ApplicationConfig = {
101
113
  | `defaultTheme` | `string` | `'system'` | Theme used on first visit or when no preference is saved. |
102
114
  | `mode` | `NgMode` | `'class'` | How the theme is applied: `class`, `attribute` (`data-theme`), or `both`. |
103
115
  | `strategy` | `NgStrategy`| `'critters'` | Anti-flash performance strategy: `critters` (inlined CSS) or `blocking`. |
104
- | `storageKey` | `string` | `'ngx-theme-stack-theme'` | Key used to persist theme preference in `localStorage`. |
116
+ | `storageKey` | `string` | `'ngx-theme-stack'` | Key used to persist theme preference in `localStorage`. |
105
117
 
106
118
  > [!IMPORTANT]
107
119
  > Whenever you update these settings, run `ng generate ngx-theme-stack:sync` to ensure your `index.html` is updated with the correct anti-flash script.
108
120
 
109
- ## 🛠️ Basic Usage
121
+ ## 🛠️ Usage
122
+
123
+ For most use cases, we recommend using the built-in **Utility Services**. They provide ready-to-use logic for common patterns.
124
+
125
+ ### 1. Simple Toggle (Dark/Light)
126
+
127
+ Use `ThemeToggleService` when you only need a simple switch.
128
+
129
+ ```typescript
130
+ import { inject } from '@angular/core';
131
+ import { ThemeToggleService } from 'ngx-theme-stack';
132
+
133
+ @Component({
134
+ selector: 'app-theme-toggle',
135
+ standalone: true,
136
+ template: `
137
+ <button (click)="toggle.toggle()">
138
+ Switch to {{ toggle.isDark() ? 'Light' : 'Dark' }}
139
+ </button>
140
+ `,
141
+ })
142
+ export class ThemeToggleComponent {
143
+ protected toggle = inject(ThemeToggleService);
144
+ }
145
+ ```
110
146
 
111
- ### CoreThemeService API
147
+ ### 2. Multi-theme Cycle
112
148
 
113
- The foundational service managing the theme state. It exposes pure Angular Signals and a solid minimal API.
149
+ Use `ThemeCycleService` to rotate through all your configured themes in order.
150
+
151
+ ```typescript
152
+ import { inject } from '@angular/core';
153
+ import { ThemeCycleService } from 'ngx-theme-stack';
154
+
155
+ @Component({
156
+ selector: 'app-theme-cycler',
157
+ standalone: true,
158
+ template: `
159
+ <button (click)="theme.cycle()">
160
+ Next theme: {{ theme.upcoming() }}
161
+ </button>
162
+ <p>Theme {{ theme.cycleIndex() + 1 }} of {{ theme.availableThemes.length }}</p>
163
+ `,
164
+ })
165
+ export class ThemeCycleComponent {
166
+ protected theme = inject(ThemeCycleService);
167
+ }
168
+ ```
169
+
170
+ ### 3. Direct Selection (Dropdowns/Lists)
171
+
172
+ Use `ThemeSelectService` to build selection interfaces like dropdowns or radio groups.
173
+
174
+ ```typescript
175
+ import { inject } from '@angular/core';
176
+ import { ThemeSelectService } from 'ngx-theme-stack';
177
+ import { FormsModule } from '@angular/forms';
178
+
179
+ @Component({
180
+ selector: 'app-theme-selector',
181
+ standalone: true,
182
+ imports: [FormsModule],
183
+ template: `
184
+ <select [ngModel]="theme.selectedTheme()" (ngModelChange)="theme.select($event)">
185
+ @for (t of theme.availableThemes; track t) {
186
+ <option [value]="t">{{ t }}</option>
187
+ }
188
+ </select>
189
+ `,
190
+ })
191
+ export class ThemeSelectComponent {
192
+ protected theme = inject(ThemeSelectService);
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ### 🛡️ Advanced: CoreThemeService API
199
+
200
+ If you need to build custom logic or want full control over the state, use the foundational `CoreThemeService`.
114
201
 
115
202
  ```typescript
116
203
  import { inject } from '@angular/core';
117
204
  import { CoreThemeService } from 'ngx-theme-stack';
118
205
 
119
206
  @Component({ ... })
120
- export class MyComponent {
207
+ export class MyAdvancedComponent {
121
208
  themeService = inject(CoreThemeService);
122
209
 
123
210
  /* --- 📊 Reactive Signals --- */
@@ -145,36 +232,15 @@ export class MyComponent {
145
232
  }
146
233
  ```
147
234
 
148
- ### Utility Services Examples
149
-
150
- #### ThemeToggleService usage:
151
-
152
- ```typescript
153
- import { ThemeToggleService } from 'ngx-theme-stack';
154
-
155
- @Component({
156
- selector: 'app-theme-toggle',
157
- template: `
158
- <button (click)="toggle.toggle()">Switch to {{ toggle.isDark() ? 'Light' : 'Dark' }}</button>
159
- `,
160
- })
161
- export class ThemeToggleComponent {
162
- protected toggle = inject(ThemeToggleService);
163
- }
164
- ```
165
-
166
235
  ## 🎨 Styling
167
236
 
168
- The `ng add` command automatically creates a **`src/themes.css`** file in your project. This is where you should define your theme-specific CSS variables.
169
-
170
- The library targets the `<html>` element. Based on your configured `mode`, you should define your variables like this:
237
+ The `ng add` command automatically creates a **`src/themes.css`** file. This is where you should define your theme-specific CSS variables.
171
238
 
172
239
  ```css
173
240
  /* src/themes.css */
174
241
 
175
242
  /* Using Classes (Default Mode) */
176
- :root,
177
- .light {
243
+ :root, .light {
178
244
  --bg-color: #ffffff;
179
245
  --text-color: #333333;
180
246
  }
@@ -188,32 +254,14 @@ The library targets the `<html>` element. Based on your configured `mode`, you s
188
254
  --bg-color: #ff5f6d;
189
255
  --text-color: #ffffff;
190
256
  }
191
-
192
- /* Using Attributes (Attribute Mode) */
193
- [data-theme='light'] {
194
- --bg-color: #ffffff;
195
- --text-color: #333333;
196
- }
197
-
198
- [data-theme='dark'] {
199
- --bg-color: #121212;
200
- --text-color: #ffffff;
201
- }
202
-
203
- [data-theme='sunset'] {
204
- --bg-color: #ff5f6d;
205
- --text-color: #ffffff;
206
- }
207
257
  ```
208
258
 
209
259
  ## 🌪️ Tailwind CSS v4 Integration
210
260
 
211
- If you are using **Tailwind CSS v4**, you can achieve a much cleaner HTML by mapping your `themes.css` variables to your Tailwind theme. This avoids cluttering your components with `dark:` variants.
261
+ If you are using **Tailwind CSS v4**, you can achieve a much cleaner HTML by mapping your `themes.css` variables to your Tailwind theme.
212
262
 
213
263
  ### 1. Configure Custom Variants
214
264
 
215
- In your main `styles.css`, define how Tailwind should detect your themes:
216
-
217
265
  ```css
218
266
  /* src/styles.css */
219
267
  @import 'tailwindcss';
@@ -227,19 +275,16 @@ In your main `styles.css`, define how Tailwind should detect your themes:
227
275
 
228
276
  ### 2. Map Semantic Variables
229
277
 
230
- Extend your Tailwind theme using the variables defined in `themes.css`:
231
-
232
278
  ```css
233
279
  @theme {
234
280
  --color-main-bg: var(--bg-color);
235
281
  --color-main-text: var(--text-color);
236
- --color-card-bg: var(--card-bg);
237
282
  }
238
283
  ```
239
284
 
240
285
  ### 3. Usage in Components
241
286
 
242
- Now, instead of writing `<div class="bg-white dark:bg-black">`, you simply write:
287
+ Now you can write clean, theme-aware classes:
243
288
 
244
289
  ```html
245
290
  <div class="bg-main-bg text-main-text shadow-xl">
@@ -247,16 +292,14 @@ Now, instead of writing `<div class="bg-white dark:bg-black">`, you simply write
247
292
  </div>
248
293
  ```
249
294
 
250
- This approach keeps your UI code clean, semantic, and fully synchronized with `ngx-theme-stack`.
251
-
252
295
  ## ⚡ Performance Strategies
253
296
 
254
- `ngx-theme-stack` offers two ways to handle the initial theme application to prevent that annoying white flash:
297
+ `ngx-theme-stack` offers two ways to handle the initial theme application to prevent white flashes:
255
298
 
256
- 1. **Critters (Default)**: Best for SSR/Static sites. It uses hidden markers to trick the Angular builder into inlining all your theme CSS variables directly in the HTML `<head>`. Result: **Zero network requests for CSS variables.**
257
- 2. **Blocking**: Best for standard SPAs. It loads the `themes.css` file as a traditional blocking resource.
299
+ 1. **Critters (Default)**: Best for SSR/Static sites. Inlines CSS variables directly in the HTML `<head>`. Result: **Zero network requests for theme variables.**
300
+ 2. **Blocking**: Best for standard SPAs. Loads `themes.css` as a traditional blocking resource.
258
301
 
259
- The `ng-add` schematic helps you configure the right one automatically. You can always use the **Sync Command** to refresh your `index.html` if you change your configuration:
302
+ Use the **Sync Command** to refresh your `index.html` if you change your configuration:
260
303
 
261
304
  ```bash
262
305
  ng generate ngx-theme-stack:sync --project YOUR_PROJECT_NAME
@@ -56,7 +56,7 @@ const DEFAULT_THEMES = ['system', 'light', 'dark'];
56
56
  */
57
57
  const DEFAULT_NG_CONFIG = {
58
58
  defaultTheme: 'system',
59
- storageKey: 'ngx-theme-stack-theme',
59
+ storageKey: 'ngx-theme-stack',
60
60
  mode: 'class',
61
61
  strategy: 'critters',
62
62
  themes: [...DEFAULT_THEMES],
@@ -76,7 +76,7 @@ const NGX_THEME_STACK_CONFIG = new InjectionToken('NGX_THEME_STACK_CONFIG', {
76
76
  * **Defaults:**
77
77
  * - `themes`: `['light', 'dark', 'system']`
78
78
  * - `defaultTheme`: `'system'`
79
- * - `storageKey`: `'ngx-theme-stack-theme'`
79
+ * - `storageKey`: `'ngx-theme-stack'`
80
80
  * - `mode`: `'class'`
81
81
  * - `strategy`: `'critters'`
82
82
 
@@ -355,11 +355,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
355
355
  class ThemeCycleService {
356
356
  #core = inject(CoreThemeService);
357
357
  /** List of all configured themes for cycling. Defaults to `['light', 'dark', 'system']`. */
358
- #cycle = this.#core.availableThemes;
358
+ availableThemes = this.#core.availableThemes;
359
359
  /** The theme explicitly selected by the user. May be `'system'`. */
360
360
  selectedTheme = this.#core.selectedTheme;
361
361
  /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */
362
362
  resolvedTheme = this.#core.resolvedTheme;
363
+ /** Index of the currently selected theme in the cycle. */
364
+ cycleIndex = computed(() => {
365
+ return this.availableThemes.indexOf(this.selectedTheme());
366
+ }, ...(ngDevMode ? [{ debugName: "cycleIndex" }] : /* istanbul ignore next */ []));
367
+ /** The theme that comes before the currently selected theme in the cycle. */
368
+ preceding = computed(() => {
369
+ const index = this.cycleIndex();
370
+ const len = this.availableThemes.length;
371
+ return this.availableThemes[(index - 1 + len) % len];
372
+ }, ...(ngDevMode ? [{ debugName: "preceding" }] : /* istanbul ignore next */ []));
373
+ /** The theme that comes after the currently selected theme in the cycle. */
374
+ upcoming = computed(() => {
375
+ const index = this.cycleIndex();
376
+ return this.availableThemes[(index + 1) % this.availableThemes.length];
377
+ }, ...(ngDevMode ? [{ debugName: "upcoming" }] : /* istanbul ignore next */ []));
363
378
  /** Whether the currently applied theme is `'dark'`. */
364
379
  isDark = this.#core.isDark;
365
380
  /** Whether the currently applied theme is `'light'`. */
@@ -380,10 +395,7 @@ class ThemeCycleService {
380
395
  * the cycle restarts from the first theme.
381
396
  */
382
397
  cycle() {
383
- const current = this.#core.selectedTheme();
384
- const index = this.#cycle.indexOf(current);
385
- const next = this.#cycle[(index + 1) % this.#cycle.length];
386
- this.#core.setTheme(next);
398
+ this.#core.setTheme(this.upcoming());
387
399
  }
388
400
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
389
401
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, providedIn: 'root' });
@@ -1 +1 @@
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\n/**\n * Theme application mode.\n * - `'attribute'`: sets `data-theme` attribute on `<html>`\n * - `'class'`: adds theme class to `<html>`\n * - `'both'`: uses both attribute and class\n */\nexport type NgMode = 'attribute' | 'class' | 'both';\n\n/**\n * Theme application strategy.\n * - `'blocking'`: theme CSS is loaded synchronously before rendering\n * - `'critters'`: theme CSS is inlined using Critters for SSR/SSG\n */\nexport type NgStrategy = 'blocking' | 'critters';\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 /** The theme to use on first visit or when no preference is saved. Default: 'system'. */\n defaultTheme: NgTheme<T>;\n\n /** Key used to persist theme preference in localStorage. Default: 'ngx-theme-stack-theme'. */\n storageKey: string;\n\n /** \n * How the theme should be applied to the document (via class, attribute or both). \n * Default: 'class'.\n */\n mode: NgMode;\n\n /** \n * Performance strategy for anti-flash. \n * Use 'critters' for SSG/SSR builds with inlined CSS, 'blocking' for standard CSS files.\n * Default: 'critters'.\n */\n strategy: NgStrategy;\n\n /** List of supported theme identifiers. Default: ['light', 'dark', 'system']. */\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 strategy: 'critters',\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 * **Defaults:**\n * - `themes`: `['light', 'dark', 'system']`\n * - `defaultTheme`: `'system'`\n * - `storageKey`: `'ngx-theme-stack-theme'`\n * - `mode`: `'class'`\n * - `strategy`: `'critters'`\n\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 * // SSR/SSG Optimization — uses Critters inlining strategy\n * provideThemeStack({\n * strategy: 'critters',\n * mode: 'class',\n * })\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: 'attribute',\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 /**\n * The anti-flash class to remove from the host element.\n * Internal mechanism to bridge the gap between the blocking script's\n * initial DOM state and Angular's first effect run.\n */\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 before the initial theme\n * is resolved from `localStorage`. Becomes `true` immediately after the\n * first browser render pass.\n *\n * **Important:** Guard template elements that display `selectedTheme()` or\n * `resolvedTheme()` behind this signal to prevent hydration-mismatch flashes\n * (e.g. if the server renders the default 'system' but the user has 'dark' stored).\n *\n * @example\n * ```html\n * {{ theme.isHydrated() ? theme.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 currently 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 user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\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 currently 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 user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\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 currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\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 user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\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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"}
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\n/**\n * Theme application mode.\n * - `'attribute'`: sets `data-theme` attribute on `<html>`\n * - `'class'`: adds theme class to `<html>`\n * - `'both'`: uses both attribute and class\n */\nexport type NgMode = 'attribute' | 'class' | 'both';\n\n/**\n * Theme application strategy.\n * - `'blocking'`: theme CSS is loaded synchronously before rendering\n * - `'critters'`: theme CSS is inlined using Critters for SSR/SSG\n */\nexport type NgStrategy = 'blocking' | 'critters';\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 /** The theme to use on first visit or when no preference is saved. Default: 'system'. */\n defaultTheme: NgTheme<T>;\n\n /** Key used to persist theme preference in localStorage. Default: 'ngx-theme-stack-theme'. */\n storageKey: string;\n\n /** \n * How the theme should be applied to the document (via class, attribute or both). \n * Default: 'class'.\n */\n mode: NgMode;\n\n /** \n * Performance strategy for anti-flash. \n * Use 'critters' for SSG/SSR builds with inlined CSS, 'blocking' for standard CSS files.\n * Default: 'critters'.\n */\n strategy: NgStrategy;\n\n /** List of supported theme identifiers. Default: ['light', 'dark', 'system']. */\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',\n mode: 'class',\n strategy: 'critters',\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 * **Defaults:**\n * - `themes`: `['light', 'dark', 'system']`\n * - `defaultTheme`: `'system'`\n * - `storageKey`: `'ngx-theme-stack'`\n * - `mode`: `'class'`\n * - `strategy`: `'critters'`\n\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 * // SSR/SSG Optimization — uses Critters inlining strategy\n * provideThemeStack({\n * strategy: 'critters',\n * mode: 'class',\n * })\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: 'attribute',\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 /**\n * The anti-flash class to remove from the host element.\n * Internal mechanism to bridge the gap between the blocking script's\n * initial DOM state and Angular's first effect run.\n */\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 before the initial theme\n * is resolved from `localStorage`. Becomes `true` immediately after the\n * first browser render pass.\n *\n * **Important:** Guard template elements that display `selectedTheme()` or\n * `resolvedTheme()` behind this signal to prevent hydration-mismatch flashes\n * (e.g. if the server renders the default 'system' but the user has 'dark' stored).\n *\n * @example\n * ```html\n * {{ theme.isHydrated() ? theme.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 { computed, 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 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 currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** Index of the currently selected theme in the cycle. */\n readonly cycleIndex = computed(() => {\n return this.availableThemes.indexOf(this.selectedTheme());\n });\n\n /** The theme that comes before the currently selected theme in the cycle. */\n readonly preceding = computed(() => {\n const index = this.cycleIndex();\n const len = this.availableThemes.length;\n return this.availableThemes[(index - 1 + len) % len];\n });\n\n /** The theme that comes after the currently selected theme in the cycle. */\n readonly upcoming = computed(() => {\n const index = this.cycleIndex();\n return this.availableThemes[(index + 1) % this.availableThemes.length];\n });\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 user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and\n * resolved the real persisted theme. Use to prevent hydration flashes.\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 this.#core.setTheme(this.upcoming());\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 currently 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 user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\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 currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\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 user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-theme-stack",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "A stack of themes for Angular applications.",
5
5
  "schematics": "./schematics/collection.json",
6
6
  "ng-add": {
@@ -1,11 +1,9 @@
1
1
  import { SchematicContext, Tree } from '@angular-devkit/schematics';
2
2
  /**
3
- * Attempts to automatically locate and update the application configuration files.
4
- * It searches for standard Angular file paths relative to the project's source root.
5
- *
6
- * @param tree The virtual file tree of the project.
7
- * @param context The schematic's execution context.
8
- * @param sourceRoot The source root for the project (e.g. 'projects/demo-ngx-theme-stack/src').
9
- * @param provideCall The pre-formatted code string for provideThemeStack().
3
+ * Patches the application configuration to include or update the theme stack provider.
4
+ * Uses a hybrid approach:
5
+ * 1. Attempts the official 'addRootProvider' for standard standalone apps.
6
+ * 2. If it fails or if the provider already exists, it uses a smart AST walker
7
+ * to either inject or UPDATE the existing configuration.
10
8
  */
11
- export declare function patchAppConfig(tree: Tree, context: SchematicContext, sourceRoot: string, provideCall: string): void;
9
+ export declare function patchAppConfig(tree: Tree, context: SchematicContext, projectSourceRoot: string, provideCall: string, projectName?: string): Promise<void>;
@@ -1,53 +1,190 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  exports.patchAppConfig = patchAppConfig;
13
+ const utility_1 = require("@schematics/angular/utility");
14
+ const ts = require("typescript");
4
15
  /**
5
- * Attempts to automatically locate and update the application configuration files.
6
- * It searches for standard Angular file paths relative to the project's source root.
7
- *
8
- * @param tree The virtual file tree of the project.
9
- * @param context The schematic's execution context.
10
- * @param sourceRoot The source root for the project (e.g. 'projects/demo-ngx-theme-stack/src').
11
- * @param provideCall The pre-formatted code string for provideThemeStack().
16
+ * Patches the application configuration to include or update the theme stack provider.
17
+ * Uses a hybrid approach:
18
+ * 1. Attempts the official 'addRootProvider' for standard standalone apps.
19
+ * 2. If it fails or if the provider already exists, it uses a smart AST walker
20
+ * to either inject or UPDATE the existing configuration.
12
21
  */
13
- function patchAppConfig(tree, context, sourceRoot, provideCall) {
14
- const candidates = [`${sourceRoot}/app/app.config.ts`, `${sourceRoot}/main.ts`, `${sourceRoot}/app/app.module.ts`].map(p => p.startsWith('/') ? p.slice(1) : p);
15
- for (const filePath of candidates) {
16
- if (!tree.exists(filePath))
17
- continue;
18
- const content = tree.readText(filePath);
19
- let updated = content;
20
- if (updated.includes('provideThemeStack')) {
21
- updated = updated.replace(/provideThemeStack\s*\([\s\S]*?\)/, provideCall);
22
- tree.overwrite(filePath, updated);
22
+ function patchAppConfig(tree, context, projectSourceRoot, provideCall, projectName) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ var _a, _b;
25
+ const mainPath = `${projectSourceRoot}/main.ts`.replace(/^\//, '');
26
+ const appConfigPath = `${projectSourceRoot}/app/app.config.ts`.replace(/^\//, '');
27
+ const startFile = tree.exists(appConfigPath) ? appConfigPath : tree.exists(mainPath) ? mainPath : null;
28
+ if (!startFile) {
29
+ context.logger.warn('⚠ Could not find app.config.ts or main.ts. Please add provideThemeStack() manually.');
23
30
  return;
24
31
  }
25
- // Add import if missing
26
- if (!updated.includes("from 'ngx-theme-stack'")) {
27
- const lastImport = updated.lastIndexOf('import ');
28
- const eol = updated.indexOf('\n', lastImport);
29
- updated =
30
- updated.slice(0, eol + 1) +
31
- `import { provideThemeStack } from 'ngx-theme-stack';\n` +
32
- updated.slice(eol + 1);
33
- }
34
- // Inject into providers array
35
- if (updated.includes('providers:')) {
36
- updated = updated.replace(/providers:\s*\[([\s\S]*?)\]/, (_m, inner) => {
37
- const trimmed = inner.trimEnd();
38
- const sep = trimmed.length > 0 && !trimmed.endsWith(',') ? ',\n ' : '\n ';
39
- return `providers: [${trimmed}${sep}${provideCall}\n ]`;
40
- });
41
- }
42
- else if (updated.includes('bootstrapApplication')) {
43
- updated = updated.replace(/bootstrapApplication\s*\(\s*([^,)]+)\s*\)/, `bootstrapApplication($1, {\n providers: [\n ${provideCall}\n ]\n})`);
44
- }
45
- tree.overwrite(filePath, updated);
46
- return;
32
+ // Check if already present to decide between "Inject" or "Update"
33
+ const content = ((_a = tree.read(startFile)) === null || _a === void 0 ? void 0 : _a.toString()) || '';
34
+ const alreadyHasProvider = content.includes('provideThemeStack');
35
+ // ── Strategy 1: Standard addRootProvider (Only if NOT already present) ─────
36
+ if (projectName && !alreadyHasProvider) {
37
+ try {
38
+ const rule = (0, utility_1.addRootProvider)(projectName, ({ code, external }) => code `${external('provideThemeStack', 'ngx-theme-stack')}(${provideCall.replace(/^provideThemeStack\(/, '').replace(/\)$/, '')})`);
39
+ yield Promise.resolve(rule(tree, context));
40
+ // Re-read content to see if it worked
41
+ const updatedContent = ((_b = tree.read(startFile)) === null || _b === void 0 ? void 0 : _b.toString()) || '';
42
+ if (updatedContent.includes('provideThemeStack'))
43
+ return;
44
+ }
45
+ catch (e) {
46
+ context.logger.debug(`Standard addRootProvider failed: ${String(e)}`);
47
+ }
48
+ }
49
+ // ── Strategy 2: Smart AST Patching / Updating ─────────────────────────────
50
+ // This will handle both "Delegated Providers" and "Updates to existing config"
51
+ if (yield applySmartPatch(tree, context, startFile, provideCall)) {
52
+ return;
53
+ }
54
+ context.logger.warn(`⚠ Could not automatically inject or update provider in ${startFile}.`);
55
+ });
56
+ }
57
+ /**
58
+ * Recursively follows identifiers to find where the providers array is defined
59
+ * or where provideThemeStack is already called.
60
+ */
61
+ function applySmartPatch(tree_1, context_1, filePath_1, provideCall_1) {
62
+ return __awaiter(this, arguments, void 0, function* (tree, context, filePath, provideCall, visitedFiles = new Set()) {
63
+ if (visitedFiles.has(filePath))
64
+ return false;
65
+ visitedFiles.add(filePath);
66
+ const buffer = tree.read(filePath);
67
+ if (!buffer)
68
+ return false;
69
+ const content = buffer.toString();
70
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
71
+ // A. UPDATE: If provideThemeStack is already here, replace it.
72
+ const existingCall = findProvideThemeStackCall(sourceFile);
73
+ if (existingCall) {
74
+ const updated = content.slice(0, existingCall.getStart()) + provideCall + content.slice(existingCall.getEnd());
75
+ tree.overwrite(filePath, updated);
76
+ return true;
77
+ }
78
+ // B. INJECT: Look for a providers array literal: providers: [ ... ]
79
+ const arrayLiteral = findProvidersArrayLiteral(sourceFile);
80
+ if (arrayLiteral) {
81
+ insertIntoArray(tree, filePath, arrayLiteral, provideCall);
82
+ ensureImport(tree, filePath, 'provideThemeStack', 'ngx-theme-stack');
83
+ return true;
84
+ }
85
+ // C. DELEGATION: Look for a delegated provider: providers: SOME_CONSTANT
86
+ const delegatedIdentifier = findProvidersIdentifier(sourceFile);
87
+ if (delegatedIdentifier) {
88
+ const importPath = findImportPathForIdentifier(sourceFile, delegatedIdentifier);
89
+ if (importPath) {
90
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
91
+ let resolvedPath = `${dir}/${importPath}.ts`.replace(/\/\/+/g, '/').replace(/\.ts\.ts$/, '.ts');
92
+ if (!tree.exists(resolvedPath)) {
93
+ resolvedPath = `${dir}/${importPath}/index.ts`.replace(/\/\/+/g, '/');
94
+ }
95
+ if (tree.exists(resolvedPath)) {
96
+ return applySmartPatch(tree, context, resolvedPath, provideCall, visitedFiles);
97
+ }
98
+ }
99
+ else {
100
+ const variableDeclaration = findVariableDeclaration(sourceFile, delegatedIdentifier);
101
+ if (variableDeclaration && variableDeclaration.initializer && ts.isArrayLiteralExpression(variableDeclaration.initializer)) {
102
+ insertIntoArray(tree, filePath, variableDeclaration.initializer, provideCall);
103
+ ensureImport(tree, filePath, 'provideThemeStack', 'ngx-theme-stack');
104
+ return true;
105
+ }
106
+ }
107
+ }
108
+ return false;
109
+ });
110
+ }
111
+ function findProvideThemeStackCall(node) {
112
+ let result = null;
113
+ function visit(n) {
114
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === 'provideThemeStack') {
115
+ result = n;
116
+ }
117
+ if (!result)
118
+ ts.forEachChild(n, visit);
47
119
  }
48
- context.logger.warn(`⚠ Could not find app.config.ts / main.ts / app.module.ts.\n` +
49
- ` Add manually:\n\n` +
50
- ` import { provideThemeStack } from 'ngx-theme-stack';\n\n` +
51
- ` providers: [ ${provideCall} ]`);
120
+ visit(node);
121
+ return result;
122
+ }
123
+ function findProvidersArrayLiteral(node) {
124
+ let result = null;
125
+ function visit(n) {
126
+ if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === 'providers') {
127
+ if (ts.isArrayLiteralExpression(n.initializer)) {
128
+ result = n.initializer;
129
+ }
130
+ }
131
+ if (!result)
132
+ ts.forEachChild(n, visit);
133
+ }
134
+ visit(node);
135
+ return result;
136
+ }
137
+ function findProvidersIdentifier(node) {
138
+ let result = null;
139
+ function visit(n) {
140
+ if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === 'providers') {
141
+ if (ts.isIdentifier(n.initializer)) {
142
+ result = n.initializer.text;
143
+ }
144
+ }
145
+ if (!result)
146
+ ts.forEachChild(n, visit);
147
+ }
148
+ visit(node);
149
+ return result;
150
+ }
151
+ function findImportPathForIdentifier(sourceFile, identifier) {
152
+ var _a;
153
+ for (const statement of sourceFile.statements) {
154
+ if (ts.isImportDeclaration(statement) && ((_a = statement.importClause) === null || _a === void 0 ? void 0 : _a.namedBindings) && ts.isNamedImports(statement.importClause.namedBindings)) {
155
+ if (statement.importClause.namedBindings.elements.some(e => e.name.text === identifier)) {
156
+ return statement.moduleSpecifier.text;
157
+ }
158
+ }
159
+ }
160
+ return null;
161
+ }
162
+ function findVariableDeclaration(sourceFile, identifier) {
163
+ for (const statement of sourceFile.statements) {
164
+ if (ts.isVariableStatement(statement)) {
165
+ for (const decl of statement.declarationList.declarations) {
166
+ if (ts.isIdentifier(decl.name) && decl.name.text === identifier) {
167
+ return decl;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ return null;
173
+ }
174
+ function insertIntoArray(tree, filePath, array, text) {
175
+ const content = tree.read(filePath).toString();
176
+ const insertionPos = array.elements.length > 0
177
+ ? array.elements[array.elements.length - 1].getEnd()
178
+ : array.getStart() + 1;
179
+ const prefix = array.elements.length > 0 ? ', ' : '';
180
+ const updated = content.slice(0, insertionPos) + prefix + text + content.slice(insertionPos);
181
+ tree.overwrite(filePath, updated);
182
+ }
183
+ function ensureImport(tree, filePath, symbol, module) {
184
+ const content = tree.read(filePath).toString();
185
+ if (content.includes(symbol))
186
+ return;
187
+ const updated = `import { ${symbol} } from '${module}';\n` + content;
188
+ tree.overwrite(filePath, updated);
52
189
  }
53
190
  //# sourceMappingURL=app-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/app-config.ts"],"names":[],"mappings":";;AAWA,wCA0DC;AAnED;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAC5B,IAAU,EACV,OAAyB,EACzB,UAAkB,EAClB,WAAmB;IAEnB,MAAM,UAAU,GAAG,CAAC,GAAG,UAAU,oBAAoB,EAAE,GAAG,UAAU,UAAU,EAAE,GAAG,UAAU,oBAAoB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhK,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,SAAS;QAErC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAExC,IAAI,OAAO,GAAG,OAAO,CAAC;QAEtB,IAAI,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC1C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,kCAAkC,EAAE,WAAW,CAAC,CAAC;YAC3E,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAClC,OAAO;QACT,CAAC;QAED,wBAAwB;QACxB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;YAChD,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAC9C,OAAO;gBACL,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC;oBACzB,wDAAwD;oBACxD,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAC3B,CAAC;QAED,8BAA8B;QAC9B,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACnC,OAAO,GAAG,OAAO,CAAC,OAAO,CACvB,6BAA6B,EAC7B,CAAC,EAAU,EAAE,KAAa,EAAE,EAAE;gBAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;gBAChC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAChF,OAAO,eAAe,OAAO,GAAG,GAAG,GAAG,WAAW,OAAO,CAAC;YAC3D,CAAC,CACF,CAAC;QACJ,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;YACpD,OAAO,GAAG,OAAO,CAAC,OAAO,CACvB,2CAA2C,EAC3C,mDAAmD,WAAW,WAAW,CAC1E,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,IAAI,CACjB,6DAA6D;QAC3D,qBAAqB;QACrB,4DAA4D;QAC5D,kBAAkB,WAAW,IAAI,CACpC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/app-config.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@
11
11
  export declare const DEFAULT_THEMES: readonly ["system", "light", "dark"];
12
12
  export declare const DEFAULTS: {
13
13
  readonly defaultTheme: "system";
14
- readonly storageKey: "ngx-theme-stack-theme";
14
+ readonly storageKey: "ngx-theme-stack";
15
15
  readonly mode: "class";
16
16
  readonly strategy: "critters";
17
17
  readonly themes: readonly ["system", "light", "dark"];
@@ -14,7 +14,7 @@ exports.DEFAULTS = exports.DEFAULT_THEMES = void 0;
14
14
  exports.DEFAULT_THEMES = ['system', 'light', 'dark'];
15
15
  exports.DEFAULTS = {
16
16
  defaultTheme: 'system',
17
- storageKey: 'ngx-theme-stack-theme',
17
+ storageKey: 'ngx-theme-stack',
18
18
  mode: 'class',
19
19
  strategy: 'critters',
20
20
  themes: [...exports.DEFAULT_THEMES],
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/constants.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEtD,QAAA,QAAQ,GAAG;IACtB,YAAY,EAAE,QAAQ;IACtB,UAAU,EAAE,uBAAuB;IACnC,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,CAAC,GAAG,sBAAc,CAAC;CACnB,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/constants.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEtD,QAAA,QAAQ,GAAG;IACtB,YAAY,EAAE,QAAQ;IACtB,UAAU,EAAE,iBAAiB;IAC7B,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,CAAC,GAAG,sBAAc,CAAC;CACnB,CAAC"}
@@ -2,10 +2,5 @@ import { Rule } from '@angular-devkit/schematics';
2
2
  import { Schema } from './schema';
3
3
  /**
4
4
  * Main schematic factory for the 'ng-add' command.
5
- * Depending on the 'mode' option, it either applies default library values ("quick")
6
- * or starts an interactive session to collect personalized configuration ("custom").
7
- *
8
- * @param options The schema options provided by the Angular CLI.
9
- * @returns A rule that modifies the project's setup.
10
5
  */
11
6
  export declare function ngAdd(options: Schema): Rule;
@@ -16,11 +16,7 @@ const app_config_1 = require("./app-config");
16
16
  const constants_1 = require("./constants");
17
17
  const utils_1 = require("./utils");
18
18
  /**
19
- * Interactively prompts the user for custom configuration options using readline.
20
- * It allows defining additional themes, selecting a default theme from the expanded list,
21
- * providing a custom localStorage key, and choosing a CSS application mode.
22
- *
23
- * @returns A promise resolving to the user's selected configuration options.
19
+ * Interactively prompts the user for custom configuration options.
24
20
  */
25
21
  function collectCustomOptions() {
26
22
  return __awaiter(this, void 0, void 0, function* () {
@@ -29,25 +25,22 @@ function collectCustomOptions() {
29
25
  process.stdout.write('\n');
30
26
  const rawThemes = yield (0, utils_1.ask)(rl, ' Custom themes (comma-separated, Enter to skip): ');
31
27
  const customThemes = rawThemes
32
- ? rawThemes
33
- .split(',')
34
- .map((t) => t.trim())
35
- .filter(Boolean)
28
+ ? rawThemes.split(',').map((t) => t.trim()).filter(Boolean)
36
29
  : [];
37
- const allThemes = [...constants_1.DEFAULT_THEMES, ...customThemes];
38
- const defaultTheme = yield (0, utils_1.askList)(rl, 'Default theme:', allThemes, 0);
30
+ const themes = [...constants_1.DEFAULT_THEMES, ...customThemes];
31
+ const defaultTheme = yield (0, utils_1.askList)(rl, 'Default theme:', themes, 0);
39
32
  const rawKey = yield (0, utils_1.ask)(rl, ` localStorage key [${constants_1.DEFAULTS.storageKey}]: `);
40
33
  const storageKey = rawKey || constants_1.DEFAULTS.storageKey;
41
34
  const MODES = ['class', 'attribute', 'both'];
42
35
  const mode = yield (0, utils_1.askList)(rl, 'How to apply theme on <html>:', MODES, 0);
43
36
  const STRATEGIES = ['critters', 'blocking'];
44
- process.stdout.write('\n');
45
- process.stdout.write(' Anti-flash strategy:\n');
37
+ process.stdout.write('\n Anti-flash strategy:\n');
46
38
  process.stdout.write(' - critters: Zero network requests (inlines CSS in <head>)\n');
47
39
  process.stdout.write(' - blocking: Standard CSS loading (themes.css)\n');
48
40
  const strategy = (yield (0, utils_1.askList)(rl, 'Choose strategy:', STRATEGIES, 0));
41
+ const provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy);
49
42
  process.stdout.write('\n');
50
- return { defaultTheme, storageKey, mode, themes: allThemes, strategy };
43
+ return { defaultTheme, storageKey, mode, themes, strategy, provideCall };
51
44
  }
52
45
  finally {
53
46
  rl.close();
@@ -56,65 +49,51 @@ function collectCustomOptions() {
56
49
  }
57
50
  /**
58
51
  * Main schematic factory for the 'ng-add' command.
59
- * Depending on the 'mode' option, it either applies default library values ("quick")
60
- * or starts an interactive session to collect personalized configuration ("custom").
61
- *
62
- * @param options The schema options provided by the Angular CLI.
63
- * @returns A rule that modifies the project's setup.
64
52
  */
65
53
  function ngAdd(options) {
66
54
  return (tree, context) => __awaiter(this, void 0, void 0, function* () {
67
- // ── Workspace Resolution ──────────────────────────────────────────────────
68
- // In a monorepo, we must find the project's actual root and sourceRoot.
69
55
  const workspaceConfig = tree.read('/angular.json');
70
56
  if (!workspaceConfig) {
71
57
  throw new Error('Could not find angular.json. Are you in an Angular workspace?');
72
58
  }
73
59
  const workspace = JSON.parse(workspaceConfig.toString());
74
- const projectName = options.project || workspace.defaultProject;
60
+ const projectName = options.project || workspace.defaultProject || Object.keys(workspace.projects)[0];
75
61
  const project = workspace.projects[projectName];
76
62
  if (!project) {
77
63
  throw new Error(`Project "${projectName}" not found in angular.json.`);
78
64
  }
79
65
  const projectRoot = project.root || '';
80
66
  const projectSourceRoot = project.sourceRoot || `${projectRoot}/src`;
81
- context.logger.info('');
82
- context.logger.info(`🎨 ngx-theme-stack — setup [project: ${projectName}]`);
83
- context.logger.info('');
84
- let provideCall;
85
- let scriptOptions;
86
- let finalThemes;
87
- let themesToScaffold;
88
- let finalStrategy;
89
- const changeset = [];
67
+ context.logger.info('\n🎨 ngx-theme-stack — setup');
68
+ context.logger.info(` project: ${projectName}\n`);
69
+ let config;
90
70
  if (options.mode === 'quick') {
91
71
  const themes = [...constants_1.DEFAULT_THEMES];
92
- const { defaultTheme, storageKey, mode } = constants_1.DEFAULTS;
93
- const strategy = 'critters'; // default for quick
94
- provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy);
95
- scriptOptions = { storageKey, defaultTheme, mode };
96
- finalThemes = themes;
97
- themesToScaffold = themes.filter((t) => t !== 'system');
98
- finalStrategy = strategy;
99
- context.logger.info('⚡ Quick setup providing explicit defaults (strategy: critters).');
72
+ const { defaultTheme, storageKey, mode, strategy } = constants_1.DEFAULTS;
73
+ config = {
74
+ defaultTheme,
75
+ storageKey,
76
+ mode,
77
+ themes,
78
+ strategy: strategy,
79
+ provideCall: (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy),
80
+ };
81
+ context.logger.info('⚡ Quick setup — providing explicit defaults.');
100
82
  }
101
83
  else {
102
84
  context.logger.info('🛠 Custom setup — answer the prompts below:');
103
- const opts = yield collectCustomOptions();
104
- const { defaultTheme, storageKey, mode, themes, strategy } = opts;
105
- provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy);
106
- scriptOptions = { storageKey, defaultTheme, mode };
107
- finalThemes = themes;
108
- themesToScaffold = themes.filter((t) => t !== 'system');
109
- finalStrategy = strategy;
85
+ config = yield collectCustomOptions();
110
86
  }
87
+ const changeset = [];
88
+ const themesToScaffold = config.themes.filter((t) => t !== 'system');
111
89
  return (0, schematics_1.chain)([
90
+ // 1. Scaffold themes.css
112
91
  (t) => {
113
- const themesPath = `${projectSourceRoot}/themes.css`;
92
+ const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
114
93
  if (!t.exists(themesPath)) {
115
94
  let content = '/* ngx-theme-stack tokens */\n\n';
116
95
  themesToScaffold.forEach((theme) => {
117
- const selector = scriptOptions.mode === 'attribute' ? `[data-theme="${theme}"]` : `.${theme}`;
96
+ const selector = config.mode === 'attribute' ? `[data-theme="${theme}"]` : `.${theme}`;
118
97
  if (theme === 'light') {
119
98
  content += `:root, ${selector} {\n /* Add your light theme variables here */\n}\n\n`;
120
99
  }
@@ -123,12 +102,13 @@ function ngAdd(options) {
123
102
  }
124
103
  });
125
104
  t.create(themesPath, content);
126
- changeset.push(` \u001b[36mA\u001b[0m ${themesPath.replace(/^\//, '')} (theme tokens)`);
105
+ changeset.push(` \u001b[36mA\u001b[0m ${themesPath} (theme tokens)`);
127
106
  }
128
107
  else {
129
- changeset.push(` \u001b[90mℹ\u001b[0m ${themesPath.replace(/^\//, '')} (already exists)`);
108
+ changeset.push(` \u001b[90mℹ\u001b[0m ${themesPath} (already exists)`);
130
109
  }
131
110
  },
111
+ // 2. Add prebuild script to package.json
132
112
  (t) => {
133
113
  const pkgPath = '/package.json';
134
114
  const buffer = t.read(pkgPath);
@@ -140,46 +120,43 @@ function ngAdd(options) {
140
120
  changeset.push(' \u001b[33mM\u001b[0m package.json (prebuild script)');
141
121
  }
142
122
  },
123
+ // 3. Update angular.json (styles & optimization)
143
124
  (t) => {
144
125
  var _a;
145
- const workspaceConfig = t.read('/angular.json');
146
- if (workspaceConfig) {
147
- const workspace = JSON.parse(workspaceConfig.toString());
148
- const project = workspace.projects[projectName];
149
- const target = project.architect.build.options;
150
- const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
151
- // Add themes.css to styles if not already there
152
- if (target.styles && !target.styles.includes(themesPath)) {
153
- target.styles.unshift(themesPath);
126
+ const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
127
+ const target = project.architect.build.options;
128
+ if (target.styles && !target.styles.includes(themesPath)) {
129
+ target.styles.unshift(themesPath);
130
+ }
131
+ const prodConfig = (_a = project.architect.build.configurations) === null || _a === void 0 ? void 0 : _a.production;
132
+ if (prodConfig && config.strategy === 'blocking') {
133
+ if (typeof prodConfig.optimization === 'object') {
134
+ prodConfig.optimization.styles = prodConfig.optimization.styles || {};
135
+ prodConfig.optimization.styles.inlineCritical = false;
154
136
  }
155
- // Handle inlineCritical optimization for the blocking strategy
156
- const prodConfig = (_a = project.architect.build.configurations) === null || _a === void 0 ? void 0 : _a.production;
157
- if (prodConfig && finalStrategy === 'blocking') {
158
- if (typeof prodConfig.optimization === 'object') {
159
- prodConfig.optimization.styles = prodConfig.optimization.styles || {};
160
- prodConfig.optimization.styles.inlineCritical = false;
161
- }
162
- else {
163
- prodConfig.optimization = {
164
- styles: { inlineCritical: false },
165
- };
166
- }
137
+ else {
138
+ prodConfig.optimization = { styles: { inlineCritical: false } };
167
139
  }
168
- t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
169
- changeset.push(' \u001b[33mM\u001b[0m angular.json (styles & optimization)');
170
140
  }
141
+ t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
142
+ changeset.push(' \u001b[33mM\u001b[0m angular.json (styles & optimization)');
171
143
  },
172
- (t, ctx) => {
173
- (0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, provideCall);
144
+ // 4. Patch App Config and Index HTML
145
+ (t, ctx) => __awaiter(this, void 0, void 0, function* () {
146
+ yield (0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, config.provideCall, projectName);
174
147
  changeset.push(' \u001b[33mM\u001b[0m app.config.ts (provided theme stack)');
175
- (0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, Object.assign(Object.assign({}, scriptOptions), { themes: finalThemes, strategy: options.strategy || finalStrategy }));
148
+ (0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, {
149
+ storageKey: config.storageKey,
150
+ defaultTheme: config.defaultTheme,
151
+ mode: config.mode,
152
+ themes: config.themes,
153
+ strategy: config.strategy,
154
+ });
176
155
  changeset.push(' \u001b[33mM\u001b[0m index.html (injected anti-flash)');
177
- ctx.logger.info('\u001b[1mChangeset:\u001b[0m');
156
+ ctx.logger.info('\n\u001b[1mChangeset:\u001b[0m');
178
157
  changeset.forEach((entry) => ctx.logger.info(entry));
179
- ctx.logger.info('');
180
- ctx.logger.info('\u001b[1m\u001b[32m🏁 Done.\u001b[0m');
181
- ctx.logger.info('');
182
- },
158
+ ctx.logger.info('\n\u001b[1m\u001b[32m🏁 Done.\u001b[0m\n');
159
+ }),
183
160
  ]);
184
161
  });
185
162
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
@@ -169,7 +169,7 @@ declare const NGX_THEME_STACK_CONFIG: InjectionToken<NgConfig<string>>;
169
169
  * **Defaults:**
170
170
  * - `themes`: `['light', 'dark', 'system']`
171
171
  * - `defaultTheme`: `'system'`
172
- * - `storageKey`: `'ngx-theme-stack-theme'`
172
+ * - `storageKey`: `'ngx-theme-stack'`
173
173
  * - `mode`: `'class'`
174
174
  * - `strategy`: `'critters'`
175
175
 
@@ -228,10 +228,18 @@ declare function provideThemeStack<const T extends string = DefaultNgTheme>(conf
228
228
  */
229
229
  declare class ThemeCycleService {
230
230
  #private;
231
+ /** List of all configured themes for cycling. Defaults to `['light', 'dark', 'system']`. */
232
+ readonly availableThemes: string[];
231
233
  /** The theme explicitly selected by the user. May be `'system'`. */
232
234
  readonly selectedTheme: _angular_core.Signal<ngx_theme_stack.NgTheme>;
233
235
  /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */
234
236
  readonly resolvedTheme: _angular_core.Signal<(string & {}) | ngx_theme_stack.NgSystemTheme>;
237
+ /** Index of the currently selected theme in the cycle. */
238
+ readonly cycleIndex: _angular_core.Signal<number>;
239
+ /** The theme that comes before the currently selected theme in the cycle. */
240
+ readonly preceding: _angular_core.Signal<string>;
241
+ /** The theme that comes after the currently selected theme in the cycle. */
242
+ readonly upcoming: _angular_core.Signal<string>;
235
243
  /** Whether the currently applied theme is `'dark'`. */
236
244
  readonly isDark: _angular_core.Signal<boolean>;
237
245
  /** Whether the currently applied theme is `'light'`. */