ngx-theme-stack 3.4.0 โ 3.5.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 +202 -129
- package/fesm2022/ngx-theme-stack.mjs +18 -9
- package/fesm2022/ngx-theme-stack.mjs.map +1 -1
- package/package.json +21 -2
- package/schematics/ng-add/app-config.js +26 -33
- package/schematics/ng-add/app-config.js.map +1 -1
- package/schematics/ng-add/index.d.ts +0 -3
- package/schematics/ng-add/index.js +74 -64
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/utils.js.map +1 -1
- package/schematics/sync/index.js +33 -46
- package/schematics/sync/index.js.map +1 -1
- package/types/ngx-theme-stack.d.ts +36 -9
package/README.md
CHANGED
|
@@ -1,96 +1,140 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐จ ngx-theme-stack
|
|
4
|
+
|
|
5
|
+
**A simple and powerful headless theme manager for Angular.**
|
|
6
|
+
Built for performance and SSR support.
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/ngx-theme-stack)
|
|
9
|
+
[](https://github.com/WanderleeDev/ngx-theme-stack/blob/main/LICENSE)
|
|
10
|
+
[](https://angular.dev/)
|
|
11
|
+
[](https://angular.dev/guide/signals)
|
|
12
|
+
[](https://angular.dev/guide/ssr)
|
|
13
|
+
|
|
14
|
+
[๐ Live Demo](https://demo-ngx-theme-stack.wanderlee.site/) ยท [๐ Documentation](https://ngx-theme-stack-docs.wanderlee.site/) ยท [โญ Star on GitHub](https://github.com/WanderleeDev/ngx-theme-stack)
|
|
2
15
|
|
|
3
16
|

|
|
4
17
|
|
|
5
|
-
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
6
21
|
|
|
7
|
-
|
|
22
|
+
## ๐ Table of Contents
|
|
23
|
+
|
|
24
|
+
- [๐ Features](#-features)
|
|
25
|
+
- [๐ฆ Installation](#-installation)
|
|
26
|
+
- [๐ค What does `ng add` do for you?](#-what-does-ng-add-do-for-you)
|
|
27
|
+
- [๐๏ธ Architecture & Extensibility](#๏ธ-architecture--extensibility)
|
|
28
|
+
- [โ๏ธ Supported Versions](#๏ธ-supported-versions)
|
|
29
|
+
- [โ๏ธ Configuration](#๏ธ-configuration)
|
|
30
|
+
- [๐ ๏ธ Usage](#๏ธ-usage)
|
|
31
|
+
- [๐ก๏ธ CoreThemeService API](#๏ธ-advanced-corethemeservice-api)
|
|
32
|
+
- [๐จ Styling](#-styling)
|
|
33
|
+
- [๐ช๏ธ Tailwind CSS v4 Integration](#๏ธ-tailwind-css-v4-integration)
|
|
34
|
+
- [โก Performance Strategies](#-performance-strategies)
|
|
35
|
+
- [๐ License](#-license)
|
|
36
|
+
|
|
37
|
+
---
|
|
8
38
|
|
|
9
39
|
## ๐ Features
|
|
10
40
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
41
|
+
| | Feature | Description |
|
|
42
|
+
| --- | ------------------------------- | --------------------------------------------------- |
|
|
43
|
+
| โก | **Single command setup** | Automatic configuration via `ng add` |
|
|
44
|
+
| ๐ | **System preference detection** | Auto-syncs with OS via `prefers-color-scheme` |
|
|
45
|
+
| ๐ | **Dynamic switching** | Toggle, cycle, or select between themes |
|
|
46
|
+
| ๐ ๏ธ | **Highly customizable** | Custom themes, class prefixes, configurable storage |
|
|
47
|
+
| ๐งฑ | **Angular Signals** | Maximum reactivity and performance |
|
|
48
|
+
| ๐ | **SSR ready** | Safe in server-side rendering environments |
|
|
49
|
+
| ๐ซ | **Zero flicker** | Anti-flash script + Critters Trick strategy |
|
|
18
50
|
|
|
19
|
-
|
|
51
|
+
---
|
|
20
52
|
|
|
21
|
-
|
|
53
|
+
## ๐ฆ Installation
|
|
22
54
|
|
|
23
55
|
```bash
|
|
24
56
|
ng add ngx-theme-stack
|
|
25
57
|
```
|
|
26
58
|
|
|
27
|
-
### Installation Modes
|
|
28
|
-
|
|
29
59
|
> [!TIP]
|
|
30
60
|
> **๐ Using Bun?**
|
|
31
|
-
> Since `ng add` is currently not supported for Bun environments,
|
|
61
|
+
> Since `ng add` is currently not supported for Bun environments, use this two-step process:
|
|
32
62
|
>
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
63
|
+
> ```bash
|
|
64
|
+
> bun add ngx-theme-stack
|
|
65
|
+
> ng generate ngx-theme-stack:ng-add
|
|
66
|
+
> ```
|
|
67
|
+
|
|
68
|
+
### Installation modes
|
|
37
69
|
|
|
38
|
-
When running `ng add`, you
|
|
70
|
+
When running `ng add`, you choose between two modes:
|
|
39
71
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- Initial theme: `system`.
|
|
43
|
-
- Apply mode: `class` (adds the theme class to the `<html>` element).
|
|
44
|
-
- Available themes: `['light', 'dark', 'system']`.
|
|
45
|
-
- **Strategy**: `critters` (Zero-flash via CSS inlining).
|
|
72
|
+
<details>
|
|
73
|
+
<summary><strong>โก Quick mode</strong> โ default, applies instantly</summary>
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
| Option | Value |
|
|
76
|
+
| ---------------- | ------------------------- |
|
|
77
|
+
| Initial theme | `system` |
|
|
78
|
+
| Apply mode | `class` on `<html>` |
|
|
79
|
+
| Available themes | `light`, `dark`, `system` |
|
|
80
|
+
| Strategy | `critters` โ zero flash |
|
|
81
|
+
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
<details>
|
|
85
|
+
<summary><strong>๐ ๏ธ Custom mode</strong> โ full control</summary>
|
|
86
|
+
|
|
87
|
+
- Choose which themes to include (e.g. `blue`, `high-contrast`)
|
|
88
|
+
- Configure the default theme on startup
|
|
89
|
+
- Change the `localStorage` key
|
|
90
|
+
- Apply via `class`, `data-theme` attribute, or both
|
|
91
|
+
- Pick your anti-flash strategy: `critters` or `blocking`
|
|
92
|
+
|
|
93
|
+
</details>
|
|
94
|
+
|
|
95
|
+
---
|
|
53
96
|
|
|
54
97
|
## ๐ค What does `ng add` do for you?
|
|
55
98
|
|
|
56
|
-
|
|
99
|
+
The installation command automates the following:
|
|
57
100
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
101
|
+
| File | What changes |
|
|
102
|
+
| --------------- | ----------------------------------------------------------------------- |
|
|
103
|
+
| `app.config.ts` | Injects `provideThemeStack()` using AST โ follows imports automatically |
|
|
104
|
+
| `index.html` | Injects the blocking anti-flash script into `<head>` |
|
|
105
|
+
| `package.json` | Adds a `"prebuild"` script for theme synchronization |
|
|
106
|
+
| `angular.json` | Registers `themes.css` and optimizes build config |
|
|
107
|
+
| `themes.css` | Scaffolds base theme tokens if they don't exist |
|
|
63
108
|
|
|
64
109
|
> [!TIP]
|
|
65
|
-
> **Re-configuration support:**
|
|
66
|
-
|
|
67
|
-
## ๐๏ธ Architecture & Extensibility
|
|
110
|
+
> **Re-configuration support:** Run `ng add` multiple times freely. The schematic updates existing code without duplicating it.
|
|
68
111
|
|
|
69
|
-
|
|
112
|
+
---
|
|
70
113
|
|
|
71
|
-
|
|
72
|
-
- **Extensibility:** You can inject `CoreThemeService` to build your own custom services or components with specific business logic.
|
|
114
|
+
## ๐๏ธ Architecture & Extensibility
|
|
73
115
|
|
|
74
|
-
|
|
116
|
+
The **`CoreThemeService`** is the foundation โ it manages state (Signals), persistence (localStorage), system detection (matchMedia), and safe DOM manipulation (SSR compatible).
|
|
75
117
|
|
|
76
|
-
|
|
118
|
+
### Utility services
|
|
77
119
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
120
|
+
| Service | Pattern | Description |
|
|
121
|
+
| -------------------- | ------- | ------------------------------------------------------------------------- |
|
|
122
|
+
| `ThemeToggleService` | Toggle | Binary switch between `light` and `dark` |
|
|
123
|
+
| `ThemeSelectService` | Select | Exposes the full theme list; ideal for dropdowns |
|
|
124
|
+
| `ThemeCycleService` | Cycle | Rotates through all themes; exposes `upcoming`, `preceding`, `cycleIndex` |
|
|
81
125
|
|
|
82
126
|
---
|
|
83
127
|
|
|
84
128
|
## โ๏ธ Supported Versions
|
|
85
129
|
|
|
86
|
-
| Angular Version |
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
130
|
+
| Angular Version | Status |
|
|
131
|
+
| --------------- | --------- |
|
|
132
|
+
| Angular 21 | โ
Stable |
|
|
133
|
+
| Angular 20 | โ
Stable |
|
|
90
134
|
|
|
91
|
-
|
|
135
|
+
---
|
|
92
136
|
|
|
93
|
-
|
|
137
|
+
## โ๏ธ Configuration
|
|
94
138
|
|
|
95
139
|
```typescript
|
|
96
140
|
import { provideThemeStack } from 'ngx-theme-stack';
|
|
@@ -98,34 +142,38 @@ import { provideThemeStack } from 'ngx-theme-stack';
|
|
|
98
142
|
export const appConfig: ApplicationConfig = {
|
|
99
143
|
providers: [
|
|
100
144
|
provideThemeStack({
|
|
101
|
-
themes: ['light', 'dark', 'sunset'], //
|
|
102
|
-
defaultTheme: 'system', //
|
|
103
|
-
mode: 'class', // 'class'
|
|
104
|
-
strategy: 'critters', // 'critters'
|
|
105
|
-
storageKey: 'ngx-theme-stack', //
|
|
145
|
+
themes: ['light', 'dark', 'sunset'], // your theme identifiers
|
|
146
|
+
defaultTheme: 'system', // resolves via matchMedia
|
|
147
|
+
mode: 'class', // 'class' | 'attribute' | 'both'
|
|
148
|
+
strategy: 'critters', // 'critters' | 'blocking'
|
|
149
|
+
storageKey: 'ngx-theme-stack', // localStorage key
|
|
106
150
|
}),
|
|
107
151
|
],
|
|
108
152
|
};
|
|
109
153
|
```
|
|
110
154
|
|
|
111
|
-
|
|
112
|
-
| :------------- | :----------- | :---------------------------- | :------------------------------------------------------------------------ |
|
|
113
|
-
| `themes` | `string[]` | `['light', 'dark', 'system']` | List of supported theme identifiers. |
|
|
114
|
-
| `defaultTheme` | `string` | `'system'` | Theme used on first visit or when no preference is saved. |
|
|
115
|
-
| `mode` | `NgMode` | `'class'` | How the theme is applied: `class`, `attribute` (`data-theme`), or `both`. |
|
|
116
|
-
| `strategy` | `NgStrategy` | `'critters'` | Anti-flash performance strategy: `critters` (inlined CSS) or `blocking`. |
|
|
117
|
-
| `storageKey` | `string` | `'ngx-theme-stack'` | Key used to persist theme preference in `localStorage`. |
|
|
155
|
+
### Options reference
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
| Option | Type | Default | Description |
|
|
158
|
+
| -------------- | ------------ | ----------------------------- | ------------------------ |
|
|
159
|
+
| `themes` | `string[]` | `['light', 'dark', 'system']` | Merged with built-ins |
|
|
160
|
+
| `defaultTheme` | `string` | `'system'` | Theme on first visit |
|
|
161
|
+
| `mode` | `NgMode` | `'class'` | How the theme is applied |
|
|
162
|
+
| `strategy` | `NgStrategy` | `'critters'` | Anti-flash strategy |
|
|
163
|
+
| `storageKey` | `string` | `'ngx-theme-stack'` | Persistence key |
|
|
121
164
|
|
|
122
|
-
|
|
165
|
+
> [!NOTE]
|
|
166
|
+
> Custom themes are **merged** with built-ins. Passing `['sepia', 'ocean']` resolves to `['system', 'light', 'dark', 'sepia', 'ocean']`. After config changes, run:
|
|
167
|
+
>
|
|
168
|
+
> ```bash
|
|
169
|
+
> ng generate ngx-theme-stack:sync --project YOUR_PROJECT_NAME
|
|
170
|
+
> ```
|
|
123
171
|
|
|
124
|
-
|
|
172
|
+
---
|
|
125
173
|
|
|
126
|
-
|
|
174
|
+
## ๐ ๏ธ Usage
|
|
127
175
|
|
|
128
|
-
|
|
176
|
+
### 1 โ Simple toggle (dark/light)
|
|
129
177
|
|
|
130
178
|
```typescript
|
|
131
179
|
import { inject } from '@angular/core';
|
|
@@ -143,9 +191,7 @@ export class ThemeToggleComponent {
|
|
|
143
191
|
}
|
|
144
192
|
```
|
|
145
193
|
|
|
146
|
-
### 2
|
|
147
|
-
|
|
148
|
-
Use `ThemeCycleService` to rotate through all your configured themes in order.
|
|
194
|
+
### 2 โ Multi-theme cycle
|
|
149
195
|
|
|
150
196
|
```typescript
|
|
151
197
|
import { inject } from '@angular/core';
|
|
@@ -155,7 +201,7 @@ import { ThemeCycleService } from 'ngx-theme-stack';
|
|
|
155
201
|
selector: 'app-theme-cycler',
|
|
156
202
|
standalone: true,
|
|
157
203
|
template: `
|
|
158
|
-
<button (click)="theme.cycle()">Next
|
|
204
|
+
<button (click)="theme.cycle()">Next: {{ theme.upcoming() }}</button>
|
|
159
205
|
<p>Theme {{ theme.cycleIndex() + 1 }} of {{ theme.availableThemes.length }}</p>
|
|
160
206
|
`,
|
|
161
207
|
})
|
|
@@ -164,9 +210,7 @@ export class ThemeCycleComponent {
|
|
|
164
210
|
}
|
|
165
211
|
```
|
|
166
212
|
|
|
167
|
-
### 3
|
|
168
|
-
|
|
169
|
-
Use `ThemeSelectService` to build selection interfaces like dropdowns or radio groups.
|
|
213
|
+
### 3 โ Direct selection (dropdowns/lists)
|
|
170
214
|
|
|
171
215
|
```typescript
|
|
172
216
|
import { inject } from '@angular/core';
|
|
@@ -192,9 +236,7 @@ export class ThemeSelectComponent {
|
|
|
192
236
|
|
|
193
237
|
---
|
|
194
238
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
If you need to build custom logic or want full control over the state, use the foundational `CoreThemeService`.
|
|
239
|
+
## ๐ก๏ธ Advanced: CoreThemeService API
|
|
198
240
|
|
|
199
241
|
```typescript
|
|
200
242
|
import { inject } from '@angular/core';
|
|
@@ -204,39 +246,37 @@ import { CoreThemeService } from 'ngx-theme-stack';
|
|
|
204
246
|
export class MyAdvancedComponent {
|
|
205
247
|
themeService = inject(CoreThemeService);
|
|
206
248
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Helper boolean signals evaluating the applied theme
|
|
216
|
-
isDark = this.themeService.isDark;
|
|
217
|
-
isLight = this.themeService.isLight;
|
|
218
|
-
isSystem = this.themeService.isSystem;
|
|
219
|
-
|
|
220
|
-
// True after the first browser render. Great for preventing SSR flickering!
|
|
221
|
-
isHydrated = this.themeService.isHydrated;
|
|
222
|
-
|
|
223
|
-
/* --- ๐ ๏ธ Methods --- */
|
|
249
|
+
// โโ Signals โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
250
|
+
selectedTheme = this.themeService.selectedTheme; // chosen by user
|
|
251
|
+
resolvedTheme = this.themeService.resolvedTheme; // applied to DOM
|
|
252
|
+
isDark = this.themeService.isDark;
|
|
253
|
+
isLight = this.themeService.isLight;
|
|
254
|
+
isSystem = this.themeService.isSystem;
|
|
255
|
+
isHydrated = this.themeService.isHydrated; // true after first render
|
|
224
256
|
|
|
257
|
+
// โโ Methods โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
225
258
|
changeTheme(newTheme: string) {
|
|
226
|
-
//
|
|
227
|
-
this.themeService.setTheme(newTheme);
|
|
259
|
+
this.themeService.setTheme(newTheme); // validates + applies + persists
|
|
228
260
|
}
|
|
229
261
|
}
|
|
230
262
|
```
|
|
231
263
|
|
|
264
|
+
> [!NOTE]
|
|
265
|
+
> `isDark` and `isLight` are `false` when a custom theme is active (e.g. `'sunset'`). For custom theme guards, use `resolvedTheme()` directly:
|
|
266
|
+
>
|
|
267
|
+
> ```typescript
|
|
268
|
+
> const isSunset = computed(() => themeService.resolvedTheme() === 'sunset');
|
|
269
|
+
> ```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
232
273
|
## ๐จ Styling
|
|
233
274
|
|
|
234
|
-
|
|
275
|
+
`ng add` creates `src/themes.css` automatically. Define your CSS variables there:
|
|
235
276
|
|
|
236
277
|
```css
|
|
237
278
|
/* src/themes.css */
|
|
238
279
|
|
|
239
|
-
/* Using Classes (Default Mode) */
|
|
240
280
|
:root,
|
|
241
281
|
.light {
|
|
242
282
|
--bg-color: #ffffff;
|
|
@@ -254,55 +294,88 @@ The `ng add` command automatically creates a **`src/themes.css`** file. This is
|
|
|
254
294
|
}
|
|
255
295
|
```
|
|
256
296
|
|
|
257
|
-
|
|
297
|
+
---
|
|
258
298
|
|
|
259
|
-
|
|
299
|
+
## ๐ช๏ธ Tailwind CSS v4 Integration
|
|
260
300
|
|
|
261
|
-
###
|
|
301
|
+
### Map semantic variables (recommended)
|
|
262
302
|
|
|
263
303
|
```css
|
|
264
304
|
/* src/styles.css */
|
|
265
305
|
@import 'tailwindcss';
|
|
266
306
|
|
|
267
|
-
/* If using Class mode */
|
|
268
|
-
@custom-variant dark (&:where(.dark, .dark *));
|
|
269
|
-
|
|
270
|
-
/* If using Attribute mode */
|
|
271
|
-
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### 2. Map Semantic Variables
|
|
275
|
-
|
|
276
|
-
```css
|
|
277
307
|
@theme {
|
|
278
308
|
--color-main-bg: var(--bg-color);
|
|
279
309
|
--color-main-text: var(--text-color);
|
|
280
310
|
}
|
|
281
311
|
```
|
|
282
312
|
|
|
283
|
-
###
|
|
284
|
-
|
|
285
|
-
Now you can write clean, theme-aware classes:
|
|
313
|
+
### Use in components โ no `dark:` prefix needed
|
|
286
314
|
|
|
287
315
|
```html
|
|
288
316
|
<div class="bg-main-bg text-main-text shadow-xl">
|
|
289
|
-
<!--
|
|
317
|
+
<!-- automatically reflects the active theme -->
|
|
290
318
|
</div>
|
|
291
319
|
```
|
|
292
320
|
|
|
293
|
-
|
|
321
|
+
> **Why this works:** CSS variables are set on `<html>` before Angular boots. Tailwind tokens point directly to those variables, covering all themes (dark, light, sunset, etc.) without extra configuration.
|
|
294
322
|
|
|
295
|
-
|
|
323
|
+
<details>
|
|
324
|
+
<summary><strong>Optional: enable the <code>dark:</code> prefix</strong></summary>
|
|
296
325
|
|
|
297
|
-
|
|
298
|
-
2. **Blocking**: Best for standard SPAs. Loads `themes.css` as a traditional blocking resource.
|
|
326
|
+
Only needed if you want `dark:` utilities tied to ngx-theme-stack's toggle:
|
|
299
327
|
|
|
300
|
-
|
|
328
|
+
```css
|
|
329
|
+
/* Class mode */
|
|
330
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
301
331
|
|
|
302
|
-
|
|
303
|
-
|
|
332
|
+
/* Attribute mode */
|
|
333
|
+
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
|
304
334
|
```
|
|
305
335
|
|
|
336
|
+
> **โ ๏ธ** This disconnects `dark:` from OS preference and only covers the built-in `dark` theme. For multi-theme support, prefer the CSS variable approach above.
|
|
337
|
+
|
|
338
|
+
</details>
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## โก Performance Strategies
|
|
343
|
+
|
|
344
|
+
### How the theme is applied on first load
|
|
345
|
+
|
|
346
|
+
`ng add` injects a minimal blocking script as the **first child of `<head>`**. It runs before any stylesheet or Angular bundle:
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
1. Read stored theme from localStorage
|
|
350
|
+
2. If 'system' โ resolve OS preference via matchMedia('prefers-color-scheme: dark')
|
|
351
|
+
3. Apply theme to <html> (class, attribute, or both)
|
|
352
|
+
4. Set color-scheme CSS property for native browser adaptation
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Strategy comparison
|
|
356
|
+
|
|
357
|
+
| | critters (default) | blocking |
|
|
358
|
+
| ------------------------- | ---------------------------------- | ------------------------------------------ |
|
|
359
|
+
| **How it works** | Inlines all CSS vars into `<head>` | Loads `themes.css` as render-blocking file |
|
|
360
|
+
| **Network requests** | Zero | One (then cached) |
|
|
361
|
+
| **Flash risk** | None | None |
|
|
362
|
+
| **Works with CSR** | โ
| โ
|
|
|
363
|
+
| **Works with SSR/SSG** | โ
| โ |
|
|
364
|
+
| **Strict CSP compatible** | โ requires `unsafe-inline` | โ
|
|
|
365
|
+
| **Best for** | Most apps | Strict CSP, many themes |
|
|
366
|
+
|
|
367
|
+
<details>
|
|
368
|
+
<summary><strong>When to choose blocking over critters</strong></summary>
|
|
369
|
+
|
|
370
|
+
- **Strict CSP** โ Critters generates inline `<style>` tags requiring `'unsafe-inline'` in `style-src`
|
|
371
|
+
- **Many themes** โ All theme variables get inlined into HTML on every request; a cached file is more efficient
|
|
372
|
+
- **Critters conflicts** โ Complex CSS pipelines (PostCSS, CSS Modules) can conflict with Critters
|
|
373
|
+
- **Simpler debugging** โ An explicit stylesheet is easier to inspect in DevTools
|
|
374
|
+
|
|
375
|
+
</details>
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
306
379
|
## ๐ License
|
|
307
380
|
|
|
308
|
-
[MIT](
|
|
381
|
+
[MIT](https://github.com/WanderleeDev/ngx-theme-stack/blob/main/LICENSE)
|
|
@@ -70,8 +70,10 @@ const NGX_THEME_STACK_CONFIG = new InjectionToken('NGX_THEME_STACK_CONFIG', {
|
|
|
70
70
|
/**
|
|
71
71
|
* Provides Theme Stack configuration to Angular's DI system.
|
|
72
72
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* The `themes` option accepts **additional** theme identifiers beyond the
|
|
74
|
+
* built-in defaults. They are merged with `'light'`, `'dark'`, and `'system'`
|
|
75
|
+
* so that the resolved {@link NgConfig.themes} array always includes all of
|
|
76
|
+
* them โ your custom values are appended after the built-ins.
|
|
75
77
|
*
|
|
76
78
|
* **Defaults:**
|
|
77
79
|
* - `themes`: `['light', 'dark', 'system']`
|
|
@@ -99,7 +101,7 @@ const NGX_THEME_STACK_CONFIG = new InjectionToken('NGX_THEME_STACK_CONFIG', {
|
|
|
99
101
|
* provideThemeStack()
|
|
100
102
|
*
|
|
101
103
|
* @example
|
|
102
|
-
* //
|
|
104
|
+
* // Critters strategy โ inlines all theme CSS in <head> (works for CSR, SSR, and SSG)
|
|
103
105
|
* provideThemeStack({
|
|
104
106
|
* strategy: 'critters',
|
|
105
107
|
* mode: 'class',
|
|
@@ -157,10 +159,7 @@ class CoreThemeService {
|
|
|
157
159
|
#document = inject(DOCUMENT);
|
|
158
160
|
#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
159
161
|
// โโ Theme configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
160
|
-
/**
|
|
161
|
-
* The initial stored theme read from localStorage.
|
|
162
|
-
* This is used to determine the initial theme of the application.
|
|
163
|
-
*/
|
|
162
|
+
/** The initial stored theme read from localStorage on startup. */
|
|
164
163
|
#initialStoredTheme = this.readStoredTheme();
|
|
165
164
|
/** List of available themes for Select/Cycle services. Defaults to ['system', 'light', 'dark']. */
|
|
166
165
|
availableThemes = this.#config.themes;
|
|
@@ -187,9 +186,19 @@ class CoreThemeService {
|
|
|
187
186
|
const theme = this.#selectedTheme();
|
|
188
187
|
return theme === 'system' ? this.#systemPreference() : theme;
|
|
189
188
|
}, ...(ngDevMode ? [{ debugName: "resolvedTheme" }] : /* istanbul ignore next */ []));
|
|
190
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Whether the currently applied theme is dark.
|
|
191
|
+
*
|
|
192
|
+
* Returns `false` when a custom theme (e.g. `'sunset'`) is active โ use
|
|
193
|
+
* `resolvedTheme()` directly if you need to inspect the current custom theme.
|
|
194
|
+
*/
|
|
191
195
|
isDark = computed(() => this.resolvedTheme() === 'dark', ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
|
|
192
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Whether the currently applied theme is light.
|
|
198
|
+
*
|
|
199
|
+
* Returns `false` when a custom theme (e.g. `'sepia'`) is active โ use
|
|
200
|
+
* `resolvedTheme()` directly if you need to inspect the current custom theme.
|
|
201
|
+
*/
|
|
193
202
|
isLight = computed(() => this.resolvedTheme() === 'light', ...(ngDevMode ? [{ debugName: "isLight" }] : /* istanbul ignore next */ []));
|
|
194
203
|
/** Whether the currently applied theme is system. */
|
|
195
204
|
isSystem = computed(() => this.selectedTheme() === 'system', ...(ngDevMode ? [{ debugName: "isSystem" }] : /* istanbul ignore next */ []));
|