ngx-theme-stack 3.1.0 → 3.2.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 +116 -75
- package/fesm2022/ngx-theme-stack.mjs +19 -7
- package/fesm2022/ngx-theme-stack.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/ng-add/app-config.d.ts +6 -8
- package/schematics/ng-add/app-config.js +180 -43
- package/schematics/ng-add/app-config.js.map +1 -1
- package/schematics/ng-add/constants.d.ts +1 -1
- package/schematics/ng-add/constants.js +1 -1
- package/schematics/ng-add/constants.js.map +1 -1
- package/schematics/ng-add/index.d.ts +0 -5
- package/schematics/ng-add/index.js +58 -81
- package/schematics/ng-add/index.js.map +1 -1
- package/types/ngx-theme-stack.d.ts +9 -1
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# ngx-theme-stack 🎨
|
|
2
2
|
|
|
3
|
-

|
|
3
|
+

|
|
4
4
|
|
|
5
5
|
A simple and powerful headless theme manager for **Angular**. Built for performance and SSR support.
|
|
6
6
|
|
|
7
|
+
[**🌐 Live Demo**](https://demo-ngx-theme-stack.wanderlee.site/) | [**📚 Documentation**](https://ngx-theme-stack-docs.wanderlee.site/) | [**⭐ Star on GitHub**](https://github.com/WanderleeDev/ngx-theme-stack)
|
|
8
|
+
|
|
7
9
|
## 🚀 Features
|
|
8
10
|
|
|
9
11
|
- ⚡ **Single Command Installation**: Automatic configuration via `ng add`.
|
|
@@ -24,6 +26,15 @@ ng add ngx-theme-stack
|
|
|
24
26
|
|
|
25
27
|
### Installation Modes
|
|
26
28
|
|
|
29
|
+
> [!TIP]
|
|
30
|
+
> **🚀 Using Bun?**
|
|
31
|
+
> Since `ng add` is currently not supported for Bun environments, please use this two-step process:
|
|
32
|
+
>
|
|
33
|
+
> 1. **Install:** `bun add ngx-theme-stack`
|
|
34
|
+
> 2. **Configure:** `ng generate ngx-theme-stack:ng-add`
|
|
35
|
+
>
|
|
36
|
+
> This ensures Bun handles the dependency management while the schematic automates the code configuration (providers, index.html, tokens, etc.).
|
|
37
|
+
|
|
27
38
|
When running `ng add`, you will be presented with two configuration options:
|
|
28
39
|
|
|
29
40
|
1. **Quick Mode**:
|
|
@@ -44,12 +55,14 @@ When running `ng add`, you will be presented with two configuration options:
|
|
|
44
55
|
|
|
45
56
|
To provide a "Zero Config" experience, the installation command automates the following:
|
|
46
57
|
|
|
47
|
-
1. **`
|
|
48
|
-
2. **`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
1. **`app.config.ts` (or `main.ts`)**: Injects `provideThemeStack()` into your providers array. It's **Smart**: it uses AST to follow imports and find your providers even if they are delegated to external files.
|
|
59
|
+
2. **`index.html`**: Injects the blocking anti-flash script into the `<head>` to ensure a seamless theme experience.
|
|
60
|
+
3. **`package.json`**: Adds a `"prebuild"` script to automate theme synchronization.
|
|
61
|
+
4. **`angular.json`**: Registers `themes.css` and optimizes build configurations.
|
|
62
|
+
5. **`themes.css`**: Scaffolds your base theme tokens if they don't exist.
|
|
63
|
+
|
|
64
|
+
> [!TIP]
|
|
65
|
+
> **Re-configuration support:** You can run `ng add` multiple times. If you change your mind about the `storageKey` or the `mode`, the schematic will update your existing code and script automatically without duplicating them.
|
|
53
66
|
|
|
54
67
|
## 🏗️ Architecture & Extensibility
|
|
55
68
|
|
|
@@ -64,7 +77,7 @@ For common use cases, we include three services with predefined logic:
|
|
|
64
77
|
|
|
65
78
|
1. **`ThemeToggleService`**: A simple binary switch between `light` and `dark`.
|
|
66
79
|
2. **`ThemeSelectService`**: Exposes the full list of themes and methods to select them.
|
|
67
|
-
3. **`ThemeCycleService`**: A circular function to cycle through all available themes
|
|
80
|
+
3. **`ThemeCycleService`**: A circular function to cycle through all available themes; exposes `upcoming`, `preceding`, and `cycleIndex` signals for UI feedback.
|
|
68
81
|
|
|
69
82
|
---
|
|
70
83
|
|
|
@@ -86,38 +99,109 @@ export const appConfig: ApplicationConfig = {
|
|
|
86
99
|
providers: [
|
|
87
100
|
provideThemeStack({
|
|
88
101
|
themes: ['light', 'dark', 'sunset'], // Your theme identifiers
|
|
89
|
-
defaultTheme: 'system',
|
|
90
|
-
mode: 'class',
|
|
91
|
-
strategy: 'critters',
|
|
92
|
-
storageKey: 'ngx-theme-stack
|
|
93
|
-
})
|
|
94
|
-
]
|
|
102
|
+
defaultTheme: 'system', // Initial fallback ('system' resolves via matchMedia)
|
|
103
|
+
mode: 'class', // 'class', 'attribute' or 'both'
|
|
104
|
+
strategy: 'critters', // 'critters' (SSR) or 'blocking' (Standard SPA)
|
|
105
|
+
storageKey: 'ngx-theme-stack', // LocalStorage key
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
95
108
|
};
|
|
96
109
|
```
|
|
97
110
|
|
|
98
|
-
| Option
|
|
99
|
-
|
|
|
100
|
-
| `themes`
|
|
101
|
-
| `defaultTheme` | `string`
|
|
102
|
-
| `mode`
|
|
103
|
-
| `strategy`
|
|
104
|
-
| `storageKey`
|
|
111
|
+
| Option | Type | Default | Description |
|
|
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`. |
|
|
105
118
|
|
|
106
119
|
> [!IMPORTANT]
|
|
107
120
|
> Whenever you update these settings, run `ng generate ngx-theme-stack:sync` to ensure your `index.html` is updated with the correct anti-flash script.
|
|
108
121
|
|
|
109
|
-
## 🛠️
|
|
122
|
+
## 🛠️ Usage
|
|
123
|
+
|
|
124
|
+
For most use cases, we recommend using the built-in **Utility Services**. They provide ready-to-use logic for common patterns.
|
|
125
|
+
|
|
126
|
+
### 1. Simple Toggle (Dark/Light)
|
|
127
|
+
|
|
128
|
+
Use `ThemeToggleService` when you only need a simple switch.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { inject } from '@angular/core';
|
|
132
|
+
import { ThemeToggleService } from 'ngx-theme-stack';
|
|
133
|
+
|
|
134
|
+
@Component({
|
|
135
|
+
selector: 'app-theme-toggle',
|
|
136
|
+
standalone: true,
|
|
137
|
+
template: `
|
|
138
|
+
<button (click)="toggle.toggle()">Switch to {{ toggle.isDark() ? 'Light' : 'Dark' }}</button>
|
|
139
|
+
`,
|
|
140
|
+
})
|
|
141
|
+
export class ThemeToggleComponent {
|
|
142
|
+
protected toggle = inject(ThemeToggleService);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 2. Multi-theme Cycle
|
|
147
|
+
|
|
148
|
+
Use `ThemeCycleService` to rotate through all your configured themes in order.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { inject } from '@angular/core';
|
|
152
|
+
import { ThemeCycleService } from 'ngx-theme-stack';
|
|
153
|
+
|
|
154
|
+
@Component({
|
|
155
|
+
selector: 'app-theme-cycler',
|
|
156
|
+
standalone: true,
|
|
157
|
+
template: `
|
|
158
|
+
<button (click)="theme.cycle()">Next theme: {{ theme.upcoming() }}</button>
|
|
159
|
+
<p>Theme {{ theme.cycleIndex() + 1 }} of {{ theme.availableThemes.length }}</p>
|
|
160
|
+
`,
|
|
161
|
+
})
|
|
162
|
+
export class ThemeCycleComponent {
|
|
163
|
+
protected theme = inject(ThemeCycleService);
|
|
164
|
+
}
|
|
165
|
+
```
|
|
110
166
|
|
|
111
|
-
###
|
|
167
|
+
### 3. Direct Selection (Dropdowns/Lists)
|
|
112
168
|
|
|
113
|
-
|
|
169
|
+
Use `ThemeSelectService` to build selection interfaces like dropdowns or radio groups.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { inject } from '@angular/core';
|
|
173
|
+
import { ThemeSelectService } from 'ngx-theme-stack';
|
|
174
|
+
import { FormsModule } from '@angular/forms';
|
|
175
|
+
|
|
176
|
+
@Component({
|
|
177
|
+
selector: 'app-theme-selector',
|
|
178
|
+
standalone: true,
|
|
179
|
+
imports: [FormsModule],
|
|
180
|
+
template: `
|
|
181
|
+
<select [ngModel]="theme.selectedTheme()" (ngModelChange)="theme.select($event)">
|
|
182
|
+
@for (t of theme.availableThemes; track t) {
|
|
183
|
+
<option [value]="t">{{ t }}</option>
|
|
184
|
+
}
|
|
185
|
+
</select>
|
|
186
|
+
`,
|
|
187
|
+
})
|
|
188
|
+
export class ThemeSelectComponent {
|
|
189
|
+
protected theme = inject(ThemeSelectService);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### 🛡️ Advanced: CoreThemeService API
|
|
196
|
+
|
|
197
|
+
If you need to build custom logic or want full control over the state, use the foundational `CoreThemeService`.
|
|
114
198
|
|
|
115
199
|
```typescript
|
|
116
200
|
import { inject } from '@angular/core';
|
|
117
201
|
import { CoreThemeService } from 'ngx-theme-stack';
|
|
118
202
|
|
|
119
203
|
@Component({ ... })
|
|
120
|
-
export class
|
|
204
|
+
export class MyAdvancedComponent {
|
|
121
205
|
themeService = inject(CoreThemeService);
|
|
122
206
|
|
|
123
207
|
/* --- 📊 Reactive Signals --- */
|
|
@@ -145,29 +229,9 @@ export class MyComponent {
|
|
|
145
229
|
}
|
|
146
230
|
```
|
|
147
231
|
|
|
148
|
-
### Utility Services Examples
|
|
149
|
-
|
|
150
|
-
#### ThemeToggleService usage:
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
import { ThemeToggleService } from 'ngx-theme-stack';
|
|
154
|
-
|
|
155
|
-
@Component({
|
|
156
|
-
selector: 'app-theme-toggle',
|
|
157
|
-
template: `
|
|
158
|
-
<button (click)="toggle.toggle()">Switch to {{ toggle.isDark() ? 'Light' : 'Dark' }}</button>
|
|
159
|
-
`,
|
|
160
|
-
})
|
|
161
|
-
export class ThemeToggleComponent {
|
|
162
|
-
protected toggle = inject(ThemeToggleService);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
232
|
## 🎨 Styling
|
|
167
233
|
|
|
168
|
-
The `ng add` command automatically creates a **`src/themes.css`** file
|
|
169
|
-
|
|
170
|
-
The library targets the `<html>` element. Based on your configured `mode`, you should define your variables like this:
|
|
234
|
+
The `ng add` command automatically creates a **`src/themes.css`** file. This is where you should define your theme-specific CSS variables.
|
|
171
235
|
|
|
172
236
|
```css
|
|
173
237
|
/* src/themes.css */
|
|
@@ -188,32 +252,14 @@ The library targets the `<html>` element. Based on your configured `mode`, you s
|
|
|
188
252
|
--bg-color: #ff5f6d;
|
|
189
253
|
--text-color: #ffffff;
|
|
190
254
|
}
|
|
191
|
-
|
|
192
|
-
/* Using Attributes (Attribute Mode) */
|
|
193
|
-
[data-theme='light'] {
|
|
194
|
-
--bg-color: #ffffff;
|
|
195
|
-
--text-color: #333333;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
[data-theme='dark'] {
|
|
199
|
-
--bg-color: #121212;
|
|
200
|
-
--text-color: #ffffff;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
[data-theme='sunset'] {
|
|
204
|
-
--bg-color: #ff5f6d;
|
|
205
|
-
--text-color: #ffffff;
|
|
206
|
-
}
|
|
207
255
|
```
|
|
208
256
|
|
|
209
257
|
## 🌪️ Tailwind CSS v4 Integration
|
|
210
258
|
|
|
211
|
-
If you are using **Tailwind CSS v4**, you can achieve a much cleaner HTML by mapping your `themes.css` variables to your Tailwind theme.
|
|
259
|
+
If you are using **Tailwind CSS v4**, you can achieve a much cleaner HTML by mapping your `themes.css` variables to your Tailwind theme.
|
|
212
260
|
|
|
213
261
|
### 1. Configure Custom Variants
|
|
214
262
|
|
|
215
|
-
In your main `styles.css`, define how Tailwind should detect your themes:
|
|
216
|
-
|
|
217
263
|
```css
|
|
218
264
|
/* src/styles.css */
|
|
219
265
|
@import 'tailwindcss';
|
|
@@ -227,19 +273,16 @@ In your main `styles.css`, define how Tailwind should detect your themes:
|
|
|
227
273
|
|
|
228
274
|
### 2. Map Semantic Variables
|
|
229
275
|
|
|
230
|
-
Extend your Tailwind theme using the variables defined in `themes.css`:
|
|
231
|
-
|
|
232
276
|
```css
|
|
233
277
|
@theme {
|
|
234
278
|
--color-main-bg: var(--bg-color);
|
|
235
279
|
--color-main-text: var(--text-color);
|
|
236
|
-
--color-card-bg: var(--card-bg);
|
|
237
280
|
}
|
|
238
281
|
```
|
|
239
282
|
|
|
240
283
|
### 3. Usage in Components
|
|
241
284
|
|
|
242
|
-
Now
|
|
285
|
+
Now you can write clean, theme-aware classes:
|
|
243
286
|
|
|
244
287
|
```html
|
|
245
288
|
<div class="bg-main-bg text-main-text shadow-xl">
|
|
@@ -247,16 +290,14 @@ Now, instead of writing `<div class="bg-white dark:bg-black">`, you simply write
|
|
|
247
290
|
</div>
|
|
248
291
|
```
|
|
249
292
|
|
|
250
|
-
This approach keeps your UI code clean, semantic, and fully synchronized with `ngx-theme-stack`.
|
|
251
|
-
|
|
252
293
|
## ⚡ Performance Strategies
|
|
253
294
|
|
|
254
|
-
`ngx-theme-stack` offers two ways to handle the initial theme application to prevent
|
|
295
|
+
`ngx-theme-stack` offers two ways to handle the initial theme application to prevent white flashes:
|
|
255
296
|
|
|
256
|
-
1. **Critters (Default)**: Best for SSR/Static sites.
|
|
257
|
-
2. **Blocking**: Best for standard SPAs.
|
|
297
|
+
1. **Critters (Default)**: Best for SSR/Static sites. Inlines CSS variables directly in the HTML `<head>`. Result: **Zero network requests for theme variables.**
|
|
298
|
+
2. **Blocking**: Best for standard SPAs. Loads `themes.css` as a traditional blocking resource.
|
|
258
299
|
|
|
259
|
-
|
|
300
|
+
Use the **Sync Command** to refresh your `index.html` if you change your configuration:
|
|
260
301
|
|
|
261
302
|
```bash
|
|
262
303
|
ng generate ngx-theme-stack:sync --project YOUR_PROJECT_NAME
|
|
@@ -56,7 +56,7 @@ const DEFAULT_THEMES = ['system', 'light', 'dark'];
|
|
|
56
56
|
*/
|
|
57
57
|
const DEFAULT_NG_CONFIG = {
|
|
58
58
|
defaultTheme: 'system',
|
|
59
|
-
storageKey: 'ngx-theme-stack
|
|
59
|
+
storageKey: 'ngx-theme-stack',
|
|
60
60
|
mode: 'class',
|
|
61
61
|
strategy: 'critters',
|
|
62
62
|
themes: [...DEFAULT_THEMES],
|
|
@@ -76,7 +76,7 @@ const NGX_THEME_STACK_CONFIG = new InjectionToken('NGX_THEME_STACK_CONFIG', {
|
|
|
76
76
|
* **Defaults:**
|
|
77
77
|
* - `themes`: `['light', 'dark', 'system']`
|
|
78
78
|
* - `defaultTheme`: `'system'`
|
|
79
|
-
* - `storageKey`: `'ngx-theme-stack
|
|
79
|
+
* - `storageKey`: `'ngx-theme-stack'`
|
|
80
80
|
* - `mode`: `'class'`
|
|
81
81
|
* - `strategy`: `'critters'`
|
|
82
82
|
|
|
@@ -355,11 +355,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
355
355
|
class ThemeCycleService {
|
|
356
356
|
#core = inject(CoreThemeService);
|
|
357
357
|
/** List of all configured themes for cycling. Defaults to `['light', 'dark', 'system']`. */
|
|
358
|
-
|
|
358
|
+
availableThemes = this.#core.availableThemes;
|
|
359
359
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
360
360
|
selectedTheme = this.#core.selectedTheme;
|
|
361
361
|
/** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */
|
|
362
362
|
resolvedTheme = this.#core.resolvedTheme;
|
|
363
|
+
/** Index of the currently selected theme in the cycle. */
|
|
364
|
+
cycleIndex = computed(() => {
|
|
365
|
+
return this.availableThemes.indexOf(this.selectedTheme());
|
|
366
|
+
}, ...(ngDevMode ? [{ debugName: "cycleIndex" }] : /* istanbul ignore next */ []));
|
|
367
|
+
/** The theme that comes before the currently selected theme in the cycle. */
|
|
368
|
+
preceding = computed(() => {
|
|
369
|
+
const index = this.cycleIndex();
|
|
370
|
+
const len = this.availableThemes.length;
|
|
371
|
+
return this.availableThemes[(index - 1 + len) % len];
|
|
372
|
+
}, ...(ngDevMode ? [{ debugName: "preceding" }] : /* istanbul ignore next */ []));
|
|
373
|
+
/** The theme that comes after the currently selected theme in the cycle. */
|
|
374
|
+
upcoming = computed(() => {
|
|
375
|
+
const index = this.cycleIndex();
|
|
376
|
+
return this.availableThemes[(index + 1) % this.availableThemes.length];
|
|
377
|
+
}, ...(ngDevMode ? [{ debugName: "upcoming" }] : /* istanbul ignore next */ []));
|
|
363
378
|
/** Whether the currently applied theme is `'dark'`. */
|
|
364
379
|
isDark = this.#core.isDark;
|
|
365
380
|
/** Whether the currently applied theme is `'light'`. */
|
|
@@ -380,10 +395,7 @@ class ThemeCycleService {
|
|
|
380
395
|
* the cycle restarts from the first theme.
|
|
381
396
|
*/
|
|
382
397
|
cycle() {
|
|
383
|
-
|
|
384
|
-
const index = this.#cycle.indexOf(current);
|
|
385
|
-
const next = this.#cycle[(index + 1) % this.#cycle.length];
|
|
386
|
-
this.#core.setTheme(next);
|
|
398
|
+
this.#core.setTheme(this.upcoming());
|
|
387
399
|
}
|
|
388
400
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
389
401
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ThemeCycleService, providedIn: 'root' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-theme-stack.mjs","sources":["../../../projects/ngx-theme-stack/src/lib/errors.ts","../../../projects/ngx-theme-stack/src/lib/types.ts","../../../projects/ngx-theme-stack/src/lib/config/index.ts","../../../projects/ngx-theme-stack/src/lib/core/core-theme.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-cycle.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-select.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-toggle.service.ts","../../../projects/ngx-theme-stack/src/public-api.ts","../../../projects/ngx-theme-stack/src/ngx-theme-stack.ts"],"sourcesContent":["/**\n * Base error class for `ngx-theme-stack`.\n *\n * Thrown when the library configuration is invalid.\n * Consumers can use `instanceof NgxThemeStackError` to catch only\n * errors originating from this library.\n *\n * @example\n * try {\n * bootstrapApplication(AppComponent, appConfig);\n * } catch (e) {\n * if (e instanceof NgxThemeStackError) {\n * console.error('Bad ngx-theme-stack config:', e.message);\n * }\n * }\n */\nexport class NgxThemeStackError extends Error {\n override readonly name = 'NgxThemeStackError';\n\n constructor(message: string) {\n super(`[ngx-theme-stack] ${message}`);\n // Restore prototype chain (required when targeting ES5 or older)\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Runtime list of built-in themes.\n *\n * Lives here (and not in config/index.ts) because it defines a type:\n * config/index.ts already imports from types.ts, so placing DEFAULT_THEMES\n * here avoids any circular dependency.\n *\n * ⚠ KEEP IN SYNC with the duplicate in:\n * projects/ngx-theme-stack/schematics/ng-add/constants.ts → DEFAULT_THEMES\n *\n * Schematics compile to CommonJS and cannot import from this ESM file,\n * so the values are intentionally duplicated. Change both at the same time.\n */\nexport const DEFAULT_THEMES = ['system', 'light', 'dark'] as const;\n\n/** Literal union of built-in themes: `'system' | 'light' | 'dark'`. */\nexport type DefaultNgTheme = (typeof DEFAULT_THEMES)[number];\n\n/**\n * Theme type.\n *\n * - **Without** `T`: open union — accepts any `string` with IDE autocomplete\n * hints for the built-in themes (`'system' | 'light' | 'dark'`).\n * - **With** `T`: closed union — exactly `DefaultNgTheme | T`, enabling\n * full type-safety for custom theme sets.\n *\n * @example\n * NgTheme // 'system' | 'light' | 'dark' | (string & {})\n * NgTheme<'sepia'> // 'system' | 'light' | 'dark' | 'sepia'\n */\nexport type NgTheme<T extends string = string & {}> = DefaultNgTheme | T;\n\n/**\n * Resolved theme — always `'light'` or `'dark'`, never `'system'`.\n * Represents the value that comes from `matchMedia`, not user selection.\n */\nexport type NgSystemTheme = Exclude<DefaultNgTheme, 'system'>;\n\n/**\n * Theme application mode.\n * - `'attribute'`: sets `data-theme` attribute on `<html>`\n * - `'class'`: adds theme class to `<html>`\n * - `'both'`: uses both attribute and class\n */\nexport type NgMode = 'attribute' | 'class' | 'both';\n\n/**\n * Theme application strategy.\n * - `'blocking'`: theme CSS is loaded synchronously before rendering\n * - `'critters'`: theme CSS is inlined using Critters for SSR/SSG\n */\nexport type NgStrategy = 'blocking' | 'critters';\n\n/**\n * Library configuration.\n *\n * @typeParam T - Custom theme literals. Defaults to open `string`, preserving\n * backwards compatibility. Pass specific literals (e.g. `'sepia' | 'ocean'`)\n * via {@link provideThemeStack} to get a closed, type-safe theme union.\n */\nexport interface NgConfig<T extends string = string & {}> {\n /** The theme to use on first visit or when no preference is saved. Default: 'system'. */\n defaultTheme: NgTheme<T>;\n\n /** Key used to persist theme preference in localStorage. Default: 'ngx-theme-stack-theme'. */\n storageKey: string;\n\n /** \n * How the theme should be applied to the document (via class, attribute or both). \n * Default: 'class'.\n */\n mode: NgMode;\n\n /** \n * Performance strategy for anti-flash. \n * Use 'critters' for SSG/SSR builds with inlined CSS, 'blocking' for standard CSS files.\n * Default: 'critters'.\n */\n strategy: NgStrategy;\n\n /** List of supported theme identifiers. Default: ['light', 'dark', 'system']. */\n themes: NgTheme<T>[];\n}\n","import { InjectionToken } from '@angular/core';\nimport { NgxThemeStackError } from '../errors';\nimport { DEFAULT_THEMES, DefaultNgTheme, NgConfig } from '../types';\n\n/**\n * ⚠ ATTENTION: SHARED CONFIGURATION VALUES\n *\n * These defaults MUST match the schematic defaults in:\n * projects/ngx-theme-stack/schematics/ng-add/constants.ts → DEFAULTS\n *\n * Schematics compile to CommonJS and cannot import from this ESM file,\n * so the values are intentionally duplicated. Change both at the same time.\n *\n * If you change defaults here, also update:\n * schematics/ng-add/constants.ts → DEFAULTS + DEFAULT_THEMES\n */\n\nexport const DEFAULT_NG_CONFIG = {\n defaultTheme: 'system',\n storageKey: 'ngx-theme-stack-theme',\n mode: 'class',\n strategy: 'critters',\n themes: [...DEFAULT_THEMES],\n} satisfies NgConfig;\n\n// The token uses NgConfig<string> because Angular DI resolves types at runtime\n// and cannot carry generic parameters. Type-safety is enforced at the\n// provideThemeStack() call site instead.\nexport const NGX_THEME_STACK_CONFIG = new InjectionToken<NgConfig<string>>(\n 'NGX_THEME_STACK_CONFIG',\n {\n factory: () => DEFAULT_NG_CONFIG,\n },\n);\n\n/**\n * Provides Theme Stack configuration to Angular's DI system.\n *\n * Custom `themes` are **merged** with the built-in defaults\n * (`'light'`, `'dark'`, `'system'`), so you never lose the base themes.\n *\n * **Defaults:**\n * - `themes`: `['light', 'dark', 'system']`\n * - `defaultTheme`: `'system'`\n * - `storageKey`: `'ngx-theme-stack-theme'`\n * - `mode`: `'class'`\n * - `strategy`: `'critters'`\n\n *\n * The type parameter `T` is **inferred automatically** from the `themes` array\n * when passed as a `const` — no need to specify it manually.\n *\n * @typeParam T - Custom theme string literals, inferred from the `themes` option.\n *\n * @param config - Optional partial configuration. Omitted fields fall back to\n * {@link DEFAULT_NG_CONFIG}.\n *\n * @throws {@link NgxThemeStackError}\n * - If any entry in `themes` is an empty or whitespace-only string.\n * - If `defaultTheme` is not present in the resolved (merged) themes array.\n * - If `storageKey` is an empty or whitespace-only string.\n *\n * @example\n * // Default — uses built-in themes and sensible defaults\n * provideThemeStack()\n *\n * @example\n * // SSR/SSG Optimization — uses Critters inlining strategy\n * provideThemeStack({\n * strategy: 'critters',\n * mode: 'class',\n * })\n *\n * @example\n * // Closed union: TypeScript infers 'sepia' | 'ocean' from the array\n * provideThemeStack({\n * themes: ['sepia', 'ocean'] as const,\n * defaultTheme: 'sepia', // ✅ in resolved themes\n * // defaultTheme: 'nope', // ❌ throws NgxThemeStackError at runtime\n * })\n *\n * @example\n * // Custom storage key and mode\n * provideThemeStack({\n * storageKey: 'my-app-theme',\n * mode: 'attribute',\n * })\n */\nexport function provideThemeStack<const T extends string = DefaultNgTheme>(\n config: Partial<NgConfig<T>> = {},\n) {\n config.themes?.forEach((t) => {\n if (t.trim() === '') throw new NgxThemeStackError('Theme cannot be empty or whitespace.');\n });\n\n const themes = config.themes\n ? Array.from(new Set([...DEFAULT_NG_CONFIG.themes, ...config.themes]))\n : DEFAULT_NG_CONFIG.themes;\n\n if (config.defaultTheme && !(themes as string[]).includes(config.defaultTheme as string)) {\n throw new NgxThemeStackError(\n `\"defaultTheme\" must be one of the resolved themes: [${themes.join(', ')}].`,\n );\n }\n\n if (config.storageKey !== undefined && config.storageKey.trim() === '') {\n throw new NgxThemeStackError('\"storageKey\" cannot be empty or whitespace.');\n }\n\n return {\n provide: NGX_THEME_STACK_CONFIG,\n useValue: {\n ...DEFAULT_NG_CONFIG,\n ...config,\n themes,\n } as NgConfig<string>,\n };\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n DestroyRef,\n DOCUMENT,\n effect,\n inject,\n Injectable,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\nimport { NGX_THEME_STACK_CONFIG } from '../config';\nimport { NgxThemeStackError } from '../errors';\nimport { NgSystemTheme, NgTheme } from '../types';\n\n/**\n * Core service for managing the application's color theme.\n *\n * Handles theme persistence, system preference detection, and DOM updates.\n * Supports built-in themes ('dark', 'light', 'system') and custom extensions.\n */\n@Injectable({ providedIn: 'root' })\nexport class CoreThemeService {\n // ── Dependencies ──────────────────────────────────────────────────────────\n\n readonly #config = inject(NGX_THEME_STACK_CONFIG);\n readonly #destroyRef = inject(DestroyRef);\n readonly #document = inject(DOCUMENT);\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n // ── Theme configuration ───────────────────────────────────────────────────\n\n /**\n * The initial stored theme read from localStorage.\n * This is used to determine the initial theme of the application.\n */\n readonly #initialStoredTheme = this.readStoredTheme();\n\n /** List of available themes for Select/Cycle services. Defaults to ['system', 'light', 'dark']. */\n readonly availableThemes = this.#config.themes;\n\n /** Internal Set for O(1) existence checks. */\n readonly #validThemes = new Set<NgTheme>(this.availableThemes);\n\n /**\n * The anti-flash class to remove from the host element.\n * Internal mechanism to bridge the gap between the blocking script's\n * initial DOM state and Angular's first effect run.\n */\n #antiFlashClass: string | null = null;\n\n // ── System preference ─────────────────────────────────────────────────────\n\n /** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */\n readonly #mediaQuery: MediaQueryList | null = this.#isBrowser\n ? (this.#document.defaultView?.matchMedia('(prefers-color-scheme: dark)') ?? null)\n : null;\n\n readonly #systemPreference = signal<NgSystemTheme>(this.resolveSystemPreference());\n\n // ── Theme state ───────────────────────────────────────────────────────────\n\n readonly #selectedTheme = signal<NgTheme>(this.resolveInitialTheme());\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#selectedTheme.asReadonly();\n\n /** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) — never `'system'`. */\n readonly resolvedTheme = computed(() => {\n const theme = this.#selectedTheme();\n return theme === 'system' ? this.#systemPreference() : theme;\n });\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = computed(() => this.resolvedTheme() === 'dark');\n\n /** Whether the currently applied theme is light. */\n readonly isLight = computed(() => this.resolvedTheme() === 'light');\n\n /** Whether the currently applied theme is system. */\n readonly isSystem = computed(() => this.selectedTheme() === 'system');\n\n /**\n * Whether the service has completed client-side initialization.\n *\n * `false` during SSR and on the very first render pass before the initial theme\n * is resolved from `localStorage`. Becomes `true` immediately after the\n * first browser render pass.\n *\n * **Important:** Guard template elements that display `selectedTheme()` or\n * `resolvedTheme()` behind this signal to prevent hydration-mismatch flashes\n * (e.g. if the server renders the default 'system' but the user has 'dark' stored).\n *\n * @example\n * ```html\n * {{ theme.isHydrated() ? theme.selectedTheme() : '...' }}\n * ```\n */\n readonly isHydrated = signal(false);\n\n // ── Event handler ─────────────────────────────────────────────────────────\n\n readonly #onSystemPreferenceChange = () =>\n this.#systemPreference.set(this.resolveSystemPreference());\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n\n constructor() {\n this.captureAntiFlashClass();\n\n if (this.#isBrowser && this.#selectedTheme() === 'system') {\n this.startSystemThemeListener();\n }\n\n effect(() => this.applyThemeToDOM(this.resolvedTheme()));\n afterNextRender(() => this.isHydrated.set(true));\n this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Changes the active theme.\n *\n * Persists the choice explicitly so that switching e.g. from `'system'` to\n * `'light'` is saved even when the resolved theme did not change\n * (system preference was already `'light'`).\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n public setTheme(theme: NgTheme): void {\n if (!this.#validThemes.has(theme)) {\n throw new NgxThemeStackError(\n `Invalid theme: \"${theme}\". Valid values are: ${[...this.#validThemes].join(', ')}.`,\n );\n }\n if (!this.#isBrowser) return;\n if (theme === this.#selectedTheme()) return;\n if (theme === 'system') {\n this.#systemPreference.set(this.resolveSystemPreference());\n this.startSystemThemeListener();\n } else {\n this.stopSystemThemeListener();\n }\n\n this.#selectedTheme.set(theme);\n this.saveTheme(theme);\n }\n\n // ── Private ───────────────────────────────────────────────────────────────\n\n private resolveSystemPreference(): NgSystemTheme {\n return this.#mediaQuery?.matches ? 'dark' : 'light';\n }\n\n private resolveInitialTheme(): NgTheme {\n const theme = this.#initialStoredTheme;\n if (theme && this.#validThemes.has(theme as NgTheme)) {\n return theme as NgTheme;\n }\n return this.#config.defaultTheme;\n }\n\n private startSystemThemeListener(): void {\n if (!this.#mediaQuery) return;\n this.stopSystemThemeListener();\n this.#mediaQuery.addEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private stopSystemThemeListener(): void {\n this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private applyThemeToDOM(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n const host = this.#document.documentElement;\n\n if (this.#antiFlashClass) {\n host.classList.remove(this.#antiFlashClass);\n this.#antiFlashClass = null;\n }\n\n const { mode } = this.#config;\n\n if (mode === 'attribute' || mode === 'both') {\n this.applyThemeAttribute(host, theme);\n }\n\n if (mode === 'class' || mode === 'both') {\n this.applyThemeClasses(host, theme);\n }\n\n this.applyColorSchemeHint(host, theme);\n }\n\n private applyThemeAttribute(host: HTMLElement, theme: NgTheme): void {\n host.setAttribute('data-theme', theme);\n }\n\n private applyThemeClasses(host: HTMLElement, theme: NgTheme): void {\n host.classList.remove(...this.availableThemes);\n host.classList.add(theme);\n }\n\n private applyColorSchemeHint(host: HTMLElement, theme: NgTheme): void {\n if (theme === 'dark' || theme === 'light') {\n host.style.setProperty('color-scheme', theme);\n return;\n }\n\n host.style.removeProperty('color-scheme');\n }\n\n private captureAntiFlashClass(): void {\n if (!this.#isBrowser || !this.#initialStoredTheme) return;\n\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(this.#initialStoredTheme)) {\n this.#antiFlashClass = null;\n return;\n }\n\n if (this.#initialStoredTheme === 'system') {\n this.#antiFlashClass = this.resolveSystemPreference();\n return;\n }\n\n this.#antiFlashClass = this.#initialStoredTheme;\n }\n\n private readStoredTheme(): string | null {\n if (!this.#isBrowser) return null;\n\n try {\n return localStorage.getItem(this.#config.storageKey);\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);\n return null;\n }\n }\n\n private saveTheme(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n try {\n localStorage.setItem(this.#config.storageKey, theme);\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not save theme to localStorage.', e);\n }\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from '../core/core-theme.service';\n\n/**\n * Convenience service for cycling through themes in a fixed order.\n *\n * Default cycle: `'system'` → `'light'` → `'dark'` → `'system'` → ...\n *\n * Use this when you want to offer users a single button that rotates\n * through all available theme options.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeCycleService {\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes for cycling. Defaults to `['light', 'dark', 'system']`. */\n readonly #cycle = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** Whether the currently applied theme is `'dark'`. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is `'light'`. */\n readonly isLight = this.#core.isLight;\n\n /** Whether the user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\n\n /**\n * Advances to the next theme in the cycle.\n *\n * Cycle order is determined by the configured `themes` property in `NgConfig`.\n *\n * If the current theme is not found in the cycle (e.g. set externally),\n * the cycle restarts from the first theme.\n */\n cycle(): void {\n const current = this.#core.selectedTheme();\n const index = this.#cycle.indexOf(current);\n const next = this.#cycle[(index + 1) % this.#cycle.length];\n this.#core.setTheme(next);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from '../core/core-theme.service';\nimport { NgTheme } from '../types';\n\n/**\n * Convenience service for selecting a theme from a list.\n *\n * Use this when you want to bind a `<select>` or a group of radio/tab\n * buttons to the full set of available themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeSelectService {\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes. Defaults to `['light', 'dark', 'system']`. */\n readonly availableThemes = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** Whether the currently applied theme is `'dark'`. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is `'light'`. */\n readonly isLight = this.#core.isLight;\n\n /** Whether the user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\n\n /**\n * Applies the given theme.\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or custom.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n select(theme: NgTheme): void {\n this.#core.setTheme(theme);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from '../core/core-theme.service';\n\n/**\n * Convenience service for toggling between `'dark'` and `'light'`.\n *\n * Use this when you only need a simple on/off switch and do not\n * need to manage `'system'` or cycle through themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeToggleService {\n readonly #core = inject(CoreThemeService);\n\n /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Whether the currently applied theme is `'dark'`. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is `'light'`. */\n readonly isLight = this.#core.isLight;\n\n /** Whether the user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\n\n /**\n * Toggles between `'dark'` and `'light'`.\n *\n * If the selected theme is explicitly `'dark'`, switches to `'light'`.\n * Otherwise (including `'system'`), switches to `'dark'`.\n */\n toggle(): void {\n const next = this.#core.resolvedTheme() === 'dark' ? 'light' : 'dark';\n this.#core.setTheme(next);\n }\n}\n","/*\n * Public API Surface of ngx-theme-stack\n */\n\nexport * from './lib/core/core-theme.service';\nexport * from './lib/config';\nexport * from './lib/services/theme-cycle.service';\nexport * from './lib/services/theme-select.service';\nexport * from './lib/services/theme-toggle.service';\nexport * from './lib/types';\nexport * from './lib/errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-theme-stack.mjs","sources":["../../../projects/ngx-theme-stack/src/lib/errors.ts","../../../projects/ngx-theme-stack/src/lib/types.ts","../../../projects/ngx-theme-stack/src/lib/config/index.ts","../../../projects/ngx-theme-stack/src/lib/core/core-theme.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-cycle.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-select.service.ts","../../../projects/ngx-theme-stack/src/lib/services/theme-toggle.service.ts","../../../projects/ngx-theme-stack/src/public-api.ts","../../../projects/ngx-theme-stack/src/ngx-theme-stack.ts"],"sourcesContent":["/**\n * Base error class for `ngx-theme-stack`.\n *\n * Thrown when the library configuration is invalid.\n * Consumers can use `instanceof NgxThemeStackError` to catch only\n * errors originating from this library.\n *\n * @example\n * try {\n * bootstrapApplication(AppComponent, appConfig);\n * } catch (e) {\n * if (e instanceof NgxThemeStackError) {\n * console.error('Bad ngx-theme-stack config:', e.message);\n * }\n * }\n */\nexport class NgxThemeStackError extends Error {\n override readonly name = 'NgxThemeStackError';\n\n constructor(message: string) {\n super(`[ngx-theme-stack] ${message}`);\n // Restore prototype chain (required when targeting ES5 or older)\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Runtime list of built-in themes.\n *\n * Lives here (and not in config/index.ts) because it defines a type:\n * config/index.ts already imports from types.ts, so placing DEFAULT_THEMES\n * here avoids any circular dependency.\n *\n * ⚠ KEEP IN SYNC with the duplicate in:\n * projects/ngx-theme-stack/schematics/ng-add/constants.ts → DEFAULT_THEMES\n *\n * Schematics compile to CommonJS and cannot import from this ESM file,\n * so the values are intentionally duplicated. Change both at the same time.\n */\nexport const DEFAULT_THEMES = ['system', 'light', 'dark'] as const;\n\n/** Literal union of built-in themes: `'system' | 'light' | 'dark'`. */\nexport type DefaultNgTheme = (typeof DEFAULT_THEMES)[number];\n\n/**\n * Theme type.\n *\n * - **Without** `T`: open union — accepts any `string` with IDE autocomplete\n * hints for the built-in themes (`'system' | 'light' | 'dark'`).\n * - **With** `T`: closed union — exactly `DefaultNgTheme | T`, enabling\n * full type-safety for custom theme sets.\n *\n * @example\n * NgTheme // 'system' | 'light' | 'dark' | (string & {})\n * NgTheme<'sepia'> // 'system' | 'light' | 'dark' | 'sepia'\n */\nexport type NgTheme<T extends string = string & {}> = DefaultNgTheme | T;\n\n/**\n * Resolved theme — always `'light'` or `'dark'`, never `'system'`.\n * Represents the value that comes from `matchMedia`, not user selection.\n */\nexport type NgSystemTheme = Exclude<DefaultNgTheme, 'system'>;\n\n/**\n * Theme application mode.\n * - `'attribute'`: sets `data-theme` attribute on `<html>`\n * - `'class'`: adds theme class to `<html>`\n * - `'both'`: uses both attribute and class\n */\nexport type NgMode = 'attribute' | 'class' | 'both';\n\n/**\n * Theme application strategy.\n * - `'blocking'`: theme CSS is loaded synchronously before rendering\n * - `'critters'`: theme CSS is inlined using Critters for SSR/SSG\n */\nexport type NgStrategy = 'blocking' | 'critters';\n\n/**\n * Library configuration.\n *\n * @typeParam T - Custom theme literals. Defaults to open `string`, preserving\n * backwards compatibility. Pass specific literals (e.g. `'sepia' | 'ocean'`)\n * via {@link provideThemeStack} to get a closed, type-safe theme union.\n */\nexport interface NgConfig<T extends string = string & {}> {\n /** The theme to use on first visit or when no preference is saved. Default: 'system'. */\n defaultTheme: NgTheme<T>;\n\n /** Key used to persist theme preference in localStorage. Default: 'ngx-theme-stack-theme'. */\n storageKey: string;\n\n /** \n * How the theme should be applied to the document (via class, attribute or both). \n * Default: 'class'.\n */\n mode: NgMode;\n\n /** \n * Performance strategy for anti-flash. \n * Use 'critters' for SSG/SSR builds with inlined CSS, 'blocking' for standard CSS files.\n * Default: 'critters'.\n */\n strategy: NgStrategy;\n\n /** List of supported theme identifiers. Default: ['light', 'dark', 'system']. */\n themes: NgTheme<T>[];\n}\n","import { InjectionToken } from '@angular/core';\nimport { NgxThemeStackError } from '../errors';\nimport { DEFAULT_THEMES, DefaultNgTheme, NgConfig } from '../types';\n\n/**\n * ⚠ ATTENTION: SHARED CONFIGURATION VALUES\n *\n * These defaults MUST match the schematic defaults in:\n * projects/ngx-theme-stack/schematics/ng-add/constants.ts → DEFAULTS\n *\n * Schematics compile to CommonJS and cannot import from this ESM file,\n * so the values are intentionally duplicated. Change both at the same time.\n *\n * If you change defaults here, also update:\n * schematics/ng-add/constants.ts → DEFAULTS + DEFAULT_THEMES\n */\n\nexport const DEFAULT_NG_CONFIG = {\n defaultTheme: 'system',\n storageKey: 'ngx-theme-stack',\n mode: 'class',\n strategy: 'critters',\n themes: [...DEFAULT_THEMES],\n} satisfies NgConfig;\n\n// The token uses NgConfig<string> because Angular DI resolves types at runtime\n// and cannot carry generic parameters. Type-safety is enforced at the\n// provideThemeStack() call site instead.\nexport const NGX_THEME_STACK_CONFIG = new InjectionToken<NgConfig<string>>(\n 'NGX_THEME_STACK_CONFIG',\n {\n factory: () => DEFAULT_NG_CONFIG,\n },\n);\n\n/**\n * Provides Theme Stack configuration to Angular's DI system.\n *\n * Custom `themes` are **merged** with the built-in defaults\n * (`'light'`, `'dark'`, `'system'`), so you never lose the base themes.\n *\n * **Defaults:**\n * - `themes`: `['light', 'dark', 'system']`\n * - `defaultTheme`: `'system'`\n * - `storageKey`: `'ngx-theme-stack'`\n * - `mode`: `'class'`\n * - `strategy`: `'critters'`\n\n *\n * The type parameter `T` is **inferred automatically** from the `themes` array\n * when passed as a `const` — no need to specify it manually.\n *\n * @typeParam T - Custom theme string literals, inferred from the `themes` option.\n *\n * @param config - Optional partial configuration. Omitted fields fall back to\n * {@link DEFAULT_NG_CONFIG}.\n *\n * @throws {@link NgxThemeStackError}\n * - If any entry in `themes` is an empty or whitespace-only string.\n * - If `defaultTheme` is not present in the resolved (merged) themes array.\n * - If `storageKey` is an empty or whitespace-only string.\n *\n * @example\n * // Default — uses built-in themes and sensible defaults\n * provideThemeStack()\n *\n * @example\n * // SSR/SSG Optimization — uses Critters inlining strategy\n * provideThemeStack({\n * strategy: 'critters',\n * mode: 'class',\n * })\n *\n * @example\n * // Closed union: TypeScript infers 'sepia' | 'ocean' from the array\n * provideThemeStack({\n * themes: ['sepia', 'ocean'] as const,\n * defaultTheme: 'sepia', // ✅ in resolved themes\n * // defaultTheme: 'nope', // ❌ throws NgxThemeStackError at runtime\n * })\n *\n * @example\n * // Custom storage key and mode\n * provideThemeStack({\n * storageKey: 'my-app-theme',\n * mode: 'attribute',\n * })\n */\nexport function provideThemeStack<const T extends string = DefaultNgTheme>(\n config: Partial<NgConfig<T>> = {},\n) {\n config.themes?.forEach((t) => {\n if (t.trim() === '') throw new NgxThemeStackError('Theme cannot be empty or whitespace.');\n });\n\n const themes = config.themes\n ? Array.from(new Set([...DEFAULT_NG_CONFIG.themes, ...config.themes]))\n : DEFAULT_NG_CONFIG.themes;\n\n if (config.defaultTheme && !(themes as string[]).includes(config.defaultTheme as string)) {\n throw new NgxThemeStackError(\n `\"defaultTheme\" must be one of the resolved themes: [${themes.join(', ')}].`,\n );\n }\n\n if (config.storageKey !== undefined && config.storageKey.trim() === '') {\n throw new NgxThemeStackError('\"storageKey\" cannot be empty or whitespace.');\n }\n\n return {\n provide: NGX_THEME_STACK_CONFIG,\n useValue: {\n ...DEFAULT_NG_CONFIG,\n ...config,\n themes,\n } as NgConfig<string>,\n };\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n DestroyRef,\n DOCUMENT,\n effect,\n inject,\n Injectable,\n PLATFORM_ID,\n signal,\n} from '@angular/core';\nimport { NGX_THEME_STACK_CONFIG } from '../config';\nimport { NgxThemeStackError } from '../errors';\nimport { NgSystemTheme, NgTheme } from '../types';\n\n/**\n * Core service for managing the application's color theme.\n *\n * Handles theme persistence, system preference detection, and DOM updates.\n * Supports built-in themes ('dark', 'light', 'system') and custom extensions.\n */\n@Injectable({ providedIn: 'root' })\nexport class CoreThemeService {\n // ── Dependencies ──────────────────────────────────────────────────────────\n\n readonly #config = inject(NGX_THEME_STACK_CONFIG);\n readonly #destroyRef = inject(DestroyRef);\n readonly #document = inject(DOCUMENT);\n readonly #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n // ── Theme configuration ───────────────────────────────────────────────────\n\n /**\n * The initial stored theme read from localStorage.\n * This is used to determine the initial theme of the application.\n */\n readonly #initialStoredTheme = this.readStoredTheme();\n\n /** List of available themes for Select/Cycle services. Defaults to ['system', 'light', 'dark']. */\n readonly availableThemes = this.#config.themes;\n\n /** Internal Set for O(1) existence checks. */\n readonly #validThemes = new Set<NgTheme>(this.availableThemes);\n\n /**\n * The anti-flash class to remove from the host element.\n * Internal mechanism to bridge the gap between the blocking script's\n * initial DOM state and Angular's first effect run.\n */\n #antiFlashClass: string | null = null;\n\n // ── System preference ─────────────────────────────────────────────────────\n\n /** MediaQueryList for OS color scheme, created once and reused. Null in SSR. */\n readonly #mediaQuery: MediaQueryList | null = this.#isBrowser\n ? (this.#document.defaultView?.matchMedia('(prefers-color-scheme: dark)') ?? null)\n : null;\n\n readonly #systemPreference = signal<NgSystemTheme>(this.resolveSystemPreference());\n\n // ── Theme state ───────────────────────────────────────────────────────────\n\n readonly #selectedTheme = signal<NgTheme>(this.resolveInitialTheme());\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#selectedTheme.asReadonly();\n\n /** Resolved theme applied to the DOM. Always `'dark'` or `'light'` (or custom) — never `'system'`. */\n readonly resolvedTheme = computed(() => {\n const theme = this.#selectedTheme();\n return theme === 'system' ? this.#systemPreference() : theme;\n });\n\n /** Whether the currently applied theme is dark. */\n readonly isDark = computed(() => this.resolvedTheme() === 'dark');\n\n /** Whether the currently applied theme is light. */\n readonly isLight = computed(() => this.resolvedTheme() === 'light');\n\n /** Whether the currently applied theme is system. */\n readonly isSystem = computed(() => this.selectedTheme() === 'system');\n\n /**\n * Whether the service has completed client-side initialization.\n *\n * `false` during SSR and on the very first render pass before the initial theme\n * is resolved from `localStorage`. Becomes `true` immediately after the\n * first browser render pass.\n *\n * **Important:** Guard template elements that display `selectedTheme()` or\n * `resolvedTheme()` behind this signal to prevent hydration-mismatch flashes\n * (e.g. if the server renders the default 'system' but the user has 'dark' stored).\n *\n * @example\n * ```html\n * {{ theme.isHydrated() ? theme.selectedTheme() : '...' }}\n * ```\n */\n readonly isHydrated = signal(false);\n\n // ── Event handler ─────────────────────────────────────────────────────────\n\n readonly #onSystemPreferenceChange = () =>\n this.#systemPreference.set(this.resolveSystemPreference());\n\n // ── Lifecycle ─────────────────────────────────────────────────────────────\n\n constructor() {\n this.captureAntiFlashClass();\n\n if (this.#isBrowser && this.#selectedTheme() === 'system') {\n this.startSystemThemeListener();\n }\n\n effect(() => this.applyThemeToDOM(this.resolvedTheme()));\n afterNextRender(() => this.isHydrated.set(true));\n this.#destroyRef.onDestroy(() => this.stopSystemThemeListener());\n }\n\n // ── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Changes the active theme.\n *\n * Persists the choice explicitly so that switching e.g. from `'system'` to\n * `'light'` is saved even when the resolved theme did not change\n * (system preference was already `'light'`).\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or a custom theme name.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n public setTheme(theme: NgTheme): void {\n if (!this.#validThemes.has(theme)) {\n throw new NgxThemeStackError(\n `Invalid theme: \"${theme}\". Valid values are: ${[...this.#validThemes].join(', ')}.`,\n );\n }\n if (!this.#isBrowser) return;\n if (theme === this.#selectedTheme()) return;\n if (theme === 'system') {\n this.#systemPreference.set(this.resolveSystemPreference());\n this.startSystemThemeListener();\n } else {\n this.stopSystemThemeListener();\n }\n\n this.#selectedTheme.set(theme);\n this.saveTheme(theme);\n }\n\n // ── Private ───────────────────────────────────────────────────────────────\n\n private resolveSystemPreference(): NgSystemTheme {\n return this.#mediaQuery?.matches ? 'dark' : 'light';\n }\n\n private resolveInitialTheme(): NgTheme {\n const theme = this.#initialStoredTheme;\n if (theme && this.#validThemes.has(theme as NgTheme)) {\n return theme as NgTheme;\n }\n return this.#config.defaultTheme;\n }\n\n private startSystemThemeListener(): void {\n if (!this.#mediaQuery) return;\n this.stopSystemThemeListener();\n this.#mediaQuery.addEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private stopSystemThemeListener(): void {\n this.#mediaQuery?.removeEventListener('change', this.#onSystemPreferenceChange);\n }\n\n private applyThemeToDOM(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n const host = this.#document.documentElement;\n\n if (this.#antiFlashClass) {\n host.classList.remove(this.#antiFlashClass);\n this.#antiFlashClass = null;\n }\n\n const { mode } = this.#config;\n\n if (mode === 'attribute' || mode === 'both') {\n this.applyThemeAttribute(host, theme);\n }\n\n if (mode === 'class' || mode === 'both') {\n this.applyThemeClasses(host, theme);\n }\n\n this.applyColorSchemeHint(host, theme);\n }\n\n private applyThemeAttribute(host: HTMLElement, theme: NgTheme): void {\n host.setAttribute('data-theme', theme);\n }\n\n private applyThemeClasses(host: HTMLElement, theme: NgTheme): void {\n host.classList.remove(...this.availableThemes);\n host.classList.add(theme);\n }\n\n private applyColorSchemeHint(host: HTMLElement, theme: NgTheme): void {\n if (theme === 'dark' || theme === 'light') {\n host.style.setProperty('color-scheme', theme);\n return;\n }\n\n host.style.removeProperty('color-scheme');\n }\n\n private captureAntiFlashClass(): void {\n if (!this.#isBrowser || !this.#initialStoredTheme) return;\n\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(this.#initialStoredTheme)) {\n this.#antiFlashClass = null;\n return;\n }\n\n if (this.#initialStoredTheme === 'system') {\n this.#antiFlashClass = this.resolveSystemPreference();\n return;\n }\n\n this.#antiFlashClass = this.#initialStoredTheme;\n }\n\n private readStoredTheme(): string | null {\n if (!this.#isBrowser) return null;\n\n try {\n return localStorage.getItem(this.#config.storageKey);\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not read theme from localStorage.', e);\n return null;\n }\n }\n\n private saveTheme(theme: NgTheme): void {\n if (!this.#isBrowser) return;\n\n try {\n localStorage.setItem(this.#config.storageKey, theme);\n } catch (e) {\n console.warn('[ngx-theme-stack] Could not save theme to localStorage.', e);\n }\n }\n}\n","import { computed, inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from '../core/core-theme.service';\n\n/**\n * Convenience service for cycling through themes in a fixed order.\n *\n * Default cycle: `'system'` → `'light'` → `'dark'` → `'system'` → ...\n *\n * Use this when you want to offer users a single button that rotates\n * through all available theme options.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeCycleService {\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes for cycling. Defaults to `['light', 'dark', 'system']`. */\n readonly availableThemes = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** Index of the currently selected theme in the cycle. */\n readonly cycleIndex = computed(() => {\n return this.availableThemes.indexOf(this.selectedTheme());\n });\n\n /** The theme that comes before the currently selected theme in the cycle. */\n readonly preceding = computed(() => {\n const index = this.cycleIndex();\n const len = this.availableThemes.length;\n return this.availableThemes[(index - 1 + len) % len];\n });\n\n /** The theme that comes after the currently selected theme in the cycle. */\n readonly upcoming = computed(() => {\n const index = this.cycleIndex();\n return this.availableThemes[(index + 1) % this.availableThemes.length];\n });\n\n /** Whether the currently applied theme is `'dark'`. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is `'light'`. */\n readonly isLight = this.#core.isLight;\n\n /** Whether the user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and\n * resolved the real persisted theme. Use to prevent hydration flashes.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\n\n /**\n * Advances to the next theme in the cycle.\n *\n * Cycle order is determined by the configured `themes` property in `NgConfig`.\n *\n * If the current theme is not found in the cycle (e.g. set externally),\n * the cycle restarts from the first theme.\n */\n cycle(): void {\n this.#core.setTheme(this.upcoming());\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from '../core/core-theme.service';\nimport { NgTheme } from '../types';\n\n/**\n * Convenience service for selecting a theme from a list.\n *\n * Use this when you want to bind a `<select>` or a group of radio/tab\n * buttons to the full set of available themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeSelectService {\n readonly #core = inject(CoreThemeService);\n\n /** List of all configured themes. Defaults to `['light', 'dark', 'system']`. */\n readonly availableThemes = this.#core.availableThemes;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** Whether the currently applied theme is `'dark'`. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is `'light'`. */\n readonly isLight = this.#core.isLight;\n\n /** Whether the user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\n\n /**\n * Applies the given theme.\n *\n * @param theme - The theme to apply: `'dark'`, `'light'`, `'system'`, or custom.\n * @throws If `theme` is not a valid theme according to library configuration.\n */\n select(theme: NgTheme): void {\n this.#core.setTheme(theme);\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { CoreThemeService } from '../core/core-theme.service';\n\n/**\n * Convenience service for toggling between `'dark'` and `'light'`.\n *\n * Use this when you only need a simple on/off switch and do not\n * need to manage `'system'` or cycle through themes.\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeToggleService {\n readonly #core = inject(CoreThemeService);\n\n /** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */\n readonly resolvedTheme = this.#core.resolvedTheme;\n\n /** The theme explicitly selected by the user. May be `'system'`. */\n readonly selectedTheme = this.#core.selectedTheme;\n\n /** Whether the currently applied theme is `'dark'`. */\n readonly isDark = this.#core.isDark;\n\n /** Whether the currently applied theme is `'light'`. */\n readonly isLight = this.#core.isLight;\n\n /** Whether the user has explicitly selected `'system'` preference. */\n readonly isSystem = this.#core.isSystem;\n\n /**\n * Whether the service has completed client-side initialization and \n * resolved the real persisted theme. Use to prevent hydration flashes.\n */\n readonly isHydrated = this.#core.isHydrated.asReadonly();\n\n /**\n * Toggles between `'dark'` and `'light'`.\n *\n * If the selected theme is explicitly `'dark'`, switches to `'light'`.\n * Otherwise (including `'system'`), switches to `'dark'`.\n */\n toggle(): void {\n const next = this.#core.resolvedTheme() === 'dark' ? 'light' : 'dark';\n this.#core.setTheme(next);\n }\n}\n","/*\n * Public API Surface of ngx-theme-stack\n */\n\nexport * from './lib/core/core-theme.service';\nexport * from './lib/config';\nexport * from './lib/services/theme-cycle.service';\nexport * from './lib/services/theme-select.service';\nexport * from './lib/services/theme-toggle.service';\nexport * from './lib/types';\nexport * from './lib/errors';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { SchematicContext, Tree } from '@angular-devkit/schematics';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param sourceRoot The source root for the project (e.g. 'projects/demo-ngx-theme-stack/src').
|
|
9
|
-
* @param provideCall The pre-formatted code string for provideThemeStack().
|
|
3
|
+
* Patches the application configuration to include or update the theme stack provider.
|
|
4
|
+
* Uses a hybrid approach:
|
|
5
|
+
* 1. Attempts the official 'addRootProvider' for standard standalone apps.
|
|
6
|
+
* 2. If it fails or if the provider already exists, it uses a smart AST walker
|
|
7
|
+
* to either inject or UPDATE the existing configuration.
|
|
10
8
|
*/
|
|
11
|
-
export declare function patchAppConfig(tree: Tree, context: SchematicContext,
|
|
9
|
+
export declare function patchAppConfig(tree: Tree, context: SchematicContext, projectSourceRoot: string, provideCall: string, projectName?: string): Promise<void>;
|
|
@@ -1,53 +1,190 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.patchAppConfig = patchAppConfig;
|
|
13
|
+
const utility_1 = require("@schematics/angular/utility");
|
|
14
|
+
const ts = require("typescript");
|
|
4
15
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @param sourceRoot The source root for the project (e.g. 'projects/demo-ngx-theme-stack/src').
|
|
11
|
-
* @param provideCall The pre-formatted code string for provideThemeStack().
|
|
16
|
+
* Patches the application configuration to include or update the theme stack provider.
|
|
17
|
+
* Uses a hybrid approach:
|
|
18
|
+
* 1. Attempts the official 'addRootProvider' for standard standalone apps.
|
|
19
|
+
* 2. If it fails or if the provider already exists, it uses a smart AST walker
|
|
20
|
+
* to either inject or UPDATE the existing configuration.
|
|
12
21
|
*/
|
|
13
|
-
function patchAppConfig(tree, context,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
updated = updated.replace(/provideThemeStack\s*\([\s\S]*?\)/, provideCall);
|
|
22
|
-
tree.overwrite(filePath, updated);
|
|
22
|
+
function patchAppConfig(tree, context, projectSourceRoot, provideCall, projectName) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
const mainPath = `${projectSourceRoot}/main.ts`.replace(/^\//, '');
|
|
26
|
+
const appConfigPath = `${projectSourceRoot}/app/app.config.ts`.replace(/^\//, '');
|
|
27
|
+
const startFile = tree.exists(appConfigPath) ? appConfigPath : tree.exists(mainPath) ? mainPath : null;
|
|
28
|
+
if (!startFile) {
|
|
29
|
+
context.logger.warn('⚠ Could not find app.config.ts or main.ts. Please add provideThemeStack() manually.');
|
|
23
30
|
return;
|
|
24
31
|
}
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
32
|
+
// Check if already present to decide between "Inject" or "Update"
|
|
33
|
+
const content = ((_a = tree.read(startFile)) === null || _a === void 0 ? void 0 : _a.toString()) || '';
|
|
34
|
+
const alreadyHasProvider = content.includes('provideThemeStack');
|
|
35
|
+
// ── Strategy 1: Standard addRootProvider (Only if NOT already present) ─────
|
|
36
|
+
if (projectName && !alreadyHasProvider) {
|
|
37
|
+
try {
|
|
38
|
+
const rule = (0, utility_1.addRootProvider)(projectName, ({ code, external }) => code `${external('provideThemeStack', 'ngx-theme-stack')}(${provideCall.replace(/^provideThemeStack\(/, '').replace(/\)$/, '')})`);
|
|
39
|
+
yield Promise.resolve(rule(tree, context));
|
|
40
|
+
// Re-read content to see if it worked
|
|
41
|
+
const updatedContent = ((_b = tree.read(startFile)) === null || _b === void 0 ? void 0 : _b.toString()) || '';
|
|
42
|
+
if (updatedContent.includes('provideThemeStack'))
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
context.logger.debug(`Standard addRootProvider failed: ${String(e)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ── Strategy 2: Smart AST Patching / Updating ─────────────────────────────
|
|
50
|
+
// This will handle both "Delegated Providers" and "Updates to existing config"
|
|
51
|
+
if (yield applySmartPatch(tree, context, startFile, provideCall)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
context.logger.warn(`⚠ Could not automatically inject or update provider in ${startFile}.`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Recursively follows identifiers to find where the providers array is defined
|
|
59
|
+
* or where provideThemeStack is already called.
|
|
60
|
+
*/
|
|
61
|
+
function applySmartPatch(tree_1, context_1, filePath_1, provideCall_1) {
|
|
62
|
+
return __awaiter(this, arguments, void 0, function* (tree, context, filePath, provideCall, visitedFiles = new Set()) {
|
|
63
|
+
if (visitedFiles.has(filePath))
|
|
64
|
+
return false;
|
|
65
|
+
visitedFiles.add(filePath);
|
|
66
|
+
const buffer = tree.read(filePath);
|
|
67
|
+
if (!buffer)
|
|
68
|
+
return false;
|
|
69
|
+
const content = buffer.toString();
|
|
70
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
71
|
+
// A. UPDATE: If provideThemeStack is already here, replace it.
|
|
72
|
+
const existingCall = findProvideThemeStackCall(sourceFile);
|
|
73
|
+
if (existingCall) {
|
|
74
|
+
const updated = content.slice(0, existingCall.getStart()) + provideCall + content.slice(existingCall.getEnd());
|
|
75
|
+
tree.overwrite(filePath, updated);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
// B. INJECT: Look for a providers array literal: providers: [ ... ]
|
|
79
|
+
const arrayLiteral = findProvidersArrayLiteral(sourceFile);
|
|
80
|
+
if (arrayLiteral) {
|
|
81
|
+
insertIntoArray(tree, filePath, arrayLiteral, provideCall);
|
|
82
|
+
ensureImport(tree, filePath, 'provideThemeStack', 'ngx-theme-stack');
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
// C. DELEGATION: Look for a delegated provider: providers: SOME_CONSTANT
|
|
86
|
+
const delegatedIdentifier = findProvidersIdentifier(sourceFile);
|
|
87
|
+
if (delegatedIdentifier) {
|
|
88
|
+
const importPath = findImportPathForIdentifier(sourceFile, delegatedIdentifier);
|
|
89
|
+
if (importPath) {
|
|
90
|
+
const dir = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
91
|
+
let resolvedPath = `${dir}/${importPath}.ts`.replace(/\/\/+/g, '/').replace(/\.ts\.ts$/, '.ts');
|
|
92
|
+
if (!tree.exists(resolvedPath)) {
|
|
93
|
+
resolvedPath = `${dir}/${importPath}/index.ts`.replace(/\/\/+/g, '/');
|
|
94
|
+
}
|
|
95
|
+
if (tree.exists(resolvedPath)) {
|
|
96
|
+
return applySmartPatch(tree, context, resolvedPath, provideCall, visitedFiles);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const variableDeclaration = findVariableDeclaration(sourceFile, delegatedIdentifier);
|
|
101
|
+
if (variableDeclaration && variableDeclaration.initializer && ts.isArrayLiteralExpression(variableDeclaration.initializer)) {
|
|
102
|
+
insertIntoArray(tree, filePath, variableDeclaration.initializer, provideCall);
|
|
103
|
+
ensureImport(tree, filePath, 'provideThemeStack', 'ngx-theme-stack');
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function findProvideThemeStackCall(node) {
|
|
112
|
+
let result = null;
|
|
113
|
+
function visit(n) {
|
|
114
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === 'provideThemeStack') {
|
|
115
|
+
result = n;
|
|
116
|
+
}
|
|
117
|
+
if (!result)
|
|
118
|
+
ts.forEachChild(n, visit);
|
|
47
119
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
120
|
+
visit(node);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function findProvidersArrayLiteral(node) {
|
|
124
|
+
let result = null;
|
|
125
|
+
function visit(n) {
|
|
126
|
+
if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === 'providers') {
|
|
127
|
+
if (ts.isArrayLiteralExpression(n.initializer)) {
|
|
128
|
+
result = n.initializer;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!result)
|
|
132
|
+
ts.forEachChild(n, visit);
|
|
133
|
+
}
|
|
134
|
+
visit(node);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function findProvidersIdentifier(node) {
|
|
138
|
+
let result = null;
|
|
139
|
+
function visit(n) {
|
|
140
|
+
if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === 'providers') {
|
|
141
|
+
if (ts.isIdentifier(n.initializer)) {
|
|
142
|
+
result = n.initializer.text;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!result)
|
|
146
|
+
ts.forEachChild(n, visit);
|
|
147
|
+
}
|
|
148
|
+
visit(node);
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
function findImportPathForIdentifier(sourceFile, identifier) {
|
|
152
|
+
var _a;
|
|
153
|
+
for (const statement of sourceFile.statements) {
|
|
154
|
+
if (ts.isImportDeclaration(statement) && ((_a = statement.importClause) === null || _a === void 0 ? void 0 : _a.namedBindings) && ts.isNamedImports(statement.importClause.namedBindings)) {
|
|
155
|
+
if (statement.importClause.namedBindings.elements.some(e => e.name.text === identifier)) {
|
|
156
|
+
return statement.moduleSpecifier.text;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function findVariableDeclaration(sourceFile, identifier) {
|
|
163
|
+
for (const statement of sourceFile.statements) {
|
|
164
|
+
if (ts.isVariableStatement(statement)) {
|
|
165
|
+
for (const decl of statement.declarationList.declarations) {
|
|
166
|
+
if (ts.isIdentifier(decl.name) && decl.name.text === identifier) {
|
|
167
|
+
return decl;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function insertIntoArray(tree, filePath, array, text) {
|
|
175
|
+
const content = tree.read(filePath).toString();
|
|
176
|
+
const insertionPos = array.elements.length > 0
|
|
177
|
+
? array.elements[array.elements.length - 1].getEnd()
|
|
178
|
+
: array.getStart() + 1;
|
|
179
|
+
const prefix = array.elements.length > 0 ? ', ' : '';
|
|
180
|
+
const updated = content.slice(0, insertionPos) + prefix + text + content.slice(insertionPos);
|
|
181
|
+
tree.overwrite(filePath, updated);
|
|
182
|
+
}
|
|
183
|
+
function ensureImport(tree, filePath, symbol, module) {
|
|
184
|
+
const content = tree.read(filePath).toString();
|
|
185
|
+
if (content.includes(symbol))
|
|
186
|
+
return;
|
|
187
|
+
const updated = `import { ${symbol} } from '${module}';\n` + content;
|
|
188
|
+
tree.overwrite(filePath, updated);
|
|
52
189
|
}
|
|
53
190
|
//# sourceMappingURL=app-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/app-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-config.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/app-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
export declare const DEFAULT_THEMES: readonly ["system", "light", "dark"];
|
|
12
12
|
export declare const DEFAULTS: {
|
|
13
13
|
readonly defaultTheme: "system";
|
|
14
|
-
readonly storageKey: "ngx-theme-stack
|
|
14
|
+
readonly storageKey: "ngx-theme-stack";
|
|
15
15
|
readonly mode: "class";
|
|
16
16
|
readonly strategy: "critters";
|
|
17
17
|
readonly themes: readonly ["system", "light", "dark"];
|
|
@@ -14,7 +14,7 @@ exports.DEFAULTS = exports.DEFAULT_THEMES = void 0;
|
|
|
14
14
|
exports.DEFAULT_THEMES = ['system', 'light', 'dark'];
|
|
15
15
|
exports.DEFAULTS = {
|
|
16
16
|
defaultTheme: 'system',
|
|
17
|
-
storageKey: 'ngx-theme-stack
|
|
17
|
+
storageKey: 'ngx-theme-stack',
|
|
18
18
|
mode: 'class',
|
|
19
19
|
strategy: 'critters',
|
|
20
20
|
themes: [...exports.DEFAULT_THEMES],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/constants.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEtD,QAAA,QAAQ,GAAG;IACtB,YAAY,EAAE,QAAQ;IACtB,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/constants.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEtD,QAAA,QAAQ,GAAG;IACtB,YAAY,EAAE,QAAQ;IACtB,UAAU,EAAE,iBAAiB;IAC7B,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,CAAC,GAAG,sBAAc,CAAC;CACnB,CAAC"}
|
|
@@ -2,10 +2,5 @@ import { Rule } from '@angular-devkit/schematics';
|
|
|
2
2
|
import { Schema } from './schema';
|
|
3
3
|
/**
|
|
4
4
|
* Main schematic factory for the 'ng-add' command.
|
|
5
|
-
* Depending on the 'mode' option, it either applies default library values ("quick")
|
|
6
|
-
* or starts an interactive session to collect personalized configuration ("custom").
|
|
7
|
-
*
|
|
8
|
-
* @param options The schema options provided by the Angular CLI.
|
|
9
|
-
* @returns A rule that modifies the project's setup.
|
|
10
5
|
*/
|
|
11
6
|
export declare function ngAdd(options: Schema): Rule;
|
|
@@ -16,11 +16,7 @@ const app_config_1 = require("./app-config");
|
|
|
16
16
|
const constants_1 = require("./constants");
|
|
17
17
|
const utils_1 = require("./utils");
|
|
18
18
|
/**
|
|
19
|
-
* Interactively prompts the user for custom configuration options
|
|
20
|
-
* It allows defining additional themes, selecting a default theme from the expanded list,
|
|
21
|
-
* providing a custom localStorage key, and choosing a CSS application mode.
|
|
22
|
-
*
|
|
23
|
-
* @returns A promise resolving to the user's selected configuration options.
|
|
19
|
+
* Interactively prompts the user for custom configuration options.
|
|
24
20
|
*/
|
|
25
21
|
function collectCustomOptions() {
|
|
26
22
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -29,25 +25,22 @@ function collectCustomOptions() {
|
|
|
29
25
|
process.stdout.write('\n');
|
|
30
26
|
const rawThemes = yield (0, utils_1.ask)(rl, ' Custom themes (comma-separated, Enter to skip): ');
|
|
31
27
|
const customThemes = rawThemes
|
|
32
|
-
? rawThemes
|
|
33
|
-
.split(',')
|
|
34
|
-
.map((t) => t.trim())
|
|
35
|
-
.filter(Boolean)
|
|
28
|
+
? rawThemes.split(',').map((t) => t.trim()).filter(Boolean)
|
|
36
29
|
: [];
|
|
37
|
-
const
|
|
38
|
-
const defaultTheme = yield (0, utils_1.askList)(rl, 'Default theme:',
|
|
30
|
+
const themes = [...constants_1.DEFAULT_THEMES, ...customThemes];
|
|
31
|
+
const defaultTheme = yield (0, utils_1.askList)(rl, 'Default theme:', themes, 0);
|
|
39
32
|
const rawKey = yield (0, utils_1.ask)(rl, ` localStorage key [${constants_1.DEFAULTS.storageKey}]: `);
|
|
40
33
|
const storageKey = rawKey || constants_1.DEFAULTS.storageKey;
|
|
41
34
|
const MODES = ['class', 'attribute', 'both'];
|
|
42
35
|
const mode = yield (0, utils_1.askList)(rl, 'How to apply theme on <html>:', MODES, 0);
|
|
43
36
|
const STRATEGIES = ['critters', 'blocking'];
|
|
44
|
-
process.stdout.write('\n');
|
|
45
|
-
process.stdout.write(' Anti-flash strategy:\n');
|
|
37
|
+
process.stdout.write('\n Anti-flash strategy:\n');
|
|
46
38
|
process.stdout.write(' - critters: Zero network requests (inlines CSS in <head>)\n');
|
|
47
39
|
process.stdout.write(' - blocking: Standard CSS loading (themes.css)\n');
|
|
48
40
|
const strategy = (yield (0, utils_1.askList)(rl, 'Choose strategy:', STRATEGIES, 0));
|
|
41
|
+
const provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy);
|
|
49
42
|
process.stdout.write('\n');
|
|
50
|
-
return { defaultTheme, storageKey, mode, themes
|
|
43
|
+
return { defaultTheme, storageKey, mode, themes, strategy, provideCall };
|
|
51
44
|
}
|
|
52
45
|
finally {
|
|
53
46
|
rl.close();
|
|
@@ -56,65 +49,51 @@ function collectCustomOptions() {
|
|
|
56
49
|
}
|
|
57
50
|
/**
|
|
58
51
|
* Main schematic factory for the 'ng-add' command.
|
|
59
|
-
* Depending on the 'mode' option, it either applies default library values ("quick")
|
|
60
|
-
* or starts an interactive session to collect personalized configuration ("custom").
|
|
61
|
-
*
|
|
62
|
-
* @param options The schema options provided by the Angular CLI.
|
|
63
|
-
* @returns A rule that modifies the project's setup.
|
|
64
52
|
*/
|
|
65
53
|
function ngAdd(options) {
|
|
66
54
|
return (tree, context) => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
-
// ── Workspace Resolution ──────────────────────────────────────────────────
|
|
68
|
-
// In a monorepo, we must find the project's actual root and sourceRoot.
|
|
69
55
|
const workspaceConfig = tree.read('/angular.json');
|
|
70
56
|
if (!workspaceConfig) {
|
|
71
57
|
throw new Error('Could not find angular.json. Are you in an Angular workspace?');
|
|
72
58
|
}
|
|
73
59
|
const workspace = JSON.parse(workspaceConfig.toString());
|
|
74
|
-
const projectName = options.project || workspace.defaultProject;
|
|
60
|
+
const projectName = options.project || workspace.defaultProject || Object.keys(workspace.projects)[0];
|
|
75
61
|
const project = workspace.projects[projectName];
|
|
76
62
|
if (!project) {
|
|
77
63
|
throw new Error(`Project "${projectName}" not found in angular.json.`);
|
|
78
64
|
}
|
|
79
65
|
const projectRoot = project.root || '';
|
|
80
66
|
const projectSourceRoot = project.sourceRoot || `${projectRoot}/src`;
|
|
81
|
-
context.logger.info('');
|
|
82
|
-
context.logger.info(
|
|
83
|
-
|
|
84
|
-
let provideCall;
|
|
85
|
-
let scriptOptions;
|
|
86
|
-
let finalThemes;
|
|
87
|
-
let themesToScaffold;
|
|
88
|
-
let finalStrategy;
|
|
89
|
-
const changeset = [];
|
|
67
|
+
context.logger.info('\n🎨 ngx-theme-stack — setup');
|
|
68
|
+
context.logger.info(` project: ${projectName}\n`);
|
|
69
|
+
let config;
|
|
90
70
|
if (options.mode === 'quick') {
|
|
91
71
|
const themes = [...constants_1.DEFAULT_THEMES];
|
|
92
|
-
const { defaultTheme, storageKey, mode } = constants_1.DEFAULTS;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
72
|
+
const { defaultTheme, storageKey, mode, strategy } = constants_1.DEFAULTS;
|
|
73
|
+
config = {
|
|
74
|
+
defaultTheme,
|
|
75
|
+
storageKey,
|
|
76
|
+
mode,
|
|
77
|
+
themes,
|
|
78
|
+
strategy: strategy,
|
|
79
|
+
provideCall: (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy),
|
|
80
|
+
};
|
|
81
|
+
context.logger.info('⚡ Quick setup — providing explicit defaults.');
|
|
100
82
|
}
|
|
101
83
|
else {
|
|
102
84
|
context.logger.info('🛠 Custom setup — answer the prompts below:');
|
|
103
|
-
|
|
104
|
-
const { defaultTheme, storageKey, mode, themes, strategy } = opts;
|
|
105
|
-
provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes, strategy);
|
|
106
|
-
scriptOptions = { storageKey, defaultTheme, mode };
|
|
107
|
-
finalThemes = themes;
|
|
108
|
-
themesToScaffold = themes.filter((t) => t !== 'system');
|
|
109
|
-
finalStrategy = strategy;
|
|
85
|
+
config = yield collectCustomOptions();
|
|
110
86
|
}
|
|
87
|
+
const changeset = [];
|
|
88
|
+
const themesToScaffold = config.themes.filter((t) => t !== 'system');
|
|
111
89
|
return (0, schematics_1.chain)([
|
|
90
|
+
// 1. Scaffold themes.css
|
|
112
91
|
(t) => {
|
|
113
|
-
const themesPath = `${projectSourceRoot}/themes.css
|
|
92
|
+
const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
|
|
114
93
|
if (!t.exists(themesPath)) {
|
|
115
94
|
let content = '/* ngx-theme-stack tokens */\n\n';
|
|
116
95
|
themesToScaffold.forEach((theme) => {
|
|
117
|
-
const selector =
|
|
96
|
+
const selector = config.mode === 'attribute' ? `[data-theme="${theme}"]` : `.${theme}`;
|
|
118
97
|
if (theme === 'light') {
|
|
119
98
|
content += `:root, ${selector} {\n /* Add your light theme variables here */\n}\n\n`;
|
|
120
99
|
}
|
|
@@ -123,12 +102,13 @@ function ngAdd(options) {
|
|
|
123
102
|
}
|
|
124
103
|
});
|
|
125
104
|
t.create(themesPath, content);
|
|
126
|
-
changeset.push(` \u001b[36mA\u001b[0m ${themesPath
|
|
105
|
+
changeset.push(` \u001b[36mA\u001b[0m ${themesPath} (theme tokens)`);
|
|
127
106
|
}
|
|
128
107
|
else {
|
|
129
|
-
changeset.push(` \u001b[90mℹ\u001b[0m ${themesPath
|
|
108
|
+
changeset.push(` \u001b[90mℹ\u001b[0m ${themesPath} (already exists)`);
|
|
130
109
|
}
|
|
131
110
|
},
|
|
111
|
+
// 2. Add prebuild script to package.json
|
|
132
112
|
(t) => {
|
|
133
113
|
const pkgPath = '/package.json';
|
|
134
114
|
const buffer = t.read(pkgPath);
|
|
@@ -140,46 +120,43 @@ function ngAdd(options) {
|
|
|
140
120
|
changeset.push(' \u001b[33mM\u001b[0m package.json (prebuild script)');
|
|
141
121
|
}
|
|
142
122
|
},
|
|
123
|
+
// 3. Update angular.json (styles & optimization)
|
|
143
124
|
(t) => {
|
|
144
125
|
var _a;
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (
|
|
153
|
-
|
|
126
|
+
const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
|
|
127
|
+
const target = project.architect.build.options;
|
|
128
|
+
if (target.styles && !target.styles.includes(themesPath)) {
|
|
129
|
+
target.styles.unshift(themesPath);
|
|
130
|
+
}
|
|
131
|
+
const prodConfig = (_a = project.architect.build.configurations) === null || _a === void 0 ? void 0 : _a.production;
|
|
132
|
+
if (prodConfig && config.strategy === 'blocking') {
|
|
133
|
+
if (typeof prodConfig.optimization === 'object') {
|
|
134
|
+
prodConfig.optimization.styles = prodConfig.optimization.styles || {};
|
|
135
|
+
prodConfig.optimization.styles.inlineCritical = false;
|
|
154
136
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (prodConfig && finalStrategy === 'blocking') {
|
|
158
|
-
if (typeof prodConfig.optimization === 'object') {
|
|
159
|
-
prodConfig.optimization.styles = prodConfig.optimization.styles || {};
|
|
160
|
-
prodConfig.optimization.styles.inlineCritical = false;
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
prodConfig.optimization = {
|
|
164
|
-
styles: { inlineCritical: false },
|
|
165
|
-
};
|
|
166
|
-
}
|
|
137
|
+
else {
|
|
138
|
+
prodConfig.optimization = { styles: { inlineCritical: false } };
|
|
167
139
|
}
|
|
168
|
-
t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
|
|
169
|
-
changeset.push(' \u001b[33mM\u001b[0m angular.json (styles & optimization)');
|
|
170
140
|
}
|
|
141
|
+
t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
|
|
142
|
+
changeset.push(' \u001b[33mM\u001b[0m angular.json (styles & optimization)');
|
|
171
143
|
},
|
|
172
|
-
|
|
173
|
-
|
|
144
|
+
// 4. Patch App Config and Index HTML
|
|
145
|
+
(t, ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
146
|
+
yield (0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, config.provideCall, projectName);
|
|
174
147
|
changeset.push(' \u001b[33mM\u001b[0m app.config.ts (provided theme stack)');
|
|
175
|
-
(0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot,
|
|
148
|
+
(0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, {
|
|
149
|
+
storageKey: config.storageKey,
|
|
150
|
+
defaultTheme: config.defaultTheme,
|
|
151
|
+
mode: config.mode,
|
|
152
|
+
themes: config.themes,
|
|
153
|
+
strategy: config.strategy,
|
|
154
|
+
});
|
|
176
155
|
changeset.push(' \u001b[33mM\u001b[0m index.html (injected anti-flash)');
|
|
177
|
-
ctx.logger.info('\u001b[1mChangeset:\u001b[0m');
|
|
156
|
+
ctx.logger.info('\n\u001b[1mChangeset:\u001b[0m');
|
|
178
157
|
changeset.forEach((entry) => ctx.logger.info(entry));
|
|
179
|
-
ctx.logger.info('');
|
|
180
|
-
|
|
181
|
-
ctx.logger.info('');
|
|
182
|
-
},
|
|
158
|
+
ctx.logger.info('\n\u001b[1m\u001b[32m🏁 Done.\u001b[0m\n');
|
|
159
|
+
}),
|
|
183
160
|
]);
|
|
184
161
|
});
|
|
185
162
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -169,7 +169,7 @@ declare const NGX_THEME_STACK_CONFIG: InjectionToken<NgConfig<string>>;
|
|
|
169
169
|
* **Defaults:**
|
|
170
170
|
* - `themes`: `['light', 'dark', 'system']`
|
|
171
171
|
* - `defaultTheme`: `'system'`
|
|
172
|
-
* - `storageKey`: `'ngx-theme-stack
|
|
172
|
+
* - `storageKey`: `'ngx-theme-stack'`
|
|
173
173
|
* - `mode`: `'class'`
|
|
174
174
|
* - `strategy`: `'critters'`
|
|
175
175
|
|
|
@@ -228,10 +228,18 @@ declare function provideThemeStack<const T extends string = DefaultNgTheme>(conf
|
|
|
228
228
|
*/
|
|
229
229
|
declare class ThemeCycleService {
|
|
230
230
|
#private;
|
|
231
|
+
/** List of all configured themes for cycling. Defaults to `['light', 'dark', 'system']`. */
|
|
232
|
+
readonly availableThemes: string[];
|
|
231
233
|
/** The theme explicitly selected by the user. May be `'system'`. */
|
|
232
234
|
readonly selectedTheme: _angular_core.Signal<ngx_theme_stack.NgTheme>;
|
|
233
235
|
/** Resolved theme currently applied to the DOM. Always concrete — never `'system'`. */
|
|
234
236
|
readonly resolvedTheme: _angular_core.Signal<(string & {}) | ngx_theme_stack.NgSystemTheme>;
|
|
237
|
+
/** Index of the currently selected theme in the cycle. */
|
|
238
|
+
readonly cycleIndex: _angular_core.Signal<number>;
|
|
239
|
+
/** The theme that comes before the currently selected theme in the cycle. */
|
|
240
|
+
readonly preceding: _angular_core.Signal<string>;
|
|
241
|
+
/** The theme that comes after the currently selected theme in the cycle. */
|
|
242
|
+
readonly upcoming: _angular_core.Signal<string>;
|
|
235
243
|
/** Whether the currently applied theme is `'dark'`. */
|
|
236
244
|
readonly isDark: _angular_core.Signal<boolean>;
|
|
237
245
|
/** Whether the currently applied theme is `'light'`. */
|