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 +14 -15
- package/package.json +1 -1
- package/schematics/skill/index.js +101 -256
- package/schematics/skill/index.js.map +1 -1
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
|
-
>
|
|
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
|
-
--
|
|
309
|
-
--
|
|
306
|
+
--background: #ffffff;
|
|
307
|
+
--foreground: #333333;
|
|
310
308
|
}
|
|
311
309
|
|
|
312
310
|
.dark {
|
|
313
|
-
--
|
|
314
|
-
--
|
|
311
|
+
--background: #121212;
|
|
312
|
+
--foreground: #ffffff;
|
|
315
313
|
}
|
|
316
314
|
|
|
317
315
|
.sunset {
|
|
318
|
-
--
|
|
319
|
-
--
|
|
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-
|
|
335
|
-
--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-
|
|
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
|
@@ -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:
|
|
12
|
+
version: '1.1.0'
|
|
18
13
|
---
|
|
19
14
|
|
|
20
15
|
# ngx-theme-stack
|
|
21
16
|
|
|
22
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
28
|
+
## Constraints & Rules
|
|
113
29
|
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
35
|
+
## SSR Hydration & Layout Stability
|
|
118
36
|
|
|
119
|
-
|
|
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
|
-
|
|
41
|
+
<img [src]="theme.isDark() ? darkLogo : lightLogo" />
|
|
124
42
|
} @else {
|
|
125
|
-
|
|
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
|
-
|
|
47
|
+
## Configuration & API
|
|
131
48
|
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
| Service | Method | Signals |
|
|
59
|
+
| -------------------- | ----------- | ------------------------------------------------------------------------------------------- |
|
|
60
|
+
| \`ThemeToggleService\` | \`toggle()\` | \`selectedTheme()\`, \`resolvedTheme()\`, \`isDark()\`, \`isLight()\`, \`isSystem()\`, \`isHydrated()\` |
|
|
61
|
+
| \`ThemeCycleService\` | \`cycle()\` | |
|
|
62
|
+
| \`ThemeSelectService\` | \`select(t)\` | |
|
|
139
63
|
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
154
|
-
--color-
|
|
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
|
|
163
|
-
- Do NOT use
|
|
164
|
-
- Do NOT
|
|
165
|
-
- Do NOT use
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
| \`
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
##
|
|
140
|
+
## Convenience Services
|
|
226
141
|
|
|
227
|
-
|
|
142
|
+
Specialized services implementing different theme selection behaviors.
|
|
228
143
|
|
|
229
|
-
###
|
|
230
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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/
|
|
307
|
-
const TEMPLATE_TOGGLE =
|
|
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 =
|
|
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 =
|
|
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.
|
|
397
|
-
{ path: `${SKILL_ROOT}/assets/theme-cycle.
|
|
398
|
-
{ path: `${SKILL_ROOT}/assets/theme-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":";;
|
|
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"}
|