@yuuvis/client-components 3.0.0 → 3.1.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.
@@ -0,0 +1,64 @@
1
+ # @yuuvis/client-components/charts
2
+
3
+ Thin wrapper around [ag-charts-angular](https://www.ag-grid.com/charts/angular/quick-start/) wired into the `@yuuvis/material` design system. Charts pick up `--ymt-*` tokens (surface, text color, palette, outlines, typography) and react to light/dark/high-contrast mode changes via `ThemeService`.
4
+
5
+ ## Install
6
+
7
+ The peer dependencies are optional — only consumers using this entry point need them:
8
+
9
+ ```bash
10
+ npm install ag-charts-angular ag-charts-community
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { Component, signal } from '@angular/core';
17
+ import { YuvChartComponent } from '@yuuvis/client-components/charts';
18
+ import type { AgChartOptions } from 'ag-charts-community';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ imports: [YuvChartComponent],
23
+ template: `<yuv-chart [options]="options()"></yuv-chart>`
24
+ })
25
+ export class MyChartComponent {
26
+ readonly options = signal<AgChartOptions>({
27
+ title: { text: 'Quarterly Revenue' },
28
+ data: [
29
+ { quarter: 'Q1', revenue: 120 },
30
+ { quarter: 'Q2', revenue: 180 },
31
+ { quarter: 'Q3', revenue: 150 },
32
+ { quarter: 'Q4', revenue: 220 }
33
+ ],
34
+ series: [{ type: 'bar', xKey: 'quarter', yKey: 'revenue' }]
35
+ });
36
+ }
37
+ ```
38
+
39
+ The component automatically applies a yuv-themed `AgChartTheme` derived from the active `--ymt-*` tokens. Pass an explicit `[theme]` to override.
40
+
41
+ ## API
42
+
43
+ | Input | Type | Notes |
44
+ |-----------|-----------------------------------|----------------------------------------------------------------------|
45
+ | `options` | `AgChartOptions` (required) | Forwarded to `<ag-charts>`. The `theme` field is overridden. |
46
+ | `theme` | `AgChartTheme \| string` | Override the auto-generated yuv theme. Optional. |
47
+
48
+ ## Theming
49
+
50
+ `YuvChartThemeService` (provided in root) reads `--ymt-*` tokens from `document.documentElement` and rebuilds the chart theme whenever `ThemeService.mode()` or `ThemeService.currentTheme()` changes. Token map:
51
+
52
+ | `--ymt-*` token | ag-charts target |
53
+ |--------------------------------|-----------------------------------------------|
54
+ | `--ymt-surface` | `overrides.common.background.fill` |
55
+ | `--ymt-text-color` | titles, legend item label, axis title |
56
+ | `--ymt-text-color-subtle` | subtitle, axis label |
57
+ | `--ymt-primary` / `--ymt-success` / `--ymt-warning` / `--ymt-danger` / `--ymt-brand` | `palette.fills` |
58
+ | `--ymt-outline` | `palette.strokes` |
59
+ | `--ymt-outline-variant` | axis lines, ticks, gridlines |
60
+ | `--mat-sys-body-medium-font` | font family across labels/titles |
61
+
62
+ Tooltips render with the default `ag-charts-tooltip` class — host SCSS styles them with surface, outline, and corner tokens so they match the rest of the design system.
63
+
64
+ If you change tokens at runtime outside of `ThemeService` (e.g. flipping a class on `<body>`), call `YuvChartThemeService.refresh()` to rebuild.
@@ -0,0 +1,206 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DOCUMENT, signal, computed, effect, Injectable, input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { AgCharts } from 'ag-charts-angular';
4
+ import { ThemeService, HIGH_CONTRAST_THEME_KEY } from '@yuuvis/client-components/common';
5
+
6
+ class YuvChartThemeService {
7
+ #document = inject(DOCUMENT);
8
+ #themeService = inject(ThemeService);
9
+ #revision = signal(0, ...(ngDevMode ? [{ debugName: "#revision" }] : /* istanbul ignore next */ []));
10
+ chartTheme = computed(() => {
11
+ this.#revision();
12
+ return this.#buildTheme();
13
+ }, ...(ngDevMode ? [{ debugName: "chartTheme" }] : /* istanbul ignore next */ []));
14
+ constructor() {
15
+ effect((onCleanup) => {
16
+ this.#themeService.mode();
17
+ this.#themeService.currentTheme();
18
+ const win = this.#document.defaultView;
19
+ if (!win) {
20
+ this.#revision.update((v) => v + 1);
21
+ return;
22
+ }
23
+ const id = win.requestAnimationFrame(() => this.#revision.update((v) => v + 1));
24
+ onCleanup(() => win.cancelAnimationFrame(id));
25
+ });
26
+ }
27
+ refresh() {
28
+ this.#revision.update((v) => v + 1);
29
+ }
30
+ #buildTheme() {
31
+ const t = this.#tokens();
32
+ const isDark = this.#themeService.mode() === 'dark';
33
+ const isHighContrast = this.#themeService.currentTheme() === HIGH_CONTRAST_THEME_KEY;
34
+ const baseTheme = isDark ? 'ag-default-dark' : 'ag-default';
35
+ const palette = {
36
+ fills: [t.primary, t.success, t.warning, t.danger, t.brand].filter(Boolean),
37
+ strokes: [t.outline].filter(Boolean)
38
+ };
39
+ const fontFamily = t.fontFamily || 'Roboto, "Helvetica Neue", sans-serif';
40
+ return {
41
+ baseTheme,
42
+ palette,
43
+ overrides: {
44
+ common: {
45
+ background: { fill: t.surface || 'transparent' },
46
+ title: {
47
+ color: t.textColor,
48
+ fontFamily,
49
+ fontSize: 16,
50
+ fontWeight: 'bold'
51
+ },
52
+ subtitle: {
53
+ color: t.textColorSubtle,
54
+ fontFamily,
55
+ fontSize: 12
56
+ },
57
+ padding: { top: 16, right: 16, bottom: 16, left: 16 },
58
+ legend: {
59
+ item: {
60
+ label: {
61
+ color: t.textColor,
62
+ fontFamily,
63
+ fontSize: 12
64
+ },
65
+ marker: {
66
+ strokeWidth: isHighContrast ? 2 : 0
67
+ }
68
+ }
69
+ },
70
+ axes: {
71
+ number: this.#axisTheme(t, fontFamily),
72
+ category: this.#axisTheme(t, fontFamily),
73
+ time: this.#axisTheme(t, fontFamily),
74
+ log: this.#axisTheme(t, fontFamily)
75
+ }
76
+ }
77
+ }
78
+ };
79
+ }
80
+ #axisTheme(themeToken, fontFamily) {
81
+ return {
82
+ line: { stroke: themeToken.outlineVariant },
83
+ tick: { stroke: themeToken.outlineVariant },
84
+ label: {
85
+ color: themeToken.textColorSubtle,
86
+ fontFamily,
87
+ fontSize: 11
88
+ },
89
+ title: {
90
+ color: themeToken.textColor,
91
+ fontFamily,
92
+ fontSize: 12
93
+ },
94
+ gridLine: {
95
+ style: [{ stroke: themeToken.outlineVariant, lineDash: [] }]
96
+ }
97
+ };
98
+ }
99
+ #tokens() {
100
+ const win = this.#document.defaultView;
101
+ const body = this.#document.body;
102
+ if (!win || !body) {
103
+ return EMPTY_TOKENS;
104
+ }
105
+ const probe = this.#document.createElement('span');
106
+ probe.style.position = 'absolute';
107
+ probe.style.visibility = 'hidden';
108
+ probe.style.pointerEvents = 'none';
109
+ body.appendChild(probe);
110
+ try {
111
+ const resolveColor = (token) => {
112
+ probe.style.color = '';
113
+ probe.style.color = `var(${token})`;
114
+ return win.getComputedStyle(probe).color || '';
115
+ };
116
+ const resolveProp = (token, prop) => {
117
+ probe.style.fontFamily = '';
118
+ probe.style.fontFamily = `var(${token})`;
119
+ return win.getComputedStyle(probe)[prop] || '';
120
+ };
121
+ return {
122
+ surface: resolveColor('--ymt-surface'),
123
+ textColor: resolveColor('--ymt-text-color'),
124
+ textColorSubtle: resolveColor('--ymt-text-color-subtle'),
125
+ primary: resolveColor('--ymt-primary'),
126
+ brand: resolveColor('--ymt-brand'),
127
+ success: resolveColor('--ymt-success'),
128
+ warning: resolveColor('--ymt-warning'),
129
+ danger: resolveColor('--ymt-danger'),
130
+ outline: resolveColor('--ymt-outline'),
131
+ outlineVariant: resolveColor('--ymt-outline-variant'),
132
+ fontFamily: resolveProp('--mat-sys-body-medium-font', 'fontFamily')
133
+ };
134
+ }
135
+ finally {
136
+ body.removeChild(probe);
137
+ }
138
+ }
139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvChartThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
140
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvChartThemeService, providedIn: 'root' });
141
+ }
142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvChartThemeService, decorators: [{
143
+ type: Injectable,
144
+ args: [{ providedIn: 'root' }]
145
+ }], ctorParameters: () => [] });
146
+ const EMPTY_TOKENS = {
147
+ surface: '',
148
+ textColor: '',
149
+ textColorSubtle: '',
150
+ primary: '',
151
+ brand: '',
152
+ success: '',
153
+ warning: '',
154
+ danger: '',
155
+ outline: '',
156
+ outlineVariant: '',
157
+ fontFamily: ''
158
+ };
159
+
160
+ /**
161
+ * Wrapper component around AG Charts that automatically applies the
162
+ * application's chart theme based on the current UI theme (light, dark,
163
+ * high-contrast). Consumers provide standard AG Charts options and may
164
+ * optionally override the theme.
165
+ *
166
+ * @example
167
+ * ```html
168
+ * <yuv-chart [options]="chartOptions" />
169
+ * ```
170
+ */
171
+ class YuvChartComponent {
172
+ themeService = inject(YuvChartThemeService);
173
+ /** AG Charts configuration (data, series, axes, etc.). */
174
+ options = input.required(...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
175
+ /**
176
+ * Optional theme override. When provided, takes precedence over the
177
+ * theme resolved by {@link YuvChartThemeService} from the current UI mode.
178
+ */
179
+ theme = input(undefined, ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
180
+ /**
181
+ * Final options passed to AG Charts: the user-supplied {@link options}
182
+ * merged with either the explicit {@link theme} input or the theme
183
+ * derived from the active UI mode.
184
+ */
185
+ mergedOptions = computed(() => {
186
+ const userOptions = this.options();
187
+ const userTheme = this.theme();
188
+ return {
189
+ ...userOptions,
190
+ theme: userTheme ?? this.themeService.chartTheme()
191
+ };
192
+ }, ...(ngDevMode ? [{ debugName: "mergedOptions" }] : /* istanbul ignore next */ []));
193
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
194
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: YuvChartComponent, isStandalone: true, selector: "yuv-chart", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ag-charts [options]=\"mergedOptions()\" />\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px;background:var(--ymt-surface);color:var(--ymt-text-color);border-radius:var(--ymt-corner-s);font:var(--ymt-font-body);overflow:hidden}ag-charts{display:block;width:100%;height:100%}:host ::ng-deep .ag-charts-tooltip{background:var(--ymt-surface-container);color:var(--ymt-on-surface);border:1px solid var(--ymt-outline-variant);border-radius:var(--ymt-corner-xs);box-shadow:0 4px 12px #0000001f;font:var(--ymt-font-body-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}:host ::ng-deep .ag-charts-tooltip-heading{background:var(--ymt-surface-container-high);color:var(--ymt-on-surface);border-bottom:1px solid var(--ymt-outline-variant);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}\n"], dependencies: [{ kind: "component", type: AgCharts, selector: "ag-charts", inputs: ["options"], outputs: ["onChartReady"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
195
+ }
196
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: YuvChartComponent, decorators: [{
197
+ type: Component,
198
+ args: [{ selector: 'yuv-chart', standalone: true, imports: [AgCharts], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ag-charts [options]=\"mergedOptions()\" />\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px;background:var(--ymt-surface);color:var(--ymt-text-color);border-radius:var(--ymt-corner-s);font:var(--ymt-font-body);overflow:hidden}ag-charts{display:block;width:100%;height:100%}:host ::ng-deep .ag-charts-tooltip{background:var(--ymt-surface-container);color:var(--ymt-on-surface);border:1px solid var(--ymt-outline-variant);border-radius:var(--ymt-corner-xs);box-shadow:0 4px 12px #0000001f;font:var(--ymt-font-body-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}:host ::ng-deep .ag-charts-tooltip-heading{background:var(--ymt-surface-container-high);color:var(--ymt-on-surface);border-bottom:1px solid var(--ymt-outline-variant);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}\n"] }]
199
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }] } });
200
+
201
+ /**
202
+ * Generated bundle index. Do not edit.
203
+ */
204
+
205
+ export { YuvChartComponent, YuvChartThemeService };
206
+ //# sourceMappingURL=yuuvis-client-components-charts.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yuuvis-client-components-charts.mjs","sources":["../../../../../libs/yuuvis/client-components/charts/src/lib/chart-theme.service.ts","../../../../../libs/yuuvis/client-components/charts/src/lib/chart.component.ts","../../../../../libs/yuuvis/client-components/charts/src/lib/chart.component.html","../../../../../libs/yuuvis/client-components/charts/src/yuuvis-client-components-charts.ts"],"sourcesContent":["import { computed, DOCUMENT, effect, inject, Injectable, signal } from '@angular/core';\nimport { HIGH_CONTRAST_THEME_KEY, ThemeService } from '@yuuvis/client-components/common';\nimport type { AgChartTheme } from 'ag-charts-community';\n\n@Injectable({ providedIn: 'root' })\nexport class YuvChartThemeService {\n readonly #document = inject(DOCUMENT);\n readonly #themeService = inject(ThemeService);\n\n readonly #revision = signal(0);\n\n readonly chartTheme = computed<AgChartTheme>(() => {\n this.#revision();\n return this.#buildTheme();\n });\n\n constructor() {\n effect((onCleanup) => {\n this.#themeService.mode();\n this.#themeService.currentTheme();\n const win = this.#document.defaultView;\n if (!win) {\n this.#revision.update((v) => v + 1);\n return;\n }\n const id = win.requestAnimationFrame(() => this.#revision.update((v) => v + 1));\n onCleanup(() => win.cancelAnimationFrame(id));\n });\n }\n\n refresh(): void {\n this.#revision.update((v) => v + 1);\n }\n\n #buildTheme(): AgChartTheme {\n const t = this.#tokens();\n const isDark = this.#themeService.mode() === 'dark';\n const isHighContrast = this.#themeService.currentTheme() === HIGH_CONTRAST_THEME_KEY;\n const baseTheme: 'ag-default' | 'ag-default-dark' = isDark ? 'ag-default-dark' : 'ag-default';\n\n const palette = {\n fills: [t.primary, t.success, t.warning, t.danger, t.brand].filter(Boolean) as string[],\n strokes: [t.outline].filter(Boolean) as string[]\n };\n\n const fontFamily = t.fontFamily || 'Roboto, \"Helvetica Neue\", sans-serif';\n\n return {\n baseTheme,\n palette,\n overrides: {\n common: {\n background: { fill: t.surface || 'transparent' },\n title: {\n color: t.textColor,\n fontFamily,\n fontSize: 16,\n fontWeight: 'bold'\n },\n subtitle: {\n color: t.textColorSubtle,\n fontFamily,\n fontSize: 12\n },\n padding: { top: 16, right: 16, bottom: 16, left: 16 },\n legend: {\n item: {\n label: {\n color: t.textColor,\n fontFamily,\n fontSize: 12\n },\n marker: {\n strokeWidth: isHighContrast ? 2 : 0\n }\n }\n },\n axes: {\n number: this.#axisTheme(t, fontFamily),\n category: this.#axisTheme(t, fontFamily),\n time: this.#axisTheme(t, fontFamily),\n log: this.#axisTheme(t, fontFamily)\n }\n }\n }\n };\n }\n\n #axisTheme(themeToken: ThemeTokens, fontFamily: string) {\n return {\n line: { stroke: themeToken.outlineVariant },\n tick: { stroke: themeToken.outlineVariant },\n label: {\n color: themeToken.textColorSubtle,\n fontFamily,\n fontSize: 11\n },\n title: {\n color: themeToken.textColor,\n fontFamily,\n fontSize: 12\n },\n gridLine: {\n style: [{ stroke: themeToken.outlineVariant, lineDash: [] }]\n }\n };\n }\n\n #tokens(): ThemeTokens {\n const win = this.#document.defaultView;\n const body = this.#document.body;\n if (!win || !body) {\n return EMPTY_TOKENS;\n }\n const probe = this.#document.createElement('span');\n probe.style.position = 'absolute';\n probe.style.visibility = 'hidden';\n probe.style.pointerEvents = 'none';\n body.appendChild(probe);\n try {\n const resolveColor = (token: string): string => {\n probe.style.color = '';\n probe.style.color = `var(${token})`;\n return win.getComputedStyle(probe).color || '';\n };\n const resolveProp = (token: string, prop: 'fontFamily'): string => {\n probe.style.fontFamily = '';\n probe.style.fontFamily = `var(${token})`;\n return win.getComputedStyle(probe)[prop] || '';\n };\n return {\n surface: resolveColor('--ymt-surface'),\n textColor: resolveColor('--ymt-text-color'),\n textColorSubtle: resolveColor('--ymt-text-color-subtle'),\n primary: resolveColor('--ymt-primary'),\n brand: resolveColor('--ymt-brand'),\n success: resolveColor('--ymt-success'),\n warning: resolveColor('--ymt-warning'),\n danger: resolveColor('--ymt-danger'),\n outline: resolveColor('--ymt-outline'),\n outlineVariant: resolveColor('--ymt-outline-variant'),\n fontFamily: resolveProp('--mat-sys-body-medium-font', 'fontFamily')\n };\n } finally {\n body.removeChild(probe);\n }\n }\n}\n\ninterface ThemeTokens {\n surface: string;\n textColor: string;\n textColorSubtle: string;\n primary: string;\n brand: string;\n success: string;\n warning: string;\n danger: string;\n outline: string;\n outlineVariant: string;\n fontFamily: string;\n}\n\nconst EMPTY_TOKENS: ThemeTokens = {\n surface: '',\n textColor: '',\n textColorSubtle: '',\n primary: '',\n brand: '',\n success: '',\n warning: '',\n danger: '',\n outline: '',\n outlineVariant: '',\n fontFamily: ''\n};\n","import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';\nimport { AgCharts } from 'ag-charts-angular';\nimport type { AgChartOptions, AgChartTheme } from 'ag-charts-community';\nimport { YuvChartThemeService } from './chart-theme.service';\nimport { YuvChartOptions } from './chart.types';\n\n/**\n * Wrapper component around AG Charts that automatically applies the\n * application's chart theme based on the current UI theme (light, dark,\n * high-contrast). Consumers provide standard AG Charts options and may\n * optionally override the theme.\n *\n * @example\n * ```html\n * <yuv-chart [options]=\"chartOptions\" />\n * ```\n */\n@Component({\n selector: 'yuv-chart',\n standalone: true,\n imports: [AgCharts],\n templateUrl: './chart.component.html',\n styleUrl: './chart.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class YuvChartComponent {\n private readonly themeService = inject(YuvChartThemeService);\n\n /** AG Charts configuration (data, series, axes, etc.). */\n readonly options = input.required<YuvChartOptions>();\n\n /**\n * Optional theme override. When provided, takes precedence over the\n * theme resolved by {@link YuvChartThemeService} from the current UI mode.\n */\n readonly theme = input<AgChartTheme | string | undefined>(undefined);\n\n /**\n * Final options passed to AG Charts: the user-supplied {@link options}\n * merged with either the explicit {@link theme} input or the theme\n * derived from the active UI mode.\n */\n protected readonly mergedOptions = computed<AgChartOptions>(() => {\n const userOptions = this.options();\n const userTheme = this.theme();\n return {\n ...userOptions,\n theme: userTheme ?? this.themeService.chartTheme()\n } as AgChartOptions;\n });\n}\n","<ag-charts [options]=\"mergedOptions()\" />\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1074,7 +1074,10 @@ class ScrollButtonsDirective {
1074
1074
  color: 'var(--ymt-text-color-subtle)',
1075
1075
  width: 'var(--ymt-sizing-m)',
1076
1076
  height: 'var(--ymt-sizing-m)',
1077
- backgroundColor: 'var(--ymt-surface-hover)'
1077
+ backgroundColor: 'var(--ymt-surface-hover)',
1078
+ // Stay clickable even when wrapped in a disabled <mat-form-field>,
1079
+ // which sets pointer-events: none on its interior.
1080
+ 'pointer-events': 'auto'
1078
1081
  });
1079
1082
  const iconEl = this.#renderer.createElement('span');
1080
1083
  iconEl.classList.add('material-symbols-sharp');
@@ -1440,6 +1443,53 @@ class ThemeService {
1440
1443
  constructor() {
1441
1444
  this.#initializeModes();
1442
1445
  }
1446
+ setMode(mode) {
1447
+ this.#saveSettings(this.#MODE_STORAGE_KEY, { mode });
1448
+ }
1449
+ setCustomTheme(key) {
1450
+ // Reset contrast to system on custom theme change
1451
+ const previousTheme = this.customTheme();
1452
+ const body = this.#document.getElementsByTagName('body')[0];
1453
+ if (previousTheme?.key)
1454
+ this.#renderer.removeClass(body, previousTheme.key);
1455
+ const selectedTheme = this.customThemes().find((theme) => theme.key === key);
1456
+ if (selectedTheme) {
1457
+ this.customTheme.set(selectedTheme);
1458
+ this.#currentTheme.set(selectedTheme.key);
1459
+ if (selectedTheme.key)
1460
+ this.#renderer.addClass(body, selectedTheme.key);
1461
+ // Check if custom theme has light or dark mode
1462
+ if ((selectedTheme.hasDarkTheme && selectedTheme.hasLightTheme) ||
1463
+ (!selectedTheme.hasDarkTheme && !selectedTheme.hasLightTheme)) {
1464
+ // If both, do nothing further with this (enable group)
1465
+ this.#disableMode.set(false);
1466
+ }
1467
+ else {
1468
+ // Else disable mode button group and set the corresponding mode active
1469
+ if (selectedTheme.hasDarkTheme) {
1470
+ this.#mode.set('dark');
1471
+ }
1472
+ else if (selectedTheme.hasLightTheme) {
1473
+ this.#mode.set('light');
1474
+ }
1475
+ this.#disableMode.set(true);
1476
+ }
1477
+ this.#saveSettings(this.#CUSTOM_THEME_STORAGE_KEY, { customTheme: key });
1478
+ }
1479
+ else {
1480
+ if (key === DEFAULT_THEME_KEY) {
1481
+ this.#disableMode.set(false);
1482
+ this.#renderer.removeClass(body, HIGH_CONTRAST_THEME_KEY);
1483
+ this.#deleteSettings(this.#CUSTOM_THEME_STORAGE_KEY);
1484
+ this.customTheme.set(DEFAULT_THEME);
1485
+ this.#currentTheme.set(DEFAULT_THEME_KEY);
1486
+ }
1487
+ // Default theme or no theme found
1488
+ }
1489
+ }
1490
+ toggleTheme(theme) {
1491
+ this.#mode.set(theme);
1492
+ }
1443
1493
  #saveSettings(key, settings) {
1444
1494
  if (typeof settings === 'object') {
1445
1495
  this.#storage.setItem(key, JSON.stringify(settings)).subscribe();
@@ -1454,16 +1504,21 @@ class ThemeService {
1454
1504
  }
1455
1505
  #checkPrefersContrast() {
1456
1506
  const prefersHighContrast = window.matchMedia('(prefers-contrast: more)').matches;
1507
+ return prefersHighContrast;
1457
1508
  }
1458
1509
  #initializeModes() {
1459
1510
  // Theme initialization
1460
- forkJoin([this.#storage.getItem(this.#MODE_STORAGE_KEY), this.#storage.getItem(this.#CUSTOM_THEME_STORAGE_KEY)])
1511
+ forkJoin([
1512
+ this.#storage.getItem(this.#MODE_STORAGE_KEY),
1513
+ this.#storage.getItem(this.#CUSTOM_THEME_STORAGE_KEY)
1514
+ ])
1461
1515
  .pipe(map$1(([savedTheme, savedCustomTheme]) => {
1462
1516
  if (savedTheme) {
1463
1517
  try {
1464
1518
  this.#mode.set(JSON.parse(savedTheme).mode);
1465
1519
  }
1466
1520
  catch (error) {
1521
+ console.error('Error parsing saved theme mode from storage:', error);
1467
1522
  this.#mode.set(this.#mode());
1468
1523
  }
1469
1524
  }
@@ -1480,54 +1535,12 @@ class ThemeService {
1480
1535
  this.setCustomTheme(customThemeKey);
1481
1536
  }
1482
1537
  catch (error) {
1483
- // ignore
1538
+ console.error('Error parsing saved custom theme from storage:', error);
1484
1539
  }
1485
1540
  }
1486
1541
  }))
1487
1542
  .subscribe();
1488
1543
  }
1489
- setMode(mode) {
1490
- this.#saveSettings(this.#MODE_STORAGE_KEY, { mode });
1491
- }
1492
- setCustomTheme(key) {
1493
- // Reset contrast to system on custom theme change
1494
- const previousTheme = this.customTheme();
1495
- const body = this.#document.getElementsByTagName('body')[0];
1496
- previousTheme && previousTheme.key && this.#renderer.removeClass(body, previousTheme.key);
1497
- const selectedTheme = this.customThemes().find((theme) => theme.key === key);
1498
- if (selectedTheme) {
1499
- this.customTheme.set(selectedTheme);
1500
- this.#currentTheme.set(selectedTheme.key);
1501
- selectedTheme.key && this.#renderer.addClass(body, selectedTheme.key);
1502
- // Check if custom theme has light or dark mode
1503
- if ((selectedTheme.hasDarkTheme && selectedTheme.hasLightTheme) || (!selectedTheme.hasDarkTheme && !selectedTheme.hasLightTheme)) {
1504
- // If both, do nothing further with this (enable group)
1505
- this.#disableMode.set(false);
1506
- }
1507
- else {
1508
- // Else disable mode button group and set the corresponding mode active
1509
- if (selectedTheme.hasDarkTheme) {
1510
- this.#mode.set('dark');
1511
- }
1512
- else if (selectedTheme.hasLightTheme) {
1513
- this.#mode.set('light');
1514
- }
1515
- this.#disableMode.set(true);
1516
- }
1517
- this.#saveSettings(this.#CUSTOM_THEME_STORAGE_KEY, { customTheme: key });
1518
- }
1519
- else {
1520
- if (key === DEFAULT_THEME_KEY) {
1521
- this.#disableMode.set(false);
1522
- this.#renderer.removeClass(body, HIGH_CONTRAST_THEME_KEY);
1523
- this.#deleteSettings(this.#CUSTOM_THEME_STORAGE_KEY);
1524
- }
1525
- // Default theme or no theme found
1526
- }
1527
- }
1528
- toggleTheme(theme) {
1529
- this.#mode.set(theme);
1530
- }
1531
1544
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1532
1545
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, providedIn: 'root' });
1533
1546
  }