ngx-theme-stack 3.8.0 → 3.8.3

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,7 +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 `.agents/skills/` (optional) |
108
+ | `SKILL.md` | Generates an AI Agent Skill under `.agents/skills/` (optional) |
109
109
 
110
110
  > [!TIP]
111
111
  > **Re-configuration support:** Run `ng add` multiple times freely. The schematic updates existing code without duplicating it.
@@ -207,9 +207,7 @@ import { ThemeCycleService } from 'ngx-theme-stack';
207
207
  selector: 'app-theme-cycle',
208
208
  template: `
209
209
  @if (theme.isHydrated()) {
210
- <button (click)="theme.cycle()">
211
- 🔄 Cycle Theme
212
- </button>
210
+ <button (click)="theme.cycle()">🔄 Cycle Theme</button>
213
211
  } @else {
214
212
  <div class="theme-cycle-skeleton"></div>
215
213
  }
@@ -290,7 +288,7 @@ export class MyAdvancedComponent {
290
288
  >
291
289
  > ```html
292
290
  > @if (theme.isHydrated()) {
293
- > <img [src]="theme.isDark() ? 'dark-logo.png' : 'light-logo.png'">
291
+ > <img [src]="theme.isDark() ? darkLogo : lightLogo" />
294
292
  > }
295
293
  > ```
296
294
 
@@ -305,18 +303,18 @@ export class MyAdvancedComponent {
305
303
 
306
304
  :root,
307
305
  .light {
308
- --bg-color: #ffffff;
309
- --text-color: #333333;
306
+ --background: #ffffff;
307
+ --foreground: #333333;
310
308
  }
311
309
 
312
310
  .dark {
313
- --bg-color: #121212;
314
- --text-color: #ffffff;
311
+ --background: #121212;
312
+ --foreground: #ffffff;
315
313
  }
316
314
 
317
315
  .sunset {
318
- --bg-color: #ff5f6d;
319
- --text-color: #ffffff;
316
+ --background: #ff5f6d;
317
+ --foreground: #ffffff;
320
318
  }
321
319
  ```
322
320
 
@@ -331,15 +329,15 @@ export class MyAdvancedComponent {
331
329
  @import 'tailwindcss';
332
330
 
333
331
  @theme {
334
- --color-main-bg: var(--bg-color);
335
- --color-main-text: var(--text-color);
332
+ --color-background: var(--background);
333
+ --color-foreground: var(--foreground);
336
334
  }
337
335
  ```
338
336
 
339
337
  ### Use in components — no `dark:` prefix needed
340
338
 
341
339
  ```html
342
- <div class="bg-main-bg text-main-text shadow-xl">
340
+ <div class="bg-background text-foreground shadow-xl">
343
341
  <!-- automatically reflects the active theme -->
344
342
  </div>
345
343
  ```
@@ -364,6 +362,7 @@ Only needed if you want `dark:` utilities tied to ngx-theme-stack's toggle:
364
362
  </details>
365
363
 
366
364
  ---
365
+
367
366
  ## 🤖 AI Code Assistants Integration
368
367
 
369
368
  `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).
@@ -404,7 +403,7 @@ Once the skill is in your workspace, your AI assistant will automatically read i
404
403
  | **Network requests** | Zero | One (then cached) |
405
404
  | **Flash risk** | None | None |
406
405
  | **Works with CSR** | ✅ | ✅ |
407
- | **Works with SSR/SSG** | ✅ | ⚠️ May flash on SSG |
406
+ | **Works with SSR/SSG** | ✅ | ⚠️ May flash on SSG |
408
407
  | **Strict CSP compatible** | ❌ requires `unsafe-inline` | ✅ |
409
408
  | **Best for** | Most apps | Strict CSP, many themes |
410
409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-theme-stack",
3
- "version": "3.8.0",
3
+ "version": "3.8.3",
4
4
  "description": "A stack of themes for Angular applications.",
5
5
  "keywords": [
6
6
  "angular",
@@ -2,314 +2,174 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateSkill = generateSkill;
4
4
  exports.skill = skill;
5
- // ── SKILL.md (Tier 2 — loaded on activation) ────────────────────────────────
5
+ // ── SKILL.md (Tier 2 — loaded on activation) ─────────
6
6
  const SKILL_CONTENT = `---
7
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.
8
+ description: Signal-based theme manager for Angular 20+. Covers setup, services, SSR guards, and Tailwind v4.
14
9
  compatibility: Angular 20+ with TypeScript. Optional Tailwind CSS v4.
15
10
  metadata:
16
11
  author: WanderleeDev
17
- version: "1.0.0"
12
+ version: '1.1.0'
18
13
  ---
19
14
 
20
15
  # ngx-theme-stack
21
16
 
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
- \`\`\`
17
+ Headless, signal-based theme manager for Angular 20+.
36
18
 
37
19
  ## Interaction Rules
38
20
 
39
- > [!IMPORTANT]
40
- > **MANDATORY INTERACTION RULE**: If the user asks to implement theme switching, configuration, or a theme-related component but does NOT explicitly specify which convenience service to use (\`ThemeToggleService\`, \`ThemeCycleService\`, or \`ThemeSelectService\`), you **MUST NOT** make assumptions or start writing code.
41
- > You **MUST** stop and ask the user to explicitly choose one of the three patterns before proceeding.
42
- >
43
- > Present these options clearly:
44
- > 1. **Toggle** (\`ThemeToggleService\`) Binary toggle between dark and light themes (best for simple buttons or icon toggles).
45
- > 2. **Cycle** (\`ThemeCycleService\`) — Rotates through all configured themes (best for 3+ themes with a single button).
46
- > 3. **Select** (\`ThemeSelectService\`) — Full dropdown/radio list of all available themes (best for settings pages or explicit pickers).
47
- >
48
- > **Custom Themes Inquiry**: In addition to the service choice, you **MUST** ask the user if they want to configure or need any **custom themes** (e.g. \`sunset\`, \`ocean\`, \`sepia\`) beyond the default \`light\`, \`dark\`, and \`system\`. Prompt the user for details on what they are looking for or need (such as specific custom theme names, colors, or CSS variables) to help them scaffold these customizations.
49
- >
50
- > **DO NOT** generate any component code or configurations until the user has explicitly responded to these questions.
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
- - **Mandatory Theme Synchronization**: After configuring, adding, or modifying themes in \`provideThemeStack()\`, you **MUST** run the synchronization schematic command: \`ng generate ngx-theme-stack:sync --project PROJECT_NAME\` (or explicitly instruct the user to run it if you cannot execute commands). Failing to synchronize themes after modification is a critical violation that breaks theme compilation.
58
- - \`isDark()\` and \`isLight()\` return \`false\` for custom themes — use \`resolvedTheme()\` directly.
59
- - **Mandatory SSR Guard**: You MUST guard all theme-dependent template elements (e.g. text, icons, images, styling classes based on theme signals) using \`@if (theme.isHydrated())\`. Using theme signals (\`isDark()\`, \`resolvedTheme()\`, etc.) directly in templates without checking \`isHydrated()\` causes content flickering and critical Angular hydration mismatch errors in SSR.
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 |
21
+ - **MANDATORY RULE**: If the user asks for theme components/switching, you **MUST** ask them to choose:
22
+ - **Toggle** (\`ThemeToggleService\`) - Binary dark/light toggle.
23
+ - **Cycle** (\`ThemeCycleService\`) - Rotate through all themes.
24
+ - **Select** (\`ThemeSelectService\`) - Full picker dropdown/radio selection.
25
+ - **Custom Themes Inquiry**: Ask if they want custom themes (e.g. \`sunset\`, colors, or CSS variables).
26
+ - **DO NOT** generate code or configs until the user responds to these questions.
111
27
 
112
- All services expose these signals: \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\`.
28
+ ## Constraints & Rules
113
29
 
114
- For complete component templates, copy from \`assets/\` directory in this skill folder.
115
- For full API details, read \`references/api-reference.md\`.
30
+ - Call \`provideThemeStack()\` once in root \`app.config.ts\`. Custom themes merge with defaults.
31
+ - **Theme Synchronization**: Run \`ng generate ngx-theme-stack:sync --project PROJECT_NAME\` after changing config.
32
+ - \`isDark()\` / \`isLight()\` return false for custom themes (use \`resolvedTheme()\`).
33
+ - Pick ONE convenience service per component. Do not write custom localStorage or direct DOM logic.
116
34
 
117
- ## SSR Hydration Guard & Layout Stability
35
+ ## SSR Hydration & Layout Stability
118
36
 
119
- Guard theme-dependent template content behind \`isHydrated()\` in SSR to prevent hydration mismatches and layout flickering.
37
+ Wrap theme-dependent elements in \`@if (theme.isHydrated())\` to prevent layout shift and SSR mismatches. Fallback placeholders in \`@else\` must match the exact hydrated dimensions.
120
38
 
121
39
  \`\`\`html
122
40
  @if (theme.isHydrated()) {
123
- <img [src]="theme.isDark() ? 'dark-logo.png' : 'light-logo.png'">
41
+ <img [src]="theme.isDark() ? darkLogo : lightLogo" />
124
42
  } @else {
125
- <!-- The placeholder/skeleton MUST match the exact size and spacing of the hydrated image -->
126
- <div class="logo-skeleton" style="width: 150px; height: 40px; display: inline-block;"></div>
43
+ <div class="logo-skeleton"></div>
127
44
  }
128
45
  \`\`\`
129
46
 
130
- ### Skeleton & Layout Stability Guidelines
47
+ ## Configuration & API
131
48
 
132
- When designing fallback skeleton loaders/placeholders:
133
- 1. **Dimension & Spacing Match**: The fallback element (e.g., inside the \`@else\` block) MUST occupy the exact same space, size, margins, padding, positioning, and responsive constraints as the hydrated element. This ensures zero layout shift (preventing CLS issues) and keeps the Largest Contentful Paint (LCP) optimized.
134
- 2. **Granular Skeletons**: NEVER wrap an entire complex component or large layout block in an \`isHydrated()\` guard if only a single word, label, icon, or sub-element changes dynamically based on the theme. Instead, place the \`isHydrated()\` guard at the most granular level possible (e.g., directly wrapping just the dynamic text or icon inside the button), leaving the surrounding button wrapper and layout static. A full-element skeleton should only be used if the entire component's structure/layout is completely dynamic.
49
+ See [references/api-reference.md](references/api-reference.md) for APIs. Examples: [Toggle](assets/theme-toggle.ts) · [Cycle](assets/theme-cycle.ts) · [Select](assets/theme-select.ts).
135
50
 
136
- ## CSS Theme Tokens
51
+ \`\`\`typescript
52
+ import { provideThemeStack } from 'ngx-theme-stack';
53
+ export const appConfig = {
54
+ providers: [provideThemeStack({ themes: ['sunset'] as const, strategy: 'critters' })],
55
+ };
56
+ \`\`\`
137
57
 
138
- Define in \`src/themes.css\` (scaffolded by \`ng add\`):
58
+ | Service | Method | Signals |
59
+ | -------------------- | ----------- | ------------------------------------------------------------------------------------------- |
60
+ | \`ThemeToggleService\` | \`toggle()\` | \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\` |
61
+ | \`ThemeCycleService\` | \`cycle()\` | |
62
+ | \`ThemeSelectService\` | \`select(t)\` | |
139
63
 
140
- \`\`\`css
141
- :root, .light { --bg: #fff; --text: #1a1a1a; }
142
- .dark { --bg: #0a0a0a; --text: #f5f5f5; }
143
- .sunset { --bg: #ff5f6d; --text: #fff; }
144
- \`\`\`
64
+ ## Styling: CSS Variables & Tailwind Separation
145
65
 
146
- ## Tailwind CSS v4
66
+ Define CSS variables in \`src/themes.css\` and map them to Tailwind in \`src/styles.css\` (use semantic classes, not \`dark:\`):
147
67
 
148
- Map variables in \`src/styles.css\`:
68
+ \`\`\`css
69
+ /* src/themes.css */
70
+ :root,
71
+ .light {
72
+ --background: #fff;
73
+ --foreground: #1a1a1a;
74
+ }
75
+ .dark {
76
+ --background: #0a0a0a;
77
+ --foreground: #f5f5f5;
78
+ }
79
+ .sunset {
80
+ --background: #ff5f6d;
81
+ --foreground: #fff;
82
+ }
83
+ \`\`\`
149
84
 
150
85
  \`\`\`css
86
+ /* src/styles.css */
151
87
  @import 'tailwindcss';
152
88
  @theme {
153
- --color-bg: var(--bg);
154
- --color-text: var(--text);
89
+ --color-background: var(--background);
90
+ --color-foreground: var(--foreground);
155
91
  }
156
92
  \`\`\`
157
93
 
158
- Use semantic classes — no \`dark:\` prefix needed: \`bg-bg text-text\`.
159
-
160
94
  ## Anti-patterns
161
95
 
162
- - Do NOT create your own localStorage logic the library handles persistence.
163
- - Do NOT use multiple convenience services in the same component.
164
- - Do NOT access \`document.documentElement\` directly the library handles DOM.
165
- - Do NOT use Tailwind's \`dark:\` prefix for multi-theme support.
166
- - Do NOT skip \`ngx-theme-stack:sync\` after changing \`provideThemeStack()\` config.
96
+ - Do NOT mix Toggle, Cycle, and Select in the same component.
97
+ - Do NOT use Tailwind's \`dark:\` utility modifier (use semantic classes mapped from themes).
98
+ - Do NOT skip \`ngx-theme-stack:sync\` schematic after updating providers.
99
+ - Do NOT use theme signals in templates without an \`@if (theme.isHydrated())\` guard.
167
100
  `;
168
- // ── references/api-reference.md (Tier 3 — loaded on demand) ─────────────────
101
+ // ── references/api-reference.md (Tier 3 — loaded on demand) ──────────────────
169
102
  const API_REFERENCE = `# ngx-theme-stack API Reference
170
103
 
171
104
  ## provideThemeStack(config?)
172
105
 
173
- Provides Theme Stack configuration to Angular's DI system.
106
+ Configures the Theme Stack in \`app.config.ts\`. Custom themes merge with built-ins (\`system\`, \`light\`, \`dark\`).
174
107
 
175
108
  \`\`\`typescript
176
109
  provideThemeStack({
177
110
  themes: ['sunset', 'ocean'] as const,
178
111
  defaultTheme: 'system',
179
112
  storageKey: 'ngx-theme-stack',
180
- mode: 'class',
181
- strategy: 'critters',
113
+ mode: 'class', // 'class' | 'attribute' | 'both'
114
+ strategy: 'critters', // 'critters' | 'blocking'
182
115
  })
183
116
  \`\`\`
184
117
 
185
- Custom themes are **merged** with built-in defaults (\`'system'\`, \`'light'\`, \`'dark'\`).
186
- Passing \`['sepia', 'ocean']\` resolves to \`['system', 'light', 'dark', 'sepia', 'ocean']\`.
187
-
188
- ### Throws \`NgxThemeStackError\` when:
189
- - A theme entry is empty or whitespace-only.
190
- - \`defaultTheme\` is not in the resolved themes array.
191
- - \`storageKey\` is empty or whitespace-only.
118
+ **Throws \`NgxThemeStackError\` when:**
119
+ - A theme entry is empty, or \`defaultTheme\` is not in themes, or \`storageKey\` is empty.
192
120
 
193
121
  ---
194
122
 
195
123
  ## CoreThemeService
196
124
 
197
- Foundation service. Manages state (signals), persistence (localStorage),
198
- system preference detection (matchMedia), and safe DOM manipulation (SSR compatible).
199
-
200
- ### Signals
125
+ Foundation service managing state (signals), persistence, system preference, and DOM manipulation (SSR safe).
201
126
 
202
- | Signal | Type | Description |
203
- | ------------------ | ------------------- | --------------------------------------------------- |
204
- | \`selectedTheme()\` | \`Signal<string>\` | Theme chosen by the user. May be \`'system'\`. |
205
- | \`resolvedTheme()\` | \`Signal<string>\` | Theme applied to DOM. Never \`'system'\`. |
206
- | \`isDark()\` | \`Signal<boolean>\` | \`true\` when resolved is \`'dark'\`. \`false\` for custom. |
207
- | \`isLight()\` | \`Signal<boolean>\` | \`true\` when resolved is \`'light'\`. \`false\` for custom.|
208
- | \`isSystem()\` | \`Signal<boolean>\` | \`true\` when user selected \`'system'\`. |
209
- | \`isHydrated()\` | \`Signal<boolean>\` | \`true\` after first browser render. Guard SSR content.|
127
+ ### Signals, Methods & Properties
210
128
 
211
- ### Methods
212
-
213
- | Method | Signature | Description |
214
- | ----------------- | ---------------------- | ---------------------------------------- |
215
- | \`setTheme()\` | \`(theme: string): void\`| Validates, applies to DOM, persists. |
216
-
217
- ### Properties
218
-
219
- | Property | Type | Description |
220
- | ----------------- | ----------- | ----------------------------------------- |
221
- | \`availableThemes\` | \`string[]\` | Resolved list including built-ins. |
129
+ | Name | Type | Description |
130
+ | --- | --- | --- |
131
+ | \`selectedTheme()\` | \`Signal<string>\` | Chosen theme (can be \`'system'\`). |
132
+ | \`resolvedTheme()\` | \`Signal<string>\` | Active theme applied to DOM (never \`'system'\`). |
133
+ | \`isDark()\` / \`isLight()\` | \`Signal<boolean>\` | \`true\` for dark/light (returns \`false\` for custom themes). |
134
+ | \`isSystem()\` / \`isHydrated()\` | \`Signal<boolean>\` | System choice active / SSR hydration finished. |
135
+ | \`availableThemes\` | \`string[]\` | All configured themes including built-ins. |
136
+ | \`setTheme(theme)\` | \`(theme: string) => void\` | Validates, persists, and applies the theme to DOM. |
222
137
 
223
138
  ---
224
139
 
225
- ## ThemeToggleService
140
+ ## Convenience Services
226
141
 
227
- Binary switch between \`'dark'\` and \`'light'\`.
142
+ Specialized services implementing different theme selection behaviors.
228
143
 
229
- ### Signals
230
- Inherits: \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\`.
231
-
232
- ### Methods
233
-
234
- | Method | Description |
235
- | ---------- | ---------------------------------------------- |
236
- | \`toggle()\` | If resolved is dark → light. Otherwise → dark. |
237
-
238
- ---
239
-
240
- ## ThemeCycleService
241
-
242
- Rotates through all configured themes in order.
243
-
244
- ### Signals
245
- Inherits all from CoreThemeService, plus:
246
-
247
- | Signal | Type | Description |
248
- | -------------- | ----------------- | --------------------------------------- |
249
- | \`cycleIndex()\` | \`Signal<number>\` | Index of current theme in the cycle. |
250
- | \`upcoming()\` | \`Signal<string>\` | Next theme in the cycle. |
251
- | \`preceding()\` | \`Signal<string>\` | Previous theme in the cycle. |
252
-
253
- ### Methods
254
-
255
- | Method | Description |
256
- | --------- | ---------------------------- |
257
- | \`cycle()\` | Advances to the next theme. |
258
-
259
- ### Properties
260
-
261
- | Property | Type | Description |
262
- | ----------------- | ----------- | ---------------------------------- |
263
- | \`availableThemes\` | \`string[]\` | Full list of themes in cycle order.|
264
-
265
- ---
266
-
267
- ## ThemeSelectService
268
-
269
- Exposes the full theme list for dropdowns, radios, or tab selection.
270
-
271
- ### Signals
272
- Inherits: \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\`.
273
-
274
- ### Methods
275
-
276
- | Method | Signature | Description |
277
- | ----------------- | ---------------------- | --------------------------- |
278
- | \`select()\` | \`(theme: string): void\`| Applies the given theme. |
144
+ ### ThemeToggleService
145
+ Binary switch between \`'dark'\` and \`'light'\`.
146
+ - \`toggle()\`: Toggles the theme.
279
147
 
280
- ### Properties
148
+ ### ThemeCycleService
149
+ Rotates through all themes in configuration order.
150
+ - \`cycle()\`: Moves to the next theme.
151
+ - \`cycleIndex()\`: \`Signal<number>\` - Current theme index.
152
+ - \`upcoming()\` / \`preceding()\`: \`Signal<string>\` - Next / previous theme in cycle.
281
153
 
282
- | Property | Type | Description |
283
- | ----------------- | ----------- | ---------------------------------- |
284
- | \`availableThemes\` | \`string[]\` | Full list of configured themes. |
154
+ ### ThemeSelectService
155
+ Full list control for select dropdowns, radio buttons, or lists.
156
+ - \`select(theme)\`: Sets the chosen theme.
285
157
 
286
158
  ---
287
159
 
288
- ## Types
289
-
290
- | Type | Definition | Description |
291
- | ------------- | ----------------------------------- | --------------------------------- |
292
- | \`NgTheme<T>\` | \`'system' \\| 'light' \\| 'dark' \\| T\`| Theme identifier union |
293
- | \`NgSystemTheme\`| \`'light' \\| 'dark'\` | Resolved system theme |
294
- | \`NgMode\` | \`'class' \\| 'attribute' \\| 'both'\` | How theme is applied to DOM |
295
- | \`NgStrategy\` | \`'critters' \\| 'blocking'\` | Anti-flash rendering strategy |
296
- | \`NgConfig<T>\` | \`interface\` | Full library configuration |
160
+ ## Types & Errors
297
161
 
298
- ## Errors
299
-
300
- | Error | When thrown |
301
- | -------------------- | --------------------------------------------------------- |
302
- | \`NgxThemeStackError\` | Invalid config, invalid theme name in \`setTheme()\`, etc. |
162
+ ### Core Types
163
+ - \`NgTheme<T>\`: \`'system' | 'light' | 'dark' | T\`
164
+ - \`NgMode\`: \`'class' | 'attribute' | 'both'\`
165
+ - \`NgStrategy\`: \`'critters' | 'blocking'\`
303
166
 
167
+ ### Errors
168
+ - \`NgxThemeStackError\`: Thrown for invalid configurations, storage keys, or theme names.
304
169
  Catch with: \`if (e instanceof NgxThemeStackError) { ... }\`
305
170
  `;
306
- // ── assets/ templates (Tier 3 — copied on demand) ───────────────────────────
307
- const TEMPLATE_TOGGLE = `# Theme Toggle Component
308
-
309
- A simple button component to toggle between light and dark themes.
310
-
311
- \`\`\`typescript
312
- import { inject, Component } from '@angular/core';
171
+ // ── assets/ component examples (Tier 3 — pure TypeScript, read on demand) ───
172
+ const TEMPLATE_TOGGLE = `import { inject, Component } from '@angular/core';
313
173
  import { ThemeToggleService } from 'ngx-theme-stack';
314
174
 
315
175
  @Component({
@@ -327,23 +187,15 @@ import { ThemeToggleService } from 'ngx-theme-stack';
327
187
  export class ThemeToggle {
328
188
  protected readonly theme = inject(ThemeToggleService);
329
189
  }
330
- \`\`\`
331
190
  `;
332
- const TEMPLATE_CYCLE = `# Theme Cycle Component
333
-
334
- A button component to cycle through all available themes.
335
-
336
- \`\`\`typescript
337
- import { inject, Component } from '@angular/core';
191
+ const TEMPLATE_CYCLE = `import { inject, Component } from '@angular/core';
338
192
  import { ThemeCycleService } from 'ngx-theme-stack';
339
193
 
340
194
  @Component({
341
195
  selector: 'app-theme-cycle',
342
196
  template: \`
343
197
  @if (theme.isHydrated()) {
344
- <button (click)="theme.cycle()">
345
- 🔄 Cycle Theme
346
- </button>
198
+ <button (click)="theme.cycle()">🔄 Cycle Theme</button>
347
199
  } @else {
348
200
  <div class="theme-cycle-skeleton"></div>
349
201
  }
@@ -352,14 +204,8 @@ import { ThemeCycleService } from 'ngx-theme-stack';
352
204
  export class ThemeCycle {
353
205
  protected readonly theme = inject(ThemeCycleService);
354
206
  }
355
- \`\`\`
356
207
  `;
357
- const TEMPLATE_SELECT = `# Theme Select Component
358
-
359
- A dropdown select component to choose any available theme.
360
-
361
- \`\`\`typescript
362
- import { inject, Component } from '@angular/core';
208
+ const TEMPLATE_SELECT = `import { inject, Component } from '@angular/core';
363
209
  import { ThemeSelectService } from 'ngx-theme-stack';
364
210
 
365
211
  @Component({
@@ -386,16 +232,15 @@ export class ThemeSelect {
386
232
  this.theme.select(value);
387
233
  }
388
234
  }
389
- \`\`\`
390
235
  `;
391
236
  // ── Schematic logic ─────────────────────────────────────────────────────────
392
237
  const SKILL_ROOT = '.agents/skills/ngx-theme-stack';
393
238
  const FILES = [
394
239
  { path: `${SKILL_ROOT}/SKILL.md`, content: SKILL_CONTENT },
395
240
  { path: `${SKILL_ROOT}/references/api-reference.md`, content: API_REFERENCE },
396
- { path: `${SKILL_ROOT}/assets/theme-toggle.component.md`, content: TEMPLATE_TOGGLE },
397
- { path: `${SKILL_ROOT}/assets/theme-cycle.component.md`, content: TEMPLATE_CYCLE },
398
- { path: `${SKILL_ROOT}/assets/theme-select.component.md`, content: TEMPLATE_SELECT },
241
+ { path: `${SKILL_ROOT}/assets/theme-toggle.ts`, content: TEMPLATE_TOGGLE },
242
+ { path: `${SKILL_ROOT}/assets/theme-cycle.ts`, content: TEMPLATE_CYCLE },
243
+ { path: `${SKILL_ROOT}/assets/theme-select.ts`, content: TEMPLATE_SELECT },
399
244
  ];
400
245
  function generateSkill(tree, context) {
401
246
  for (const file of FILES) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/skill/index.ts"],"names":[],"mappings":";;AAyZA,sCAUC;AAED,sBAMC;AAxaD,+EAA+E;AAE/E,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiKrB,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,gCAAgC,CAAC;AAEpD,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/skill/index.ts"],"names":[],"mappings":";;AAgQA,sCAUC;AAED,sBAMC;AA/QD,wDAAwD;AACxD,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8FrB,CAAC;AAEF,gFAAgF;AAEhF,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoErB,CAAC;AAEF,+EAA+E;AAE/E,MAAM,eAAe,GACrB;;;;;;;;;;;;;;;;;;CAkBC,CAAC;AAEF,MAAM,cAAc,GACpB;;;;;;;;;;;;;;;;CAgBC,CAAC;AAEF,MAAM,eAAe,GACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,CAAC;AAEF,+EAA+E;AAE/E,MAAM,UAAU,GAAG,gCAAgC,CAAC;AAEpD,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,yBAAyB,EAAE,OAAO,EAAE,eAAe,EAAE;IAC1E,EAAE,IAAI,EAAE,GAAG,UAAU,wBAAwB,EAAE,OAAO,EAAE,cAAc,EAAE;IACxE,EAAE,IAAI,EAAE,GAAG,UAAU,yBAAyB,EAAE,OAAO,EAAE,eAAe,EAAE;CAC3E,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"}