ngx-easy-theme-switcher 1.0.0

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 ADDED
@@ -0,0 +1,208 @@
1
+ # NGX Easy Theme Switcher
2
+
3
+ Angular library for easy theme switching (light/dark) with customizable toggle icons.
4
+
5
+ ## Features
6
+
7
+ - **Service-based** — inject `ThemeService` anywhere to programmatically get/set/toggle themes
8
+ - **Persistent** — theme preference is saved in `localStorage`
9
+ - **Customizable icon** — default FontAwesome, with support for any icon font or custom SVG
10
+ - **Standalone** — compatible with Angular standalone components
11
+ - **Minimal** — just a service and a toggle component
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install ngx-easy-theme-switcher
17
+ ```
18
+
19
+ FontAwesome is used by default. For icons to display, install your preferred icon library:
20
+
21
+ ```bash
22
+ # For default FontAwesome icons
23
+ npm install @fortawesome/fontawesome-free
24
+
25
+ # Or for Material Icons
26
+ npm install material-symbols
27
+
28
+ # Or for Bootstrap Icons
29
+ npm install bootstrap-icons
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Create theme files
35
+
36
+ Create two SCSS files in `src/themes/`:
37
+
38
+ **`src/themes/_light.scss`**:
39
+ ```scss
40
+ [data-theme="light"] {
41
+ --bg: #ffffff;
42
+ --text: #1a1a2e;
43
+ --primary: #4361ee;
44
+ }
45
+ ```
46
+
47
+ **`src/themes/_dark.scss`**:
48
+ ```scss
49
+ [data-theme="dark"] {
50
+ --bg: #0f172a;
51
+ --text: #e2e8f0;
52
+ --primary: #818cf8;
53
+ }
54
+ ```
55
+
56
+ ### 2. Import themes in `styles.scss`
57
+
58
+ ```scss
59
+ @use 'themes/light';
60
+ @use 'themes/dark';
61
+
62
+ body {
63
+ background-color: var(--bg);
64
+ color: var(--text);
65
+ transition: background-color 0.3s, color 0.3s;
66
+ }
67
+ ```
68
+
69
+ ### 3. Configure the service
70
+
71
+ In `app.config.ts`:
72
+
73
+ ```typescript
74
+ import { ApplicationConfig } from '@angular/core';
75
+ import { THEME_CONFIG } from 'ngx-easy-theme-switcher';
76
+
77
+ export const appConfig: ApplicationConfig = {
78
+ providers: [
79
+ {
80
+ provide: THEME_CONFIG,
81
+ useValue: {
82
+ themes: ['light', 'dark'], // your theme names
83
+ defaultTheme: 'light', // initial theme
84
+ },
85
+ },
86
+ ],
87
+ };
88
+ ```
89
+
90
+ ### 4. Add the toggle button
91
+
92
+ In any component template:
93
+
94
+ ```html
95
+ <ets-theme-toggle />
96
+ ```
97
+
98
+ ## Customization
99
+
100
+ ### Icon Fonts
101
+
102
+ The toggle component supports various icon fonts via the `iconFont` input:
103
+
104
+ ```html
105
+ <!-- Default (FontAwesome Solid) -->
106
+ <ets-theme-toggle />
107
+
108
+ <!-- Material Symbols -->
109
+ <ets-theme-toggle
110
+ iconFont="material-symbols-outlined"
111
+ lightIcon="dark_mode"
112
+ darkIcon="light_mode"
113
+ />
114
+
115
+ <!-- Bootstrap Icons -->
116
+ <ets-theme-toggle
117
+ iconFont="bi"
118
+ lightIcon="bi-sun-fill"
119
+ darkIcon="bi-moon-fill"
120
+ />
121
+ ```
122
+
123
+ Supported icon fonts: `fas`, `far`, `fal`, `fab`, `material-icons`, `material-icons-outlined`, `material-symbols-outlined`, `bi`, `pi`, `ion-icon`.
124
+
125
+ ### Custom SVG
126
+
127
+ Pass any SVG markup as the `svgIcon` input:
128
+
129
+ ```html
130
+ <ets-theme-toggle [svgIcon]="mySvg" />
131
+ ```
132
+
133
+ ```typescript
134
+ mySvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
135
+ <path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9..."/>
136
+ </svg>`;
137
+ ```
138
+
139
+ ### Custom Icon Names
140
+
141
+ Override the icon class names for each theme:
142
+
143
+ ```html
144
+ <ets-theme-toggle
145
+ lightIcon="fa-sun"
146
+ darkIcon="fa-moon"
147
+ />
148
+ ```
149
+
150
+ ### Programmatic Control
151
+
152
+ Inject `ThemeService` anywhere:
153
+
154
+ ```typescript
155
+ import { ThemeService } from 'ngx-easy-theme-switcher';
156
+
157
+ constructor(private themeService: ThemeService) {}
158
+
159
+ // Toggle theme
160
+ this.themeService.toggleTheme();
161
+
162
+ // Set specific theme
163
+ this.themeService.setTheme('dark');
164
+
165
+ // Get current theme
166
+ const current = this.themeService.currentTheme;
167
+
168
+ // Observe theme changes
169
+ this.themeService.currentTheme$.subscribe(theme => {
170
+ console.log('Theme changed to:', theme);
171
+ });
172
+ ```
173
+
174
+ ## API
175
+
176
+ ### `ThemeService`
177
+
178
+ | Method/Property | Description |
179
+ |-----------------------|----------------------------------------------|
180
+ | `currentTheme` | Get the current theme name |
181
+ | `currentTheme$` | Observable that emits on theme change |
182
+ | `themes` | The configured theme names |
183
+ | `toggleTheme()` | Switch between the two themes |
184
+ | `setTheme(name)` | Set a specific theme |
185
+
186
+ ### `ThemeConfig` (InjectionToken)
187
+
188
+ | Property | Default | Description |
189
+ |----------------|---------------------|---------------------------------------|
190
+ | `themes` | `['light', 'dark']` | Pair of theme names |
191
+ | `attribute` | `'data-theme'` | HTML attribute set on `<html>` |
192
+ | `storageKey` | `'ets-pref-theme'` | localStorage key |
193
+ | `defaultTheme` | `'light'` | Initial theme before user choice |
194
+
195
+ ### `ThemeToggleComponent` Inputs
196
+
197
+ | Input | Default | Description |
198
+ |-------------|--------------|------------------------------------------|
199
+ | `iconFont` | `'fas'` | Icon font abbreviation |
200
+ | `svgIcon` | — | Custom SVG markup (overrides iconFont) |
201
+ | `lightIcon` | `'fa-sun'` | Icon name/class for the first theme |
202
+ | `darkIcon` | `'fa-moon'` | Icon name/class for the second theme |
203
+
204
+ ## License
205
+
206
+ Copyright (c) 2024 **Gennady Artamonov**
207
+
208
+ All use is free. Redistribution or resale of the library itself as a paid product is prohibited. See [LICENSE.md](./LICENSE.md) for full terms.
@@ -0,0 +1,195 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Optional, Inject, Injectable, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
4
+ import { NgClass } from '@angular/common';
5
+
6
+ const THEME_CONFIG = new InjectionToken('THEME_CONFIG');
7
+ const DEFAULT_CONFIG = {
8
+ themes: ['light', 'dark'],
9
+ attribute: 'data-theme',
10
+ storageKey: 'ets-pref-theme',
11
+ defaultTheme: 'light',
12
+ };
13
+ class ThemeService {
14
+ config;
15
+ currentThemeSubject;
16
+ currentTheme$;
17
+ constructor(config) {
18
+ this.config = { ...DEFAULT_CONFIG, ...config };
19
+ this.currentThemeSubject = new BehaviorSubject(this.getStoredTheme());
20
+ this.currentTheme$ = this.currentThemeSubject.asObservable();
21
+ this.applyTheme(this.currentThemeSubject.value);
22
+ }
23
+ /** Get the current theme name */
24
+ get currentTheme() {
25
+ return this.currentThemeSubject.value;
26
+ }
27
+ /** Get all available theme names */
28
+ get themes() {
29
+ return this.config.themes;
30
+ }
31
+ /** Toggle between the two configured themes */
32
+ toggleTheme() {
33
+ const [themeA, themeB] = this.config.themes;
34
+ const next = this.currentTheme === themeA ? themeB : themeA;
35
+ this.setTheme(next);
36
+ }
37
+ /** Set a specific theme by name */
38
+ setTheme(theme) {
39
+ if (!this.config.themes.includes(theme)) {
40
+ console.warn(`[EasyThemeSwitcher] Theme "${theme}" is not in configured themes:`, this.config.themes);
41
+ return;
42
+ }
43
+ this.currentThemeSubject.next(theme);
44
+ this.applyTheme(theme);
45
+ this.storeTheme(theme);
46
+ }
47
+ applyTheme(theme) {
48
+ const attr = this.config.attribute;
49
+ document.documentElement.setAttribute(attr, theme);
50
+ }
51
+ getStoredTheme() {
52
+ try {
53
+ const stored = localStorage.getItem(this.config.storageKey);
54
+ if (stored && this.config.themes.includes(stored)) {
55
+ return stored;
56
+ }
57
+ }
58
+ catch {
59
+ // localStorage not available
60
+ }
61
+ return this.config.defaultTheme;
62
+ }
63
+ storeTheme(theme) {
64
+ try {
65
+ localStorage.setItem(this.config.storageKey, theme);
66
+ }
67
+ catch {
68
+ // localStorage not available
69
+ }
70
+ }
71
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ThemeService, deps: [{ token: THEME_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
72
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ThemeService, providedIn: 'root' });
73
+ }
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ThemeService, decorators: [{
75
+ type: Injectable,
76
+ args: [{
77
+ providedIn: 'root',
78
+ }]
79
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
80
+ type: Optional
81
+ }, {
82
+ type: Inject,
83
+ args: [THEME_CONFIG]
84
+ }] }] });
85
+
86
+ class ThemeToggleComponent {
87
+ themeService;
88
+ cdr;
89
+ /** Icon font abbreviation (e.g. 'fas', 'material-icons', 'bi') */
90
+ iconFont;
91
+ /** Custom SVG markup (overrides iconFont if provided) */
92
+ svgIcon;
93
+ /** Icon name for the first theme (default light) */
94
+ lightIcon;
95
+ /** Icon name for the second theme (default dark) */
96
+ darkIcon;
97
+ oppositeTheme = '';
98
+ iconClasses = {};
99
+ displayIconText = '';
100
+ destroy$ = new Subject();
101
+ constructor(themeService, cdr) {
102
+ this.themeService = themeService;
103
+ this.cdr = cdr;
104
+ }
105
+ ngOnInit() {
106
+ this.updateState(this.themeService.currentTheme);
107
+ this.themeService.currentTheme$
108
+ .pipe(takeUntil(this.destroy$))
109
+ .subscribe((theme) => {
110
+ this.updateState(theme);
111
+ this.cdr.markForCheck();
112
+ });
113
+ }
114
+ ngOnDestroy() {
115
+ this.destroy$.next();
116
+ this.destroy$.complete();
117
+ }
118
+ toggle() {
119
+ this.themeService.toggleTheme();
120
+ }
121
+ updateState(currentTheme) {
122
+ const [lightTheme, darkTheme] = this.themeService.themes;
123
+ const isLight = currentTheme === lightTheme;
124
+ this.oppositeTheme = currentTheme === lightTheme ? darkTheme : lightTheme;
125
+ const iconName = isLight
126
+ ? (this.darkIcon ?? 'fa-moon')
127
+ : (this.lightIcon ?? 'fa-sun');
128
+ const font = this.iconFont ?? 'fas';
129
+ if (font.startsWith('material')) {
130
+ this.iconClasses = { [font]: true };
131
+ this.displayIconText = iconName;
132
+ }
133
+ else {
134
+ this.iconClasses = { [font]: true, [iconName]: true };
135
+ this.displayIconText = '';
136
+ }
137
+ }
138
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ThemeToggleComponent, deps: [{ token: ThemeService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
139
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: ThemeToggleComponent, isStandalone: true, selector: "ets-theme-toggle", inputs: { iconFont: "iconFont", svgIcon: "svgIcon", lightIcon: "lightIcon", darkIcon: "darkIcon" }, ngImport: i0, template: `
140
+ <button
141
+ class="ets-toggle-btn"
142
+ (click)="toggle()"
143
+ [attr.aria-label]="'Switch to ' + oppositeTheme + ' mode'"
144
+ type="button"
145
+ >
146
+ @if (svgIcon) {
147
+ <span class="ets-icon ets-icon--svg" [innerHTML]="svgIcon"></span>
148
+ } @else {
149
+ <span
150
+ class="ets-icon"
151
+ [ngClass]="iconClasses"
152
+ >{{ displayIconText }}</span>
153
+ }
154
+ </button>
155
+ `, isInline: true, styles: [".ets-toggle-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:8px;border-radius:50%;transition:background-color .3s,color .3s;color:inherit;font-size:1.25rem;line-height:1}.ets-toggle-btn:hover{background-color:#80808026}.ets-icon{display:inline-flex;align-items:center;justify-content:center}.ets-icon--svg{line-height:0}.ets-icon--svg ::ng-deep svg{width:1.25rem;height:1.25rem;fill:currentColor}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
156
+ }
157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ThemeToggleComponent, decorators: [{
158
+ type: Component,
159
+ args: [{ selector: 'ets-theme-toggle', standalone: true, imports: [NgClass], template: `
160
+ <button
161
+ class="ets-toggle-btn"
162
+ (click)="toggle()"
163
+ [attr.aria-label]="'Switch to ' + oppositeTheme + ' mode'"
164
+ type="button"
165
+ >
166
+ @if (svgIcon) {
167
+ <span class="ets-icon ets-icon--svg" [innerHTML]="svgIcon"></span>
168
+ } @else {
169
+ <span
170
+ class="ets-icon"
171
+ [ngClass]="iconClasses"
172
+ >{{ displayIconText }}</span>
173
+ }
174
+ </button>
175
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".ets-toggle-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:8px;border-radius:50%;transition:background-color .3s,color .3s;color:inherit;font-size:1.25rem;line-height:1}.ets-toggle-btn:hover{background-color:#80808026}.ets-icon{display:inline-flex;align-items:center;justify-content:center}.ets-icon--svg{line-height:0}.ets-icon--svg ::ng-deep svg{width:1.25rem;height:1.25rem;fill:currentColor}\n"] }]
176
+ }], ctorParameters: () => [{ type: ThemeService }, { type: i0.ChangeDetectorRef }], propDecorators: { iconFont: [{
177
+ type: Input
178
+ }], svgIcon: [{
179
+ type: Input
180
+ }], lightIcon: [{
181
+ type: Input
182
+ }], darkIcon: [{
183
+ type: Input
184
+ }] } });
185
+
186
+ /*
187
+ * Public API Surface of ngx-easy-theme-switcher
188
+ */
189
+
190
+ /**
191
+ * Generated bundle index. Do not edit.
192
+ */
193
+
194
+ export { THEME_CONFIG, ThemeService, ThemeToggleComponent };
195
+ //# sourceMappingURL=ngx-easy-theme-switcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-easy-theme-switcher.mjs","sources":["../../../projects/ngx-easy-theme-switcher/src/lib/theme.service.ts","../../../projects/ngx-easy-theme-switcher/src/lib/theme-toggle/theme-toggle.component.ts","../../../projects/ngx-easy-theme-switcher/src/public-api.ts","../../../projects/ngx-easy-theme-switcher/src/ngx-easy-theme-switcher.ts"],"sourcesContent":["import { Injectable, Inject, Optional, InjectionToken } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface ThemeConfig {\n /** Names of the two themes, e.g. ['light', 'dark'] */\n themes?: [string, string];\n /** CSS attribute/class to set on the document element */\n attribute?: string;\n /** Storage key for persisting theme preference */\n storageKey?: string;\n /** Default theme (index 0 or 1 from themes array) */\n defaultTheme?: string;\n}\n\nexport const THEME_CONFIG = new InjectionToken<ThemeConfig>('THEME_CONFIG');\n\nconst DEFAULT_CONFIG: ThemeConfig = {\n themes: ['light', 'dark'],\n attribute: 'data-theme',\n storageKey: 'ets-pref-theme',\n defaultTheme: 'light',\n};\n\n@Injectable({\n providedIn: 'root',\n})\nexport class ThemeService {\n private config: ThemeConfig;\n private currentThemeSubject: BehaviorSubject<string>;\n public currentTheme$: Observable<string>;\n\n constructor(@Optional() @Inject(THEME_CONFIG) config?: ThemeConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n this.currentThemeSubject = new BehaviorSubject<string>(\n this.getStoredTheme()\n );\n this.currentTheme$ = this.currentThemeSubject.asObservable();\n this.applyTheme(this.currentThemeSubject.value);\n }\n\n /** Get the current theme name */\n get currentTheme(): string {\n return this.currentThemeSubject.value;\n }\n\n /** Get all available theme names */\n get themes(): [string, string] {\n return this.config.themes!;\n }\n\n /** Toggle between the two configured themes */\n toggleTheme(): void {\n const [themeA, themeB] = this.config.themes!;\n const next = this.currentTheme === themeA ? themeB : themeA;\n this.setTheme(next);\n }\n\n /** Set a specific theme by name */\n setTheme(theme: string): void {\n if (!this.config.themes!.includes(theme)) {\n console.warn(\n `[EasyThemeSwitcher] Theme \"${theme}\" is not in configured themes:`,\n this.config.themes\n );\n return;\n }\n this.currentThemeSubject.next(theme);\n this.applyTheme(theme);\n this.storeTheme(theme);\n }\n\n private applyTheme(theme: string): void {\n const attr = this.config.attribute!;\n document.documentElement.setAttribute(attr, theme);\n }\n\n private getStoredTheme(): string {\n try {\n const stored = localStorage.getItem(this.config.storageKey!);\n if (stored && this.config.themes!.includes(stored)) {\n return stored;\n }\n } catch {\n // localStorage not available\n }\n return this.config.defaultTheme!;\n }\n\n private storeTheme(theme: string): void {\n try {\n localStorage.setItem(this.config.storageKey!, theme);\n } catch {\n // localStorage not available\n }\n }\n}\n","import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { Subject, takeUntil } from 'rxjs';\nimport { ThemeService } from '../theme.service';\n\nexport type IconFont =\n | 'fas' // FontAwesome Solid\n | 'far' // FontAwesome Regular\n | 'fal' // FontAwesome Light\n | 'fab' // FontAwesome Brands\n | 'material-icons'\n | 'material-icons-outlined'\n | 'material-symbols-outlined'\n | 'bi' // Bootstrap Icons\n | 'pi' // PrimeIcons\n | 'ion-icon'; // Ionicons\n\n@Component({\n selector: 'ets-theme-toggle',\n standalone: true,\n imports: [NgClass],\n template: `\n <button\n class=\"ets-toggle-btn\"\n (click)=\"toggle()\"\n [attr.aria-label]=\"'Switch to ' + oppositeTheme + ' mode'\"\n type=\"button\"\n >\n @if (svgIcon) {\n <span class=\"ets-icon ets-icon--svg\" [innerHTML]=\"svgIcon\"></span>\n } @else {\n <span\n class=\"ets-icon\"\n [ngClass]=\"iconClasses\"\n >{{ displayIconText }}</span>\n }\n </button>\n `,\n styles: `\n .ets-toggle-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n transition: background-color 0.3s, color 0.3s;\n color: inherit;\n font-size: 1.25rem;\n line-height: 1;\n }\n .ets-toggle-btn:hover {\n background-color: rgba(128, 128, 128, 0.15);\n }\n .ets-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n .ets-icon--svg {\n line-height: 0;\n }\n .ets-icon--svg ::ng-deep svg {\n width: 1.25rem;\n height: 1.25rem;\n fill: currentColor;\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ThemeToggleComponent implements OnInit, OnDestroy {\n /** Icon font abbreviation (e.g. 'fas', 'material-icons', 'bi') */\n @Input() iconFont?: IconFont;\n\n /** Custom SVG markup (overrides iconFont if provided) */\n @Input() svgIcon?: string;\n\n /** Icon name for the first theme (default light) */\n @Input() lightIcon?: string;\n\n /** Icon name for the second theme (default dark) */\n @Input() darkIcon?: string;\n\n protected oppositeTheme = '';\n protected iconClasses: Record<string, boolean> = {};\n protected displayIconText = '';\n\n private destroy$ = new Subject<void>();\n\n constructor(\n private themeService: ThemeService,\n private cdr: ChangeDetectorRef,\n ) {}\n\n ngOnInit(): void {\n this.updateState(this.themeService.currentTheme);\n this.themeService.currentTheme$\n .pipe(takeUntil(this.destroy$))\n .subscribe((theme) => {\n this.updateState(theme);\n this.cdr.markForCheck();\n });\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n toggle(): void {\n this.themeService.toggleTheme();\n }\n\n private updateState(currentTheme: string): void {\n const [lightTheme, darkTheme] = this.themeService.themes;\n const isLight = currentTheme === lightTheme;\n\n this.oppositeTheme = currentTheme === lightTheme ? darkTheme : lightTheme;\n\n const iconName = isLight\n ? (this.darkIcon ?? 'fa-moon')\n : (this.lightIcon ?? 'fa-sun');\n\n const font = this.iconFont ?? 'fas';\n\n if (font.startsWith('material')) {\n this.iconClasses = { [font]: true };\n this.displayIconText = iconName;\n } else {\n this.iconClasses = { [font]: true, [iconName]: true };\n this.displayIconText = '';\n }\n }\n}\n","/*\r\n * Public API Surface of ngx-easy-theme-switcher\r\n */\r\n\r\nexport { ThemeService, THEME_CONFIG } from './lib/theme.service';\r\nexport type { ThemeConfig } from './lib/theme.service';\r\nexport { ThemeToggleComponent } from './lib/theme-toggle/theme-toggle.component';\r\nexport type { IconFont } from './lib/theme-toggle/theme-toggle.component';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.ThemeService"],"mappings":";;;;;MAca,YAAY,GAAG,IAAI,cAAc,CAAc,cAAc;AAE1E,MAAM,cAAc,GAAgB;AAClC,IAAA,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;AACzB,IAAA,SAAS,EAAE,YAAY;AACvB,IAAA,UAAU,EAAE,gBAAgB;AAC5B,IAAA,YAAY,EAAE,OAAO;CACtB;MAKY,YAAY,CAAA;AACf,IAAA,MAAM;AACN,IAAA,mBAAmB;AACpB,IAAA,aAAa;AAEpB,IAAA,WAAA,CAA8C,MAAoB,EAAA;QAChE,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE;QAC9C,IAAI,CAAC,mBAAmB,GAAG,IAAI,eAAe,CAC5C,IAAI,CAAC,cAAc,EAAE,CACtB;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE;QAC5D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC;IACjD;;AAGA,IAAA,IAAI,YAAY,GAAA;AACd,QAAA,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK;IACvC;;AAGA,IAAA,IAAI,MAAM,GAAA;AACR,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,MAAO;IAC5B;;IAGA,WAAW,GAAA;QACT,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAO;AAC5C,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM;AAC3D,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IACrB;;AAGA,IAAA,QAAQ,CAAC,KAAa,EAAA;AACpB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;AACxC,YAAA,OAAO,CAAC,IAAI,CACV,CAAA,2BAAA,EAA8B,KAAK,CAAA,8BAAA,CAAgC,EACnE,IAAI,CAAC,MAAM,CAAC,MAAM,CACnB;YACD;QACF;AACA,QAAA,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC;AACpC,QAAA,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;AACtB,QAAA,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;IACxB;AAEQ,IAAA,UAAU,CAAC,KAAa,EAAA;AAC9B,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,SAAU;QACnC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;IACpD;IAEQ,cAAc,GAAA;AACpB,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAW,CAAC;AAC5D,YAAA,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;AAClD,gBAAA,OAAO,MAAM;YACf;QACF;AAAE,QAAA,MAAM;;QAER;AACA,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,YAAa;IAClC;AAEQ,IAAA,UAAU,CAAC,KAAa,EAAA;AAC9B,QAAA,IAAI;YACF,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAW,EAAE,KAAK,CAAC;QACtD;AAAE,QAAA,MAAM;;QAER;IACF;AApEW,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,YAAY,kBAKS,YAAY,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AALjC,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,YAAY,cAFX,MAAM,EAAA,CAAA;;4FAEP,YAAY,EAAA,UAAA,EAAA,CAAA;kBAHxB,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE,MAAM;AACnB,iBAAA;;0BAMc;;0BAAY,MAAM;2BAAC,YAAY;;;MCyCjC,oBAAoB,CAAA;AAoBrB,IAAA,YAAA;AACA,IAAA,GAAA;;AAnBD,IAAA,QAAQ;;AAGR,IAAA,OAAO;;AAGP,IAAA,SAAS;;AAGT,IAAA,QAAQ;IAEP,aAAa,GAAG,EAAE;IAClB,WAAW,GAA4B,EAAE;IACzC,eAAe,GAAG,EAAE;AAEtB,IAAA,QAAQ,GAAG,IAAI,OAAO,EAAQ;IAEtC,WAAA,CACU,YAA0B,EAC1B,GAAsB,EAAA;QADtB,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,GAAG,GAAH,GAAG;IACV;IAEH,QAAQ,GAAA;QACN,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;QAChD,IAAI,CAAC,YAAY,CAAC;AACf,aAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC;AAC7B,aAAA,SAAS,CAAC,CAAC,KAAK,KAAI;AACnB,YAAA,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AACvB,YAAA,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE;AACzB,QAAA,CAAC,CAAC;IACN;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;AACpB,QAAA,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE;IAC1B;IAEA,MAAM,GAAA;AACJ,QAAA,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE;IACjC;AAEQ,IAAA,WAAW,CAAC,YAAoB,EAAA;QACtC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM;AACxD,QAAA,MAAM,OAAO,GAAG,YAAY,KAAK,UAAU;AAE3C,QAAA,IAAI,CAAC,aAAa,GAAG,YAAY,KAAK,UAAU,GAAG,SAAS,GAAG,UAAU;QAEzE,MAAM,QAAQ,GAAG;AACf,eAAG,IAAI,CAAC,QAAQ,IAAI,SAAS;eAC1B,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC;AAEhC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,IAAI,KAAK;AAEnC,QAAA,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;YAC/B,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,GAAG,IAAI,EAAE;AACnC,YAAA,IAAI,CAAC,eAAe,GAAG,QAAQ;QACjC;aAAO;AACL,YAAA,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,QAAQ,GAAG,IAAI,EAAE;AACrD,YAAA,IAAI,CAAC,eAAe,GAAG,EAAE;QAC3B;IACF;wGA9DW,oBAAoB,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAAA,YAAA,EAAA,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAApB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,EAAA,QAAA,EAAA,UAAA,EAAA,OAAA,EAAA,SAAA,EAAA,SAAA,EAAA,WAAA,EAAA,QAAA,EAAA,UAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAnDrB;;;;;;;;;;;;;;;;AAgBT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,qdAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAjBS,OAAO,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;4FAoDN,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBAvDhC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,kBAAkB,cAChB,IAAI,EAAA,OAAA,EACP,CAAC,OAAO,CAAC,EAAA,QAAA,EACR;;;;;;;;;;;;;;;;GAgBT,EAAA,eAAA,EAiCgB,uBAAuB,CAAC,MAAM,EAAA,MAAA,EAAA,CAAA,qdAAA,CAAA,EAAA;8GAItC,QAAQ,EAAA,CAAA;sBAAhB;gBAGQ,OAAO,EAAA,CAAA;sBAAf;gBAGQ,SAAS,EAAA,CAAA;sBAAjB;gBAGQ,QAAQ,EAAA,CAAA;sBAAhB;;;ACnFH;;AAEG;;ACFH;;AAEG;;;;"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="ngx-easy-theme-switcher" />
5
+ export * from './public-api';
@@ -0,0 +1,27 @@
1
+ import { ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
2
+ import { ThemeService } from '../theme.service';
3
+ import * as i0 from "@angular/core";
4
+ export type IconFont = 'fas' | 'far' | 'fal' | 'fab' | 'material-icons' | 'material-icons-outlined' | 'material-symbols-outlined' | 'bi' | 'pi' | 'ion-icon';
5
+ export declare class ThemeToggleComponent implements OnInit, OnDestroy {
6
+ private themeService;
7
+ private cdr;
8
+ /** Icon font abbreviation (e.g. 'fas', 'material-icons', 'bi') */
9
+ iconFont?: IconFont;
10
+ /** Custom SVG markup (overrides iconFont if provided) */
11
+ svgIcon?: string;
12
+ /** Icon name for the first theme (default light) */
13
+ lightIcon?: string;
14
+ /** Icon name for the second theme (default dark) */
15
+ darkIcon?: string;
16
+ protected oppositeTheme: string;
17
+ protected iconClasses: Record<string, boolean>;
18
+ protected displayIconText: string;
19
+ private destroy$;
20
+ constructor(themeService: ThemeService, cdr: ChangeDetectorRef);
21
+ ngOnInit(): void;
22
+ ngOnDestroy(): void;
23
+ toggle(): void;
24
+ private updateState;
25
+ static ɵfac: i0.ɵɵFactoryDeclaration<ThemeToggleComponent, never>;
26
+ static ɵcmp: i0.ɵɵComponentDeclaration<ThemeToggleComponent, "ets-theme-toggle", never, { "iconFont": { "alias": "iconFont"; "required": false; }; "svgIcon": { "alias": "svgIcon"; "required": false; }; "lightIcon": { "alias": "lightIcon"; "required": false; }; "darkIcon": { "alias": "darkIcon"; "required": false; }; }, {}, never, never, true, never>;
27
+ }
@@ -0,0 +1,33 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+ import * as i0 from "@angular/core";
4
+ export interface ThemeConfig {
5
+ /** Names of the two themes, e.g. ['light', 'dark'] */
6
+ themes?: [string, string];
7
+ /** CSS attribute/class to set on the document element */
8
+ attribute?: string;
9
+ /** Storage key for persisting theme preference */
10
+ storageKey?: string;
11
+ /** Default theme (index 0 or 1 from themes array) */
12
+ defaultTheme?: string;
13
+ }
14
+ export declare const THEME_CONFIG: InjectionToken<ThemeConfig>;
15
+ export declare class ThemeService {
16
+ private config;
17
+ private currentThemeSubject;
18
+ currentTheme$: Observable<string>;
19
+ constructor(config?: ThemeConfig);
20
+ /** Get the current theme name */
21
+ get currentTheme(): string;
22
+ /** Get all available theme names */
23
+ get themes(): [string, string];
24
+ /** Toggle between the two configured themes */
25
+ toggleTheme(): void;
26
+ /** Set a specific theme by name */
27
+ setTheme(theme: string): void;
28
+ private applyTheme;
29
+ private getStoredTheme;
30
+ private storeTheme;
31
+ static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, [{ optional: true; }]>;
32
+ static ɵprov: i0.ɵɵInjectableDeclaration<ThemeService>;
33
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "ngx-easy-theme-switcher",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight Angular service and component for seamless dark/light theme switching. Supports FontAwesome, Material Icons, Bootstrap Icons, custom SVG icons, and popular icon fonts out of the box. Persistent theme selection with localStorage.",
5
+ "keywords": [
6
+ "angular",
7
+ "theme",
8
+ "dark theme",
9
+ "utils",
10
+ "service",
11
+ "ng",
12
+ "typescript",
13
+ "theme-switcher",
14
+ "dark-mode",
15
+ "light-mode",
16
+ "themes",
17
+ "angular-library",
18
+ "dark",
19
+ "light",
20
+ "toggle",
21
+ "icon",
22
+ "fontawesome",
23
+ "material-icons",
24
+ "bootstrap-icons"
25
+ ],
26
+ "author": "Gennady Artamonov",
27
+ "license": "SEE LICENSE IN LICENSE.md",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/cyclon-b/ngx-easy-theme-switcher.git"
31
+ },
32
+ "peerDependencies": {
33
+ "@angular/common": "^19.2.0",
34
+ "@angular/core": "^19.2.0"
35
+ },
36
+ "dependencies": {
37
+ "tslib": "^2.3.0"
38
+ },
39
+ "sideEffects": false,
40
+ "module": "fesm2022/ngx-easy-theme-switcher.mjs",
41
+ "typings": "index.d.ts",
42
+ "exports": {
43
+ "./package.json": {
44
+ "default": "./package.json"
45
+ },
46
+ ".": {
47
+ "types": "./index.d.ts",
48
+ "default": "./fesm2022/ngx-easy-theme-switcher.mjs"
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,4 @@
1
+ export { ThemeService, THEME_CONFIG } from './lib/theme.service';
2
+ export type { ThemeConfig } from './lib/theme.service';
3
+ export { ThemeToggleComponent } from './lib/theme-toggle/theme-toggle.component';
4
+ export type { IconFont } from './lib/theme-toggle/theme-toggle.component';