ngx-theme-stack 3.5.2 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -105,6 +105,7 @@ The installation command automates the following:
105
105
  | `package.json` | Adds a `"prebuild"` script for theme synchronization |
106
106
  | `angular.json` | Registers `themes.css` and optimizes build config |
107
107
  | `themes.css` | Scaffolds base theme tokens if they don't exist |
108
+ | `SKILL.md` | Generates an AI Agent Skill under `.agent/skills/` (optional) |
108
109
 
109
110
  > [!TIP]
110
111
  > **Re-configuration support:** Run `ng add` multiple times freely. The schematic updates existing code without duplicating it.
@@ -181,13 +182,18 @@ import { ThemeToggleService } from 'ngx-theme-stack';
181
182
 
182
183
  @Component({
183
184
  selector: 'app-theme-toggle',
184
- standalone: true,
185
185
  template: `
186
- <button (click)="toggle.toggle()">Switch to {{ toggle.isDark() ? 'Light' : 'Dark' }}</button>
186
+ @if (theme.isHydrated()) {
187
+ <button (click)="theme.toggle()">
188
+ {{ theme.isDark() ? '🌙' : '☀️' }}
189
+ </button>
190
+ } @else {
191
+ <div class="theme-toggle-skeleton"></div>
192
+ }
187
193
  `,
188
194
  })
189
- export class ThemeToggleComponent {
190
- protected toggle = inject(ThemeToggleService);
195
+ export class ThemeToggle {
196
+ protected readonly theme = inject(ThemeToggleService);
191
197
  }
192
198
  ```
193
199
 
@@ -198,15 +204,19 @@ import { inject } from '@angular/core';
198
204
  import { ThemeCycleService } from 'ngx-theme-stack';
199
205
 
200
206
  @Component({
201
- selector: 'app-theme-cycler',
202
- standalone: true,
207
+ selector: 'app-theme-cycle',
203
208
  template: `
204
- <button (click)="theme.cycle()">Next: {{ theme.upcoming() }}</button>
205
- <p>Theme {{ theme.cycleIndex() + 1 }} of {{ theme.availableThemes.length }}</p>
209
+ @if (theme.isHydrated()) {
210
+ <button (click)="theme.cycle()">
211
+ 🔄 Cycle Theme
212
+ </button>
213
+ } @else {
214
+ <div class="theme-cycle-skeleton"></div>
215
+ }
206
216
  `,
207
217
  })
208
- export class ThemeCycleComponent {
209
- protected theme = inject(ThemeCycleService);
218
+ export class ThemeCycle {
219
+ protected readonly theme = inject(ThemeCycleService);
210
220
  }
211
221
  ```
212
222
 
@@ -215,22 +225,30 @@ export class ThemeCycleComponent {
215
225
  ```typescript
216
226
  import { inject } from '@angular/core';
217
227
  import { ThemeSelectService } from 'ngx-theme-stack';
218
- import { FormsModule } from '@angular/forms';
219
228
 
220
229
  @Component({
221
- selector: 'app-theme-selector',
222
- standalone: true,
223
- imports: [FormsModule],
230
+ selector: 'app-theme-select',
224
231
  template: `
225
- <select [ngModel]="theme.selectedTheme()" (ngModelChange)="theme.select($event)">
226
- @for (t of theme.availableThemes; track t) {
227
- <option [value]="t">{{ t }}</option>
228
- }
229
- </select>
232
+ @if (theme.isHydrated()) {
233
+ <select name="select-theme" (change)="onThemeChange($event)">
234
+ @for (t of theme.availableThemes; track t) {
235
+ <option [value]="t" [selected]="theme.selectedTheme() === t">
236
+ {{ t }}
237
+ </option>
238
+ }
239
+ </select>
240
+ } @else {
241
+ <div class="theme-select-skeleton"></div>
242
+ }
230
243
  `,
231
244
  })
232
- export class ThemeSelectComponent {
233
- protected theme = inject(ThemeSelectService);
245
+ export class ThemeSelect {
246
+ protected readonly theme = inject(ThemeSelectService);
247
+
248
+ onThemeChange(event: Event) {
249
+ const value = (event.target as HTMLSelectElement).value;
250
+ this.theme.select(value);
251
+ }
234
252
  }
235
253
  ```
236
254
 
@@ -345,6 +363,23 @@ Only needed if you want `dark:` utilities tied to ngx-theme-stack's toggle:
345
363
 
346
364
  </details>
347
365
 
366
+ ---
367
+ ## 🤖 AI Code Assistants Integration
368
+
369
+ `ngx-theme-stack` includes out-of-the-box support for AI coding assistants (such as Google Antigravity, Gemini, Claude Code, and other agents that support the open `SKILL.md` standard).
370
+
371
+ The AI Agent Skill tells coding assistants exactly how to implement theme toggles, cycles, and dropdowns, use Tailwind CSS v4 variables correctly, and handle SSR hydration protection to avoid layout flashes.
372
+
373
+ ### Setup
374
+
375
+ 1. **Automatic:** During `ng add`, you are prompted to generate the skill file. Selecting **Yes** automatically creates `.agent/skills/ngx-theme-stack/SKILL.md` in your project root.
376
+ 2. **Manual:** If you did not generate it during installation, or deleted it, you can create/re-create the skill by running:
377
+ ```bash
378
+ ng generate ngx-theme-stack:skill
379
+ ```
380
+
381
+ Once the skill is in your workspace, your AI assistant will automatically read it and generate bug-free theme management code on the first try!
382
+
348
383
  ---
349
384
 
350
385
  ## ⚡ Performance Strategies
@@ -345,10 +345,10 @@ class CoreThemeService {
345
345
  console.warn('[ngx-theme-stack] Could not save theme to localStorage.', e);
346
346
  }
347
347
  }
348
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CoreThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
349
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CoreThemeService, providedIn: 'root' });
348
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CoreThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
349
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CoreThemeService, providedIn: 'root' });
350
350
  }
351
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: CoreThemeService, decorators: [{
351
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CoreThemeService, decorators: [{
352
352
  type: Injectable,
353
353
  args: [{ providedIn: 'root' }]
354
354
  }], ctorParameters: () => [] });
@@ -406,10 +406,10 @@ class ThemeCycleService {
406
406
  cycle() {
407
407
  this.#core.setTheme(this.upcoming());
408
408
  }
409
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
410
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, providedIn: 'root' });
409
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeCycleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
410
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeCycleService, providedIn: 'root' });
411
411
  }
412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, decorators: [{
412
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeCycleService, decorators: [{
413
413
  type: Injectable,
414
414
  args: [{ providedIn: 'root' }]
415
415
  }] });
@@ -448,10 +448,10 @@ class ThemeSelectService {
448
448
  select(theme) {
449
449
  this.#core.setTheme(theme);
450
450
  }
451
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeSelectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
452
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeSelectService, providedIn: 'root' });
451
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeSelectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
452
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeSelectService, providedIn: 'root' });
453
453
  }
454
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeSelectService, decorators: [{
454
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeSelectService, decorators: [{
455
455
  type: Injectable,
456
456
  args: [{ providedIn: 'root' }]
457
457
  }] });
@@ -489,10 +489,10 @@ class ThemeToggleService {
489
489
  const next = this.#core.resolvedTheme() === 'dark' ? 'light' : 'dark';
490
490
  this.#core.setTheme(next);
491
491
  }
492
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeToggleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
493
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeToggleService, providedIn: 'root' });
492
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeToggleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
493
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeToggleService, providedIn: 'root' });
494
494
  }
495
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeToggleService, decorators: [{
495
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ThemeToggleService, decorators: [{
496
496
  type: Injectable,
497
497
  args: [{ providedIn: 'root' }]
498
498
  }] });
@@ -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'. */\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' (default) to inline all theme CSS in <head> — works for CSR, SSR, and SSG.\n * Use 'blocking' to load themes.css as a render-blocking stylesheet (HTTP-cacheable).\n */\n strategy: NgStrategy;\n\n /**\n * The **resolved** list of supported theme identifiers, always including the\n * built-in themes (`'light'`, `'dark'`, `'system'`).\n *\n * When you pass custom themes to {@link provideThemeStack}, they are **merged**\n * with the built-in defaults — your custom values are appended after them.\n *\n * @example\n * // Input to provideThemeStack:\n * themes: ['sepia', 'ocean'] as const\n *\n * // Resolved value stored in NgConfig:\n * // ['system', 'light', 'dark', 'sepia', 'ocean']\n *\n * Default (no custom themes): `['system', 'light', 'dark']`.\n */\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 * The `themes` option accepts **additional** theme identifiers beyond the\n * built-in defaults. They are merged with `'light'`, `'dark'`, and `'system'`\n * so that the resolved {@link NgConfig.themes} array always includes all of\n * them — your custom values are appended after the built-ins.\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 * // Critters strategy — inlines all theme CSS in <head> (works for CSR, SSR, and SSG)\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 /** The initial stored theme read from localStorage on startup. */\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 /**\n * Whether the currently applied theme is dark.\n *\n * Returns `false` when a custom theme (e.g. `'sunset'`) is active — use\n * `resolvedTheme()` directly if you need to inspect the current custom theme.\n */\n readonly isDark = computed(() => this.resolvedTheme() === 'dark');\n\n /**\n * Whether the currently applied theme is light.\n *\n * Returns `false` when a custom theme (e.g. `'sepia'`) is active — use\n * `resolvedTheme()` directly if you need to inspect the current custom theme.\n */\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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"}
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'. */\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' (default) to inline all theme CSS in <head> — works for CSR, SSR, and SSG.\n * Use 'blocking' to load themes.css as a render-blocking stylesheet (HTTP-cacheable).\n */\n strategy: NgStrategy;\n\n /**\n * The **resolved** list of supported theme identifiers, always including the\n * built-in themes (`'light'`, `'dark'`, `'system'`).\n *\n * When you pass custom themes to {@link provideThemeStack}, they are **merged**\n * with the built-in defaults — your custom values are appended after them.\n *\n * @example\n * // Input to provideThemeStack:\n * themes: ['sepia', 'ocean'] as const\n *\n * // Resolved value stored in NgConfig:\n * // ['system', 'light', 'dark', 'sepia', 'ocean']\n *\n * Default (no custom themes): `['system', 'light', 'dark']`.\n */\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 * The `themes` option accepts **additional** theme identifiers beyond the\n * built-in defaults. They are merged with `'light'`, `'dark'`, and `'system'`\n * so that the resolved {@link NgConfig.themes} array always includes all of\n * them — your custom values are appended after the built-ins.\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 * // Critters strategy — inlines all theme CSS in <head> (works for CSR, SSR, and SSG)\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 /** The initial stored theme read from localStorage on startup. */\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 /**\n * Whether the currently applied theme is dark.\n *\n * Returns `false` when a custom theme (e.g. `'sunset'`) is active — use\n * `resolvedTheme()` directly if you need to inspect the current custom theme.\n */\n readonly isDark = computed(() => this.resolvedTheme() === 'dark');\n\n /**\n * Whether the currently applied theme is light.\n *\n * Returns `false` when a custom theme (e.g. `'sepia'`) is active — use\n * `resolvedTheme()` directly if you need to inspect the current custom theme.\n */\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.5.2",
3
+ "version": "3.6.1",
4
4
  "description": "A stack of themes for Angular applications.",
5
5
  "keywords": [
6
6
  "angular",
@@ -10,6 +10,11 @@
10
10
  "description": "Re-sync the anti-flash script in index.html with the current provideThemeStack() configuration.",
11
11
  "factory": "./sync/index#sync",
12
12
  "schema": "./sync/schema.json"
13
+ },
14
+ "skill": {
15
+ "description": "Generate an AI Agent Skill (SKILL.md) in the project root.",
16
+ "factory": "./skill/index#skill",
17
+ "schema": "./skill/schema.json"
13
18
  }
14
19
  }
15
20
  }
@@ -15,7 +15,8 @@ const anti_flash_1 = require("./anti-flash");
15
15
  const app_config_1 = require("./app-config");
16
16
  const constants_1 = require("./constants");
17
17
  const utils_1 = require("./utils");
18
- function collectCustomOptions() {
18
+ const index_1 = require("../skill/index");
19
+ function collectCustomOptions(cliAddSkill) {
19
20
  return __awaiter(this, void 0, void 0, function* () {
20
21
  const rl = (0, utils_1.createRl)();
21
22
  try {
@@ -41,9 +42,14 @@ function collectCustomOptions() {
41
42
  process.stdout.write(' - blocking: Loads themes.css as a render-blocking stylesheet.\n');
42
43
  process.stdout.write(' HTTP-cacheable. Choose if Critters conflicts with your setup.\n');
43
44
  const strategy = (yield (0, utils_1.askList)(rl, 'Choose strategy:', STRATEGIES, 0));
45
+ let addSkill = cliAddSkill;
46
+ if (addSkill === undefined) {
47
+ const rawAddSkill = yield (0, utils_1.ask)(rl, ' Generate an AI Agent Skill (SKILL.md) in the project root? [Y/n]: ');
48
+ addSkill = rawAddSkill.toLowerCase() !== 'n';
49
+ }
44
50
  const provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy);
45
51
  process.stdout.write('\n');
46
- return { defaultTheme, storageKey, mode, themes, strategy, provideCall };
52
+ return { defaultTheme, storageKey, mode, themes, strategy, provideCall, addSkill };
47
53
  }
48
54
  finally {
49
55
  rl.close();
@@ -52,6 +58,7 @@ function collectCustomOptions() {
52
58
  }
53
59
  function ngAdd(options) {
54
60
  return (tree, context) => __awaiter(this, void 0, void 0, function* () {
61
+ var _a;
55
62
  const workspaceConfig = tree.read('/angular.json');
56
63
  if (!workspaceConfig) {
57
64
  throw new Error('Could not find angular.json. Are you in an Angular workspace?');
@@ -77,12 +84,13 @@ function ngAdd(options) {
77
84
  themes,
78
85
  strategy: strategy,
79
86
  provideCall: (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy),
87
+ addSkill: (_a = options.addSkill) !== null && _a !== void 0 ? _a : false,
80
88
  };
81
89
  context.logger.info('⚡ Quick setup — providing explicit defaults.');
82
90
  }
83
91
  else {
84
92
  context.logger.info('🛠 Custom setup — answer the prompts below:');
85
- config = yield collectCustomOptions();
93
+ config = yield collectCustomOptions(options.addSkill);
86
94
  }
87
95
  const changeset = [];
88
96
  const themesToScaffold = config.themes.filter((t) => t !== 'system');
@@ -145,6 +153,11 @@ function ngAdd(options) {
145
153
  t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
146
154
  changeset.push(' \u001b[33mM\u001b[0m angular.json (styles & optimization)');
147
155
  }
156
+ function scaffoldAgentSkill(t, ctx) {
157
+ if (config.addSkill) {
158
+ (0, index_1.generateSkill)(t, ctx);
159
+ }
160
+ }
148
161
  function patchProviderAndIndexHtml(t, ctx) {
149
162
  return __awaiter(this, void 0, void 0, function* () {
150
163
  yield (0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, config.provideCall, projectName);
@@ -166,6 +179,7 @@ function ngAdd(options) {
166
179
  scaffoldThemesCss,
167
180
  patchPrebuildScript,
168
181
  patchAngularJson,
182
+ scaffoldAgentSkill,
169
183
  patchProviderAndIndexHtml,
170
184
  ]);
171
185
  });
@@ -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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"}
@@ -5,4 +5,6 @@ export interface Schema {
5
5
  mode: 'quick' | 'custom';
6
6
  /** The strategy to prevent theme flicker. */
7
7
  strategy: 'critters' | 'blocking';
8
+ /** Whether to generate the AI agent skill. */
9
+ addSkill?: boolean;
8
10
  }
@@ -35,6 +35,10 @@
35
35
  "type": "string",
36
36
  "description": "The strategy to use for flash prevention (critters or blocking).",
37
37
  "enum": ["blocking", "critters"]
38
+ },
39
+ "addSkill": {
40
+ "type": "boolean",
41
+ "description": "Generate an AI Agent Skill (SKILL.md) in the project root."
38
42
  }
39
43
  },
40
44
  "required": ["project"]
@@ -0,0 +1,4 @@
1
+ import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
+ import { Schema } from './schema';
3
+ export declare function generateSkill(tree: Tree, context: SchematicContext): void;
4
+ export declare function skill(options: Schema): Rule;
@@ -0,0 +1,410 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSkill = generateSkill;
4
+ exports.skill = skill;
5
+ // ── SKILL.md (Tier 2 — loaded on activation) ────────────────────────────────
6
+ const SKILL_CONTENT = `---
7
+ name: ngx-theme-stack
8
+ description: >-
9
+ Use when installing, configuring, or building theme-switching UI with
10
+ ngx-theme-stack in Angular 20+. Covers provideThemeStack setup,
11
+ ThemeToggle/Cycle/Select services, SSR hydration guards, CSS variable
12
+ theming, and Tailwind CSS v4 integration. Do not use for generic Angular
13
+ styling, standalone dark-mode CSS, or projects not using ngx-theme-stack.
14
+ compatibility: Angular 20+ with TypeScript. Optional Tailwind CSS v4.
15
+ metadata:
16
+ author: WanderleeDev
17
+ version: "1.0.0"
18
+ ---
19
+
20
+ # ngx-theme-stack
21
+
22
+ A headless, signal-based theme manager for Angular 20+.
23
+ Supports dark/light/system/custom themes, SSR, and zero-flash rendering.
24
+
25
+ ## Architecture
26
+
27
+ \`\`\`
28
+ provideThemeStack() ← DI configuration (app.config.ts)
29
+
30
+ CoreThemeService ← Foundation: state, persistence, DOM updates
31
+
32
+ ┌────┼────────────┐
33
+ │ │ │
34
+ Toggle Cycle Select ← Convenience services (pick ONE per component)
35
+ \`\`\`
36
+
37
+ ## Interaction Rules
38
+
39
+ When the user asks to implement theme switching but does NOT specify which
40
+ service pattern to use, you MUST ask them to choose before writing code.
41
+ Present these options clearly:
42
+
43
+ 1. **Toggle** — A simple on/off switch between dark and light mode.
44
+ Best for: apps with only two themes, toggle buttons, icon switches.
45
+ 2. **Cycle** — A single button that rotates through all configured themes.
46
+ Best for: apps with 3+ themes, minimal UI footprint, "next theme" buttons.
47
+ 3. **Select** — A dropdown, radio group, or tab bar showing all theme options.
48
+ Best for: settings pages, theme pickers, full visibility of all themes.
49
+
50
+ Only proceed after the user confirms their choice.
51
+
52
+ ## Constraints
53
+
54
+ - Always import from \`'ngx-theme-stack'\` — never deep-import internal paths.
55
+ - Call \`provideThemeStack()\` exactly once, in the root \`app.config.ts\` providers.
56
+ - Custom themes are **merged** with built-ins. Passing \`['sepia']\` resolves to \`['system', 'light', 'dark', 'sepia']\`.
57
+ - After changing themes in \`provideThemeStack()\`, run: \`ng generate ngx-theme-stack:sync --project PROJECT_NAME\`.
58
+ - \`isDark()\` and \`isLight()\` return \`false\` for custom themes — use \`resolvedTheme()\` directly.
59
+ - Guard theme-dependent template content behind \`isHydrated()\` in SSR to prevent hydration mismatches.
60
+ - Never call \`setTheme()\` with a theme name not registered in the \`themes\` array — it throws \`NgxThemeStackError\`.
61
+ - Pick ONE convenience service per component — do not mix Toggle, Cycle, and Select in the same component.
62
+
63
+ ## Installation
64
+
65
+ \`\`\`bash
66
+ ng add ngx-theme-stack
67
+ \`\`\`
68
+
69
+ For Bun environments (where \`ng add\` is unsupported):
70
+ \`\`\`bash
71
+ bun add ngx-theme-stack
72
+ ng generate ngx-theme-stack:ng-add
73
+ \`\`\`
74
+
75
+ ## Configuration
76
+
77
+ Configure in \`app.config.ts\` via \`provideThemeStack()\`:
78
+
79
+ \`\`\`typescript
80
+ import { provideThemeStack } from 'ngx-theme-stack';
81
+
82
+ export const appConfig: ApplicationConfig = {
83
+ providers: [
84
+ provideThemeStack({
85
+ themes: ['sunset', 'ocean'] as const,
86
+ defaultTheme: 'system',
87
+ mode: 'class',
88
+ strategy: 'critters',
89
+ storageKey: 'ngx-theme-stack',
90
+ }),
91
+ ],
92
+ };
93
+ \`\`\`
94
+
95
+ | Option | Type | Default | Description |
96
+ | -------------- | ---------- | ----------------------------- | ---------------------------- |
97
+ | \`themes\` | \`string[]\` | \`['light', 'dark', 'system']\` | Merged with built-ins |
98
+ | \`defaultTheme\` | \`string\` | \`'system'\` | Theme on first visit |
99
+ | \`mode\` | \`NgMode\` | \`'class'\` | How the theme is applied |
100
+ | \`strategy\` | \`NgStrategy\`| \`'critters'\` | Anti-flash strategy |
101
+ | \`storageKey\` | \`string\` | \`'ngx-theme-stack'\` | localStorage persistence key |
102
+
103
+ ## Services Quick Reference
104
+
105
+ | Service | Method | Use case |
106
+ | -------------------- | ------------ | --------------------------------- |
107
+ | \`ThemeToggleService\` | \`toggle()\` | Binary dark/light switch |
108
+ | \`ThemeCycleService\` | \`cycle()\` | Rotate through all themes |
109
+ | \`ThemeSelectService\` | \`select(t)\` | Dropdown / radio / tab selection |
110
+ | \`CoreThemeService\` | \`setTheme(t)\`| Advanced / low-level access |
111
+
112
+ All services expose these signals: \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\`.
113
+
114
+ For complete component templates, copy from \`assets/\` directory in this skill folder.
115
+ For full API details, read \`references/api-reference.md\`.
116
+
117
+ ## SSR Hydration Guard
118
+
119
+ \`\`\`html
120
+ @if (theme.isHydrated()) {
121
+ <img [src]="theme.isDark() ? 'dark-logo.png' : 'light-logo.png'">
122
+ } @else {
123
+ <div class="logo-skeleton"></div>
124
+ }
125
+ \`\`\`
126
+
127
+ ## CSS Theme Tokens
128
+
129
+ Define in \`src/themes.css\` (scaffolded by \`ng add\`):
130
+
131
+ \`\`\`css
132
+ :root, .light { --bg: #fff; --text: #1a1a1a; }
133
+ .dark { --bg: #0a0a0a; --text: #f5f5f5; }
134
+ .sunset { --bg: #ff5f6d; --text: #fff; }
135
+ \`\`\`
136
+
137
+ ## Tailwind CSS v4
138
+
139
+ Map variables in \`src/styles.css\`:
140
+
141
+ \`\`\`css
142
+ @import 'tailwindcss';
143
+ @theme {
144
+ --color-bg: var(--bg);
145
+ --color-text: var(--text);
146
+ }
147
+ \`\`\`
148
+
149
+ Use semantic classes — no \`dark:\` prefix needed: \`bg-bg text-text\`.
150
+
151
+ ## Anti-patterns
152
+
153
+ - Do NOT create your own localStorage logic — the library handles persistence.
154
+ - Do NOT use multiple convenience services in the same component.
155
+ - Do NOT access \`document.documentElement\` directly — the library handles DOM.
156
+ - Do NOT use Tailwind's \`dark:\` prefix for multi-theme support.
157
+ - Do NOT skip \`ngx-theme-stack:sync\` after changing \`provideThemeStack()\` config.
158
+ `;
159
+ // ── references/api-reference.md (Tier 3 — loaded on demand) ─────────────────
160
+ const API_REFERENCE = `# ngx-theme-stack API Reference
161
+
162
+ ## provideThemeStack(config?)
163
+
164
+ Provides Theme Stack configuration to Angular's DI system.
165
+
166
+ \`\`\`typescript
167
+ provideThemeStack({
168
+ themes: ['sunset', 'ocean'] as const,
169
+ defaultTheme: 'system',
170
+ storageKey: 'ngx-theme-stack',
171
+ mode: 'class',
172
+ strategy: 'critters',
173
+ })
174
+ \`\`\`
175
+
176
+ Custom themes are **merged** with built-in defaults (\`'system'\`, \`'light'\`, \`'dark'\`).
177
+ Passing \`['sepia', 'ocean']\` resolves to \`['system', 'light', 'dark', 'sepia', 'ocean']\`.
178
+
179
+ ### Throws \`NgxThemeStackError\` when:
180
+ - A theme entry is empty or whitespace-only.
181
+ - \`defaultTheme\` is not in the resolved themes array.
182
+ - \`storageKey\` is empty or whitespace-only.
183
+
184
+ ---
185
+
186
+ ## CoreThemeService
187
+
188
+ Foundation service. Manages state (signals), persistence (localStorage),
189
+ system preference detection (matchMedia), and safe DOM manipulation (SSR compatible).
190
+
191
+ ### Signals
192
+
193
+ | Signal | Type | Description |
194
+ | ------------------ | ------------------- | --------------------------------------------------- |
195
+ | \`selectedTheme()\` | \`Signal<string>\` | Theme chosen by the user. May be \`'system'\`. |
196
+ | \`resolvedTheme()\` | \`Signal<string>\` | Theme applied to DOM. Never \`'system'\`. |
197
+ | \`isDark()\` | \`Signal<boolean>\` | \`true\` when resolved is \`'dark'\`. \`false\` for custom. |
198
+ | \`isLight()\` | \`Signal<boolean>\` | \`true\` when resolved is \`'light'\`. \`false\` for custom.|
199
+ | \`isSystem()\` | \`Signal<boolean>\` | \`true\` when user selected \`'system'\`. |
200
+ | \`isHydrated()\` | \`Signal<boolean>\` | \`true\` after first browser render. Guard SSR content.|
201
+
202
+ ### Methods
203
+
204
+ | Method | Signature | Description |
205
+ | ----------------- | ---------------------- | ---------------------------------------- |
206
+ | \`setTheme()\` | \`(theme: string): void\`| Validates, applies to DOM, persists. |
207
+
208
+ ### Properties
209
+
210
+ | Property | Type | Description |
211
+ | ----------------- | ----------- | ----------------------------------------- |
212
+ | \`availableThemes\` | \`string[]\` | Resolved list including built-ins. |
213
+
214
+ ---
215
+
216
+ ## ThemeToggleService
217
+
218
+ Binary switch between \`'dark'\` and \`'light'\`.
219
+
220
+ ### Signals
221
+ Inherits: \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\`.
222
+
223
+ ### Methods
224
+
225
+ | Method | Description |
226
+ | ---------- | ---------------------------------------------- |
227
+ | \`toggle()\` | If resolved is dark → light. Otherwise → dark. |
228
+
229
+ ---
230
+
231
+ ## ThemeCycleService
232
+
233
+ Rotates through all configured themes in order.
234
+
235
+ ### Signals
236
+ Inherits all from CoreThemeService, plus:
237
+
238
+ | Signal | Type | Description |
239
+ | -------------- | ----------------- | --------------------------------------- |
240
+ | \`cycleIndex()\` | \`Signal<number>\` | Index of current theme in the cycle. |
241
+ | \`upcoming()\` | \`Signal<string>\` | Next theme in the cycle. |
242
+ | \`preceding()\` | \`Signal<string>\` | Previous theme in the cycle. |
243
+
244
+ ### Methods
245
+
246
+ | Method | Description |
247
+ | --------- | ---------------------------- |
248
+ | \`cycle()\` | Advances to the next theme. |
249
+
250
+ ### Properties
251
+
252
+ | Property | Type | Description |
253
+ | ----------------- | ----------- | ---------------------------------- |
254
+ | \`availableThemes\` | \`string[]\` | Full list of themes in cycle order.|
255
+
256
+ ---
257
+
258
+ ## ThemeSelectService
259
+
260
+ Exposes the full theme list for dropdowns, radios, or tab selection.
261
+
262
+ ### Signals
263
+ Inherits: \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\`.
264
+
265
+ ### Methods
266
+
267
+ | Method | Signature | Description |
268
+ | ----------------- | ---------------------- | --------------------------- |
269
+ | \`select()\` | \`(theme: string): void\`| Applies the given theme. |
270
+
271
+ ### Properties
272
+
273
+ | Property | Type | Description |
274
+ | ----------------- | ----------- | ---------------------------------- |
275
+ | \`availableThemes\` | \`string[]\` | Full list of configured themes. |
276
+
277
+ ---
278
+
279
+ ## Types
280
+
281
+ | Type | Definition | Description |
282
+ | ------------- | ----------------------------------- | --------------------------------- |
283
+ | \`NgTheme<T>\` | \`'system' \\| 'light' \\| 'dark' \\| T\`| Theme identifier union |
284
+ | \`NgSystemTheme\`| \`'light' \\| 'dark'\` | Resolved system theme |
285
+ | \`NgMode\` | \`'class' \\| 'attribute' \\| 'both'\` | How theme is applied to DOM |
286
+ | \`NgStrategy\` | \`'critters' \\| 'blocking'\` | Anti-flash rendering strategy |
287
+ | \`NgConfig<T>\` | \`interface\` | Full library configuration |
288
+
289
+ ## Errors
290
+
291
+ | Error | When thrown |
292
+ | -------------------- | --------------------------------------------------------- |
293
+ | \`NgxThemeStackError\` | Invalid config, invalid theme name in \`setTheme()\`, etc. |
294
+
295
+ Catch with: \`if (e instanceof NgxThemeStackError) { ... }\`
296
+ `;
297
+ // ── assets/ templates (Tier 3 — copied on demand) ───────────────────────────
298
+ const TEMPLATE_TOGGLE = `# Theme Toggle Component
299
+
300
+ A simple button component to toggle between light and dark themes.
301
+
302
+ \`\`\`typescript
303
+ import { inject, Component } from '@angular/core';
304
+ import { ThemeToggleService } from 'ngx-theme-stack';
305
+
306
+ @Component({
307
+ selector: 'app-theme-toggle',
308
+ template: \`
309
+ @if (theme.isHydrated()) {
310
+ <button (click)="theme.toggle()">
311
+ {{ theme.isDark() ? '🌙' : '☀️' }}
312
+ </button>
313
+ } @else {
314
+ <div class="theme-toggle-skeleton"></div>
315
+ }
316
+ \`,
317
+ })
318
+ export class ThemeToggle {
319
+ protected readonly theme = inject(ThemeToggleService);
320
+ }
321
+ \`\`\`
322
+ `;
323
+ const TEMPLATE_CYCLE = `# Theme Cycle Component
324
+
325
+ A button component to cycle through all available themes.
326
+
327
+ \`\`\`typescript
328
+ import { inject, Component } from '@angular/core';
329
+ import { ThemeCycleService } from 'ngx-theme-stack';
330
+
331
+ @Component({
332
+ selector: 'app-theme-cycle',
333
+ template: \`
334
+ @if (theme.isHydrated()) {
335
+ <button (click)="theme.cycle()">
336
+ 🔄 Cycle Theme
337
+ </button>
338
+ } @else {
339
+ <div class="theme-cycle-skeleton"></div>
340
+ }
341
+ \`,
342
+ })
343
+ export class ThemeCycle {
344
+ protected readonly theme = inject(ThemeCycleService);
345
+ }
346
+ \`\`\`
347
+ `;
348
+ const TEMPLATE_SELECT = `# Theme Select Component
349
+
350
+ A dropdown select component to choose any available theme.
351
+
352
+ \`\`\`typescript
353
+ import { inject, Component } from '@angular/core';
354
+ import { ThemeSelectService } from 'ngx-theme-stack';
355
+
356
+ @Component({
357
+ selector: 'app-theme-select',
358
+ template: \`
359
+ @if (theme.isHydrated()) {
360
+ <select name="select-theme" (change)="onThemeChange($event)">
361
+ @for (t of theme.availableThemes; track t) {
362
+ <option [value]="t" [selected]="theme.selectedTheme() === t">
363
+ {{ t }}
364
+ </option>
365
+ }
366
+ </select>
367
+ } @else {
368
+ <div class="theme-select-skeleton"></div>
369
+ }
370
+ \`,
371
+ })
372
+ export class ThemeSelect {
373
+ protected readonly theme = inject(ThemeSelectService);
374
+
375
+ onThemeChange(event: Event) {
376
+ const value = (event.target as HTMLSelectElement).value;
377
+ this.theme.select(value);
378
+ }
379
+ }
380
+ \`\`\`
381
+ `;
382
+ // ── Schematic logic ─────────────────────────────────────────────────────────
383
+ const SKILL_ROOT = '.agent/skills/ngx-theme-stack';
384
+ const FILES = [
385
+ { path: `${SKILL_ROOT}/SKILL.md`, content: SKILL_CONTENT },
386
+ { path: `${SKILL_ROOT}/references/api-reference.md`, content: API_REFERENCE },
387
+ { path: `${SKILL_ROOT}/assets/theme-toggle.component.md`, content: TEMPLATE_TOGGLE },
388
+ { path: `${SKILL_ROOT}/assets/theme-cycle.component.md`, content: TEMPLATE_CYCLE },
389
+ { path: `${SKILL_ROOT}/assets/theme-select.component.md`, content: TEMPLATE_SELECT },
390
+ ];
391
+ function generateSkill(tree, context) {
392
+ for (const file of FILES) {
393
+ if (tree.exists(file.path)) {
394
+ tree.overwrite(file.path, file.content);
395
+ context.logger.info(` \u001b[32m✔\u001b[0m ${file.path} (updated)`);
396
+ }
397
+ else {
398
+ tree.create(file.path, file.content);
399
+ context.logger.info(` \u001b[36mA\u001b[0m ${file.path}`);
400
+ }
401
+ }
402
+ }
403
+ function skill(options) {
404
+ return (tree, context) => {
405
+ context.logger.info(`Generating AI agent skill for project: ${options.project}`);
406
+ generateSkill(tree, context);
407
+ return tree;
408
+ };
409
+ }
410
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/skill/index.ts"],"names":[],"mappings":";;AAgZA,sCAUC;AAED,sBAMC;AA/ZD,+EAA+E;AAE/E,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwJrB,CAAC;AAEF,+EAA+E;AAE/E,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwIrB,CAAC;AAEF,+EAA+E;AAE/E,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBvB,CAAC;AAEF,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBtB,CAAC;AAEF,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCvB,CAAC;AAEF,+EAA+E;AAE/E,MAAM,UAAU,GAAG,+BAA+B,CAAC;AAEnD,MAAM,KAAK,GAAwC;IACjD,EAAE,IAAI,EAAE,GAAG,UAAU,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE;IAC1D,EAAE,IAAI,EAAE,GAAG,UAAU,8BAA8B,EAAE,OAAO,EAAE,aAAa,EAAE;IAC7E,EAAE,IAAI,EAAE,GAAG,UAAU,mCAAmC,EAAE,OAAO,EAAE,eAAe,EAAE;IACpF,EAAE,IAAI,EAAE,GAAG,UAAU,kCAAkC,EAAE,OAAO,EAAE,cAAc,EAAE;IAClF,EAAE,IAAI,EAAE,GAAG,UAAU,mCAAmC,EAAE,OAAO,EAAE,eAAe,EAAE;CACrF,CAAC;AAEF,SAAgB,aAAa,CAAC,IAAU,EAAE,OAAyB;IACjE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACxC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,CAAC,IAAI,YAAY,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAgB,KAAK,CAAC,OAAe;IACnC,OAAO,CAAC,IAAU,EAAE,OAAyB,EAAE,EAAE;QAC/C,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export interface Schema {
2
+ /** Name of the Angular project. */
3
+ project: string;
4
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/skill/schema.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/schema",
3
+ "$id": "NgxThemeStackSkill",
4
+ "title": "ngx-theme-stack skill",
5
+ "type": "object",
6
+ "properties": {
7
+ "project": {
8
+ "type": "string",
9
+ "description": "The name of the Angular project.",
10
+ "$default": {
11
+ "$source": "projectName"
12
+ }
13
+ }
14
+ },
15
+ "required": ["project"]
16
+ }