@memberjunction/ng-shared-generic 5.48.0 → 5.49.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/dist/lib/_tokens.scss +25 -1
- package/dist/lib/ag-grid-theme-params.d.ts +35 -0
- package/dist/lib/ag-grid-theme-params.d.ts.map +1 -0
- package/dist/lib/ag-grid-theme-params.js +35 -0
- package/dist/lib/ag-grid-theme-params.js.map +1 -0
- package/dist/lib/theme.service.d.ts +101 -2
- package/dist/lib/theme.service.d.ts.map +1 -1
- package/dist/lib/theme.service.js +260 -13
- package/dist/lib/theme.service.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +2 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +7 -6
package/dist/lib/_tokens.scss
CHANGED
|
@@ -199,6 +199,23 @@
|
|
|
199
199
|
--mj-status-info-text: var(--mj-color-info-700);
|
|
200
200
|
--mj-status-info-border: var(--mj-color-info-500);
|
|
201
201
|
|
|
202
|
+
/* Data-Visualization Categorical Palette
|
|
203
|
+
Distinct series colors for charts/diagrams (Venn, donut, treemap, etc.).
|
|
204
|
+
Components read these instead of hardcoding a hex array; the interactive-
|
|
205
|
+
component theme bridge exposes them as `styles.chartPalette`. These are the
|
|
206
|
+
default (theme-agnostic) categorical hues — Phase 2 derivation re-tints them
|
|
207
|
+
from the org brand by overriding these same tokens. */
|
|
208
|
+
--mj-viz-1: #2196F3; /* blue */
|
|
209
|
+
--mj-viz-2: #4CAF50; /* green */
|
|
210
|
+
--mj-viz-3: #FF9800; /* orange */
|
|
211
|
+
--mj-viz-4: #E91E63; /* pink */
|
|
212
|
+
--mj-viz-5: #9C27B0; /* purple */
|
|
213
|
+
--mj-viz-6: #00BCD4; /* cyan */
|
|
214
|
+
--mj-viz-7: #F44336; /* red */
|
|
215
|
+
--mj-viz-8: #8BC34A; /* light green */
|
|
216
|
+
--mj-viz-9: #FF5722; /* deep orange */
|
|
217
|
+
--mj-viz-10: #3F51B5; /* indigo */
|
|
218
|
+
|
|
202
219
|
/* Application Accent (set per-application from metadata)
|
|
203
220
|
Default uses amber accent, but apps can override to use primary/tertiary/custom */
|
|
204
221
|
--mj-app-accent: var(--mj-brand-accent);
|
|
@@ -229,6 +246,9 @@
|
|
|
229
246
|
--mj-logo-mark-inverse: url('/assets/mj-mark-white.svg');
|
|
230
247
|
--mj-logo-wordmark: url('/assets/MJ_logo.svg');
|
|
231
248
|
--mj-logo-color: #264FAF;
|
|
249
|
+
/* Header mark geometry. */
|
|
250
|
+
--mj-logo-width: 32px;
|
|
251
|
+
--mj-logo-height: 18px;
|
|
232
252
|
|
|
233
253
|
/* ========================================
|
|
234
254
|
TYPOGRAPHY
|
|
@@ -363,7 +383,11 @@
|
|
|
363
383
|
|
|
364
384
|
--mj-bg-page: var(--mj-color-neutral-900);
|
|
365
385
|
--mj-bg-surface: var(--mj-color-neutral-800);
|
|
366
|
-
|
|
386
|
+
/* Card sits between surface (800) and elevated (700). Derived from the neutral
|
|
387
|
+
primitives (not a literal) so org brand overlays re-tint it in dark mode; the
|
|
388
|
+
68/32 srgb-linear split reproduces the historical #253347 with MJ's defaults
|
|
389
|
+
and matches the theme engine's mixHex(neutral-800, neutral-700, 0.32) exactly. */
|
|
390
|
+
--mj-bg-surface-card: color-mix(in srgb-linear, var(--mj-color-neutral-800) 68%, var(--mj-color-neutral-700));
|
|
367
391
|
--mj-bg-surface-elevated: var(--mj-color-neutral-700);
|
|
368
392
|
--mj-bg-surface-sunken: var(--mj-color-neutral-950);
|
|
369
393
|
--mj-bg-surface-hover: var(--mj-color-neutral-600);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AG Grid theme parameters mapping the `--mj-*` design-token contract onto
|
|
3
|
+
* AG Grid's theming API, so every MJ grid follows light/dark and the org brand
|
|
4
|
+
* overlay identically. Consumers pass this to AG Grid's theme builder:
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { themeAlpine, type Theme } from 'ag-grid-community';
|
|
8
|
+
* import { MJ_AG_GRID_THEME_PARAMS } from '@memberjunction/ng-shared-generic';
|
|
9
|
+
*
|
|
10
|
+
* public GridTheme: Theme = themeAlpine.withParams(MJ_AG_GRID_THEME_PARAMS);
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Kept as a plain `as const` object (no ag-grid import) so this package carries no
|
|
14
|
+
* grid dependency; `withParams` accepts it structurally. One definition prevents the
|
|
15
|
+
* per-grid copies of this block from drifting.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MJ_AG_GRID_THEME_PARAMS: {
|
|
18
|
+
readonly backgroundColor: "var(--mj-bg-surface)";
|
|
19
|
+
readonly foregroundColor: "var(--mj-text-primary)";
|
|
20
|
+
readonly textColor: "var(--mj-text-primary)";
|
|
21
|
+
readonly borderColor: "var(--mj-border-default)";
|
|
22
|
+
readonly chromeBackgroundColor: "var(--mj-bg-surface-card)";
|
|
23
|
+
readonly headerBackgroundColor: "var(--mj-bg-surface-card)";
|
|
24
|
+
readonly headerTextColor: "var(--mj-text-secondary)";
|
|
25
|
+
readonly cellTextColor: "var(--mj-text-primary)";
|
|
26
|
+
readonly subtleTextColor: "var(--mj-text-muted)";
|
|
27
|
+
readonly dataBackgroundColor: "var(--mj-bg-surface)";
|
|
28
|
+
readonly oddRowBackgroundColor: "var(--mj-bg-surface-card)";
|
|
29
|
+
readonly rowHoverColor: "var(--mj-bg-surface-hover, color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface)))";
|
|
30
|
+
readonly selectedRowBackgroundColor: "color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface))";
|
|
31
|
+
readonly accentColor: "var(--mj-brand-primary)";
|
|
32
|
+
readonly borderRadius: "var(--mj-radius-sm)";
|
|
33
|
+
readonly browserColorScheme: "inherit";
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=ag-grid-theme-params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ag-grid-theme-params.d.ts","sourceRoot":"","sources":["../../src/lib/ag-grid-theme-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;CAiB1B,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AG Grid theme parameters mapping the `--mj-*` design-token contract onto
|
|
3
|
+
* AG Grid's theming API, so every MJ grid follows light/dark and the org brand
|
|
4
|
+
* overlay identically. Consumers pass this to AG Grid's theme builder:
|
|
5
|
+
*
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { themeAlpine, type Theme } from 'ag-grid-community';
|
|
8
|
+
* import { MJ_AG_GRID_THEME_PARAMS } from '@memberjunction/ng-shared-generic';
|
|
9
|
+
*
|
|
10
|
+
* public GridTheme: Theme = themeAlpine.withParams(MJ_AG_GRID_THEME_PARAMS);
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Kept as a plain `as const` object (no ag-grid import) so this package carries no
|
|
14
|
+
* grid dependency; `withParams` accepts it structurally. One definition prevents the
|
|
15
|
+
* per-grid copies of this block from drifting.
|
|
16
|
+
*/
|
|
17
|
+
export const MJ_AG_GRID_THEME_PARAMS = {
|
|
18
|
+
backgroundColor: 'var(--mj-bg-surface)',
|
|
19
|
+
foregroundColor: 'var(--mj-text-primary)',
|
|
20
|
+
textColor: 'var(--mj-text-primary)',
|
|
21
|
+
borderColor: 'var(--mj-border-default)',
|
|
22
|
+
chromeBackgroundColor: 'var(--mj-bg-surface-card)',
|
|
23
|
+
headerBackgroundColor: 'var(--mj-bg-surface-card)',
|
|
24
|
+
headerTextColor: 'var(--mj-text-secondary)',
|
|
25
|
+
cellTextColor: 'var(--mj-text-primary)',
|
|
26
|
+
subtleTextColor: 'var(--mj-text-muted)',
|
|
27
|
+
dataBackgroundColor: 'var(--mj-bg-surface)',
|
|
28
|
+
oddRowBackgroundColor: 'var(--mj-bg-surface-card)',
|
|
29
|
+
rowHoverColor: 'var(--mj-bg-surface-hover, color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface)))',
|
|
30
|
+
selectedRowBackgroundColor: 'color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface))',
|
|
31
|
+
accentColor: 'var(--mj-brand-primary)',
|
|
32
|
+
borderRadius: 'var(--mj-radius-sm)',
|
|
33
|
+
browserColorScheme: 'inherit',
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=ag-grid-theme-params.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ag-grid-theme-params.js","sourceRoot":"","sources":["../../src/lib/ag-grid-theme-params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,eAAe,EAAE,sBAAsB;IACvC,eAAe,EAAE,wBAAwB;IACzC,SAAS,EAAE,wBAAwB;IACnC,WAAW,EAAE,0BAA0B;IACvC,qBAAqB,EAAE,2BAA2B;IAClD,qBAAqB,EAAE,2BAA2B;IAClD,eAAe,EAAE,0BAA0B;IAC3C,aAAa,EAAE,wBAAwB;IACvC,eAAe,EAAE,sBAAsB;IACvC,mBAAmB,EAAE,sBAAsB;IAC3C,qBAAqB,EAAE,2BAA2B;IAClD,aAAa,EAAE,kGAAkG;IACjH,0BAA0B,EAAE,uEAAuE;IACnG,WAAW,EAAE,yBAAyB;IACtC,YAAY,EAAE,qBAAqB;IACnC,kBAAkB,EAAE,SAAS;CACrB,CAAC","sourcesContent":["/**\n * Shared AG Grid theme parameters mapping the `--mj-*` design-token contract onto\n * AG Grid's theming API, so every MJ grid follows light/dark and the org brand\n * overlay identically. Consumers pass this to AG Grid's theme builder:\n *\n * ```typescript\n * import { themeAlpine, type Theme } from 'ag-grid-community';\n * import { MJ_AG_GRID_THEME_PARAMS } from '@memberjunction/ng-shared-generic';\n *\n * public GridTheme: Theme = themeAlpine.withParams(MJ_AG_GRID_THEME_PARAMS);\n * ```\n *\n * Kept as a plain `as const` object (no ag-grid import) so this package carries no\n * grid dependency; `withParams` accepts it structurally. One definition prevents the\n * per-grid copies of this block from drifting.\n */\nexport const MJ_AG_GRID_THEME_PARAMS = {\n backgroundColor: 'var(--mj-bg-surface)',\n foregroundColor: 'var(--mj-text-primary)',\n textColor: 'var(--mj-text-primary)',\n borderColor: 'var(--mj-border-default)',\n chromeBackgroundColor: 'var(--mj-bg-surface-card)',\n headerBackgroundColor: 'var(--mj-bg-surface-card)',\n headerTextColor: 'var(--mj-text-secondary)',\n cellTextColor: 'var(--mj-text-primary)',\n subtleTextColor: 'var(--mj-text-muted)',\n dataBackgroundColor: 'var(--mj-bg-surface)',\n oddRowBackgroundColor: 'var(--mj-bg-surface-card)',\n rowHoverColor: 'var(--mj-bg-surface-hover, color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface)))',\n selectedRowBackgroundColor: 'color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface))',\n accentColor: 'var(--mj-brand-primary)',\n borderRadius: 'var(--mj-radius-sm)',\n browserColorScheme: 'inherit',\n} as const;\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
|
+
import { DerivedTheme, ThemeLogos, ThemeSeeds } from '@memberjunction/theme-engine';
|
|
2
3
|
import * as i0 from "@angular/core";
|
|
3
4
|
/**
|
|
4
5
|
* Defines a theme available in the application.
|
|
@@ -14,6 +15,12 @@ export interface ThemeDefinition {
|
|
|
14
15
|
BaseTheme: 'light' | 'dark';
|
|
15
16
|
/** URL to the CSS file with token overrides (omit for built-in themes) */
|
|
16
17
|
CssUrl?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Derived --mj-* token overrides. Populated for brand themes emitted
|
|
20
|
+
* from seeds; an alternative/companion to CssUrl for token-based application and
|
|
21
|
+
* for feeding the Theme Builder preview without a round-trip through CSS.
|
|
22
|
+
*/
|
|
23
|
+
Tokens?: Record<string, string>;
|
|
17
24
|
/** Whether this is a built-in theme (light/dark) */
|
|
18
25
|
IsBuiltIn: boolean;
|
|
19
26
|
/** Optional description shown in theme picker */
|
|
@@ -41,13 +48,31 @@ export interface ThemeDefinition {
|
|
|
41
48
|
export declare class ThemeService {
|
|
42
49
|
private _preference$;
|
|
43
50
|
private _appliedTheme$;
|
|
51
|
+
/**
|
|
52
|
+
* The active org brand overlay id, applied independently of the user's light/dark
|
|
53
|
+
* choice and preserved across Reset()/logout.
|
|
54
|
+
*/
|
|
55
|
+
private _brandOverlayId;
|
|
56
|
+
/** The user's starred (favorited) brand theme ids, reactive for menus/badges. */
|
|
57
|
+
private _starred$;
|
|
58
|
+
/** Emits when the set of saved themes changes (create/rename/delete/duplicate/default)
|
|
59
|
+
* so open dashboards (Theme Studio, Manage Themes) refresh without a page reload. */
|
|
60
|
+
private _themesChanged$;
|
|
44
61
|
private _initialized;
|
|
45
62
|
private systemMediaQuery;
|
|
46
63
|
private boundSystemThemeHandler;
|
|
47
|
-
/** Registry of
|
|
64
|
+
/** Registry of USER-SELECTABLE themes (shown in the theme picker), seeded with light + dark. */
|
|
48
65
|
private themeRegistry;
|
|
66
|
+
/**
|
|
67
|
+
* Registry of ORG BRAND overlays (from RegisterBrandTheme). Kept separate from
|
|
68
|
+
* themeRegistry so brand overlays do NOT appear in the user's light/dark theme
|
|
69
|
+
* picker — the org brand is applied independently of the user's mode (decision #1).
|
|
70
|
+
*/
|
|
71
|
+
private brandRegistry;
|
|
49
72
|
/** Cache of loaded <link> elements by theme ID to avoid re-downloading */
|
|
50
73
|
private loadedCssLinks;
|
|
74
|
+
/** Session-scoped Blob object URLs for brand overlays, keyed by theme ID (for revocation on re-register). */
|
|
75
|
+
private brandBlobUrls;
|
|
51
76
|
/**
|
|
52
77
|
* Observable for user's theme preference (theme ID or 'system')
|
|
53
78
|
*/
|
|
@@ -86,6 +111,59 @@ export declare class ThemeService {
|
|
|
86
111
|
* Register multiple custom themes at once.
|
|
87
112
|
*/
|
|
88
113
|
RegisterThemes(themes: ThemeDefinition[]): void;
|
|
114
|
+
/**
|
|
115
|
+
* Register a brand theme from its seeds.
|
|
116
|
+
*
|
|
117
|
+
* Derives the full --mj-* token contract with @memberjunction/theme-engine, emits
|
|
118
|
+
* the `[data-theme-overlay="<id>"]` CSS, and publishes it as a session-scoped Blob
|
|
119
|
+
* URL that plugs into the existing CssUrl overlay path — so brand themes apply
|
|
120
|
+
* through the same machinery as file-based custom themes. Returns the derived
|
|
121
|
+
* theme so callers can surface the a11y contrast report / previews.
|
|
122
|
+
*
|
|
123
|
+
* The emitted overlay overrides only the primitive ramps (plus viz/shadow/font/
|
|
124
|
+
* radius); every semantic token and the `[data-theme="dark"]` block reference those
|
|
125
|
+
* primitives, so both light and dark re-point automatically off one block.
|
|
126
|
+
*
|
|
127
|
+
* @param params.seeds ThemeSeeds object or the entity's Seeds JSON string.
|
|
128
|
+
*/
|
|
129
|
+
RegisterBrandTheme(params: {
|
|
130
|
+
id: string;
|
|
131
|
+
name: string;
|
|
132
|
+
seeds: ThemeSeeds | string;
|
|
133
|
+
description?: string;
|
|
134
|
+
baseTheme?: 'light' | 'dark';
|
|
135
|
+
/** Optional logo variant URLs + geometry, emitted as --mj-logo-*. */
|
|
136
|
+
logos?: ThemeLogos;
|
|
137
|
+
/** Advanced: per-token overrides (JSON map or object) merged over the derived vars. */
|
|
138
|
+
overrides?: Record<string, string> | string | null;
|
|
139
|
+
/** Advanced: raw CSS appended to the overlay, auto-scoped to this theme. */
|
|
140
|
+
customCss?: string | null;
|
|
141
|
+
}): DerivedTheme;
|
|
142
|
+
/** Normalize the advanced overrides input (JSON string, object, or null) to a token map. */
|
|
143
|
+
private parseOverrides;
|
|
144
|
+
/**
|
|
145
|
+
* The active org brand overlay id, or null if none is applied.
|
|
146
|
+
*/
|
|
147
|
+
get BrandOverlayId(): string | null;
|
|
148
|
+
/**
|
|
149
|
+
* Apply an org brand overlay **independently of the user's light/dark mode.
|
|
150
|
+
* The overlay layers under whatever base theme is
|
|
151
|
+
* active and is re-asserted whenever the user toggles light/dark. Persists the id
|
|
152
|
+
* to localStorage so the pre-paint script restores the attribute on next load.
|
|
153
|
+
*
|
|
154
|
+
* @param id id of a brand theme registered via {@link RegisterBrandTheme}.
|
|
155
|
+
* @param options.persist pass `false` for session-only overlays backed by Blob URLs
|
|
156
|
+
* (e.g. Theme Studio's draft workspace preview) — a Blob URL doesn't survive a
|
|
157
|
+
* reload, so persisting its id would leave the pre-paint script pointing at a
|
|
158
|
+
* dangling overlay and clobber the user's real persisted selection. Defaults true.
|
|
159
|
+
*/
|
|
160
|
+
ApplyBrandOverlay(id: string, options?: {
|
|
161
|
+
persist?: boolean;
|
|
162
|
+
}): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Remove the active org brand overlay (revert to the base MJ tokens).
|
|
165
|
+
*/
|
|
166
|
+
ClearBrandOverlay(): void;
|
|
89
167
|
/**
|
|
90
168
|
* Initialize the theme service.
|
|
91
169
|
* Call after login when UserInfoEngine is available.
|
|
@@ -128,8 +206,9 @@ export declare class ThemeService {
|
|
|
128
206
|
*/
|
|
129
207
|
private loadThemeCss;
|
|
130
208
|
/**
|
|
131
|
-
* Disable (not remove)
|
|
209
|
+
* Disable (not remove) custom theme CSS <link> elements.
|
|
132
210
|
* Disabling rather than removing avoids re-downloading when switching back.
|
|
211
|
+
* @param keepId optional theme id to leave enabled (e.g. the active brand overlay).
|
|
133
212
|
*/
|
|
134
213
|
private disableAllCustomCss;
|
|
135
214
|
/**
|
|
@@ -150,6 +229,26 @@ export declare class ThemeService {
|
|
|
150
229
|
* Handle system theme change (only applies if in 'system' mode)
|
|
151
230
|
*/
|
|
152
231
|
private onSystemThemeChange;
|
|
232
|
+
/** Reactive list of the user's starred brand theme ids. */
|
|
233
|
+
get Starred$(): Observable<string[]>;
|
|
234
|
+
/** Emits after any saved-theme mutation so open dashboards can refresh their lists. */
|
|
235
|
+
get ThemesChanged$(): Observable<void>;
|
|
236
|
+
/** Broadcast that the set of saved themes changed (create/rename/delete/duplicate/default). */
|
|
237
|
+
NotifyThemesChanged(): void;
|
|
238
|
+
/** The user's starred brand theme ids (synchronous). */
|
|
239
|
+
GetStarredThemeIds(): string[];
|
|
240
|
+
/** Whether a theme id is starred by the user. */
|
|
241
|
+
IsStarred(id: string): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Toggle a theme's starred state and persist to User Settings.
|
|
244
|
+
* @returns the new starred state (true = now starred).
|
|
245
|
+
*/
|
|
246
|
+
ToggleStar(id: string): Promise<boolean>;
|
|
247
|
+
private loadStarredIds;
|
|
248
|
+
/** The brand theme the user last applied (or null to follow the org default). */
|
|
249
|
+
GetSelectedBrandThemeId(): string | null;
|
|
250
|
+
/** Persist (or clear, with null) the user's chosen brand theme so login restores it. */
|
|
251
|
+
SetSelectedBrandTheme(id: string | null): Promise<void>;
|
|
153
252
|
/**
|
|
154
253
|
* Load theme preference from User Settings.
|
|
155
254
|
* Accepts any registered theme ID or 'system'.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.service.d.ts","sourceRoot":"","sources":["../../src/lib/theme.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,UAAU,
|
|
1
|
+
{"version":3,"file":"theme.service.d.ts","sourceRoot":"","sources":["../../src/lib/theme.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
-
import { BehaviorSubject } from 'rxjs';
|
|
2
|
+
import { BehaviorSubject, Subject } from 'rxjs';
|
|
3
3
|
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
4
|
+
import { derive, emitLogoOverlayCss, emitOverlayCss } from '@memberjunction/theme-engine';
|
|
4
5
|
import * as i0 from "@angular/core";
|
|
5
6
|
/**
|
|
6
7
|
* Setting key for theme preference in MJ: User Settings entity
|
|
@@ -20,6 +21,18 @@ const THEME_SETTING_KEY = 'Explorer.Theme';
|
|
|
20
21
|
* cleared on logout via Reset().
|
|
21
22
|
*/
|
|
22
23
|
const PRELOAD_BASE_THEME_KEY = 'mj-theme';
|
|
24
|
+
/**
|
|
25
|
+
* localStorage key holding the active org **brand overlay** id, mirrored so the
|
|
26
|
+
* inline pre-paint script can restore `data-theme-overlay` on first paint (the org
|
|
27
|
+
* brand persists across the user's light/dark choice AND across logout). The overlay
|
|
28
|
+
* CSS itself is a session-scoped Blob re-emitted at login bootstrap, so this key only
|
|
29
|
+
* restores the attribute — brand colors repaint once bootstrap re-injects the CSS.
|
|
30
|
+
*/
|
|
31
|
+
const PRELOAD_OVERLAY_KEY = 'mj-theme-overlay';
|
|
32
|
+
/** User-settings key: JSON array of the user's starred (favorited) brand theme ids. */
|
|
33
|
+
const STARRED_THEMES_KEY = 'Explorer.StarredThemes';
|
|
34
|
+
/** User-settings key: the brand theme id the user last applied (restored over org default). */
|
|
35
|
+
const SELECTED_BRAND_KEY = 'Explorer.SelectedBrandTheme';
|
|
23
36
|
/**
|
|
24
37
|
* Built-in light theme definition
|
|
25
38
|
*/
|
|
@@ -60,16 +73,34 @@ const DARK_THEME = {
|
|
|
60
73
|
export class ThemeService {
|
|
61
74
|
_preference$ = new BehaviorSubject('system');
|
|
62
75
|
_appliedTheme$ = new BehaviorSubject('light');
|
|
76
|
+
/**
|
|
77
|
+
* The active org brand overlay id, applied independently of the user's light/dark
|
|
78
|
+
* choice and preserved across Reset()/logout.
|
|
79
|
+
*/
|
|
80
|
+
_brandOverlayId = null;
|
|
81
|
+
/** The user's starred (favorited) brand theme ids, reactive for menus/badges. */
|
|
82
|
+
_starred$ = new BehaviorSubject([]);
|
|
83
|
+
/** Emits when the set of saved themes changes (create/rename/delete/duplicate/default)
|
|
84
|
+
* so open dashboards (Theme Studio, Manage Themes) refresh without a page reload. */
|
|
85
|
+
_themesChanged$ = new Subject();
|
|
63
86
|
_initialized = false;
|
|
64
87
|
systemMediaQuery = null;
|
|
65
88
|
boundSystemThemeHandler = null;
|
|
66
|
-
/** Registry of
|
|
89
|
+
/** Registry of USER-SELECTABLE themes (shown in the theme picker), seeded with light + dark. */
|
|
67
90
|
themeRegistry = new Map([
|
|
68
91
|
['light', LIGHT_THEME],
|
|
69
92
|
['dark', DARK_THEME]
|
|
70
93
|
]);
|
|
94
|
+
/**
|
|
95
|
+
* Registry of ORG BRAND overlays (from RegisterBrandTheme). Kept separate from
|
|
96
|
+
* themeRegistry so brand overlays do NOT appear in the user's light/dark theme
|
|
97
|
+
* picker — the org brand is applied independently of the user's mode (decision #1).
|
|
98
|
+
*/
|
|
99
|
+
brandRegistry = new Map();
|
|
71
100
|
/** Cache of loaded <link> elements by theme ID to avoid re-downloading */
|
|
72
101
|
loadedCssLinks = new Map();
|
|
102
|
+
/** Session-scoped Blob object URLs for brand overlays, keyed by theme ID (for revocation on re-register). */
|
|
103
|
+
brandBlobUrls = new Map();
|
|
73
104
|
/**
|
|
74
105
|
* Observable for user's theme preference (theme ID or 'system')
|
|
75
106
|
*/
|
|
@@ -131,6 +162,125 @@ export class ThemeService {
|
|
|
131
162
|
this.RegisterTheme(theme);
|
|
132
163
|
}
|
|
133
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Register a brand theme from its seeds.
|
|
167
|
+
*
|
|
168
|
+
* Derives the full --mj-* token contract with @memberjunction/theme-engine, emits
|
|
169
|
+
* the `[data-theme-overlay="<id>"]` CSS, and publishes it as a session-scoped Blob
|
|
170
|
+
* URL that plugs into the existing CssUrl overlay path — so brand themes apply
|
|
171
|
+
* through the same machinery as file-based custom themes. Returns the derived
|
|
172
|
+
* theme so callers can surface the a11y contrast report / previews.
|
|
173
|
+
*
|
|
174
|
+
* The emitted overlay overrides only the primitive ramps (plus viz/shadow/font/
|
|
175
|
+
* radius); every semantic token and the `[data-theme="dark"]` block reference those
|
|
176
|
+
* primitives, so both light and dark re-point automatically off one block.
|
|
177
|
+
*
|
|
178
|
+
* @param params.seeds ThemeSeeds object or the entity's Seeds JSON string.
|
|
179
|
+
*/
|
|
180
|
+
RegisterBrandTheme(params) {
|
|
181
|
+
const seeds = typeof params.seeds === 'string' ? JSON.parse(params.seeds) : params.seeds;
|
|
182
|
+
const derived = derive(seeds);
|
|
183
|
+
const overrides = this.parseOverrides(params.overrides);
|
|
184
|
+
// One Blob carries the whole brand: color/shape/font overlay (+ advanced layer) + logo tokens.
|
|
185
|
+
const css = emitOverlayCss(params.id, derived, { overrides, customCss: params.customCss })
|
|
186
|
+
+ (params.logos ? emitLogoOverlayCss(params.id, params.logos) : '');
|
|
187
|
+
// Replace any prior Blob URL + cached <link> for this id so a re-registration
|
|
188
|
+
// (e.g. live preview) loads the new CSS instead of re-enabling stale bytes.
|
|
189
|
+
const priorUrl = this.brandBlobUrls.get(params.id);
|
|
190
|
+
if (priorUrl) {
|
|
191
|
+
URL.revokeObjectURL(priorUrl);
|
|
192
|
+
}
|
|
193
|
+
const cachedLink = this.loadedCssLinks.get(params.id);
|
|
194
|
+
if (cachedLink) {
|
|
195
|
+
cachedLink.remove();
|
|
196
|
+
this.loadedCssLinks.delete(params.id);
|
|
197
|
+
}
|
|
198
|
+
const url = URL.createObjectURL(new Blob([css], { type: 'text/css' }));
|
|
199
|
+
this.brandBlobUrls.set(params.id, url);
|
|
200
|
+
// Register into the brand registry only — NOT themeRegistry — so the org brand
|
|
201
|
+
// does not surface as a user-selectable option in the theme picker.
|
|
202
|
+
this.brandRegistry.set(params.id, {
|
|
203
|
+
Id: params.id,
|
|
204
|
+
Name: params.name,
|
|
205
|
+
BaseTheme: params.baseTheme ?? 'light',
|
|
206
|
+
CssUrl: url,
|
|
207
|
+
Tokens: derived.overlayVars,
|
|
208
|
+
IsBuiltIn: false,
|
|
209
|
+
Description: params.description,
|
|
210
|
+
PreviewColors: [
|
|
211
|
+
derived.tokens.light['--mj-brand-primary'],
|
|
212
|
+
derived.tokens.light['--mj-brand-accent'],
|
|
213
|
+
derived.tokens.light['--mj-brand-tertiary'],
|
|
214
|
+
],
|
|
215
|
+
});
|
|
216
|
+
return derived;
|
|
217
|
+
}
|
|
218
|
+
/** Normalize the advanced overrides input (JSON string, object, or null) to a token map. */
|
|
219
|
+
parseOverrides(input) {
|
|
220
|
+
if (!input)
|
|
221
|
+
return undefined;
|
|
222
|
+
if (typeof input === 'string') {
|
|
223
|
+
try {
|
|
224
|
+
const parsed = JSON.parse(input);
|
|
225
|
+
return parsed && typeof parsed === 'object' ? parsed : undefined;
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return input;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The active org brand overlay id, or null if none is applied.
|
|
235
|
+
*/
|
|
236
|
+
get BrandOverlayId() {
|
|
237
|
+
return this._brandOverlayId;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Apply an org brand overlay **independently of the user's light/dark mode.
|
|
241
|
+
* The overlay layers under whatever base theme is
|
|
242
|
+
* active and is re-asserted whenever the user toggles light/dark. Persists the id
|
|
243
|
+
* to localStorage so the pre-paint script restores the attribute on next load.
|
|
244
|
+
*
|
|
245
|
+
* @param id id of a brand theme registered via {@link RegisterBrandTheme}.
|
|
246
|
+
* @param options.persist pass `false` for session-only overlays backed by Blob URLs
|
|
247
|
+
* (e.g. Theme Studio's draft workspace preview) — a Blob URL doesn't survive a
|
|
248
|
+
* reload, so persisting its id would leave the pre-paint script pointing at a
|
|
249
|
+
* dangling overlay and clobber the user's real persisted selection. Defaults true.
|
|
250
|
+
*/
|
|
251
|
+
async ApplyBrandOverlay(id, options) {
|
|
252
|
+
const def = this.brandRegistry.get(id);
|
|
253
|
+
if (!def || !def.CssUrl) {
|
|
254
|
+
console.warn(`ApplyBrandOverlay: no registered brand theme "${id}" with CSS.`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this._brandOverlayId = id;
|
|
258
|
+
await this.loadThemeCss(def);
|
|
259
|
+
document.documentElement.setAttribute('data-theme-overlay', id);
|
|
260
|
+
if (options?.persist === false) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
window.localStorage.setItem(PRELOAD_OVERLAY_KEY, id);
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
// non-fatal: a brief brand flash on next reload at worst
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Remove the active org brand overlay (revert to the base MJ tokens).
|
|
272
|
+
*/
|
|
273
|
+
ClearBrandOverlay() {
|
|
274
|
+
this._brandOverlayId = null;
|
|
275
|
+
document.documentElement.removeAttribute('data-theme-overlay');
|
|
276
|
+
this.disableAllCustomCss();
|
|
277
|
+
try {
|
|
278
|
+
window.localStorage.removeItem(PRELOAD_OVERLAY_KEY);
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
// non-fatal
|
|
282
|
+
}
|
|
283
|
+
}
|
|
134
284
|
// ========================================
|
|
135
285
|
// LIFECYCLE
|
|
136
286
|
// ========================================
|
|
@@ -143,6 +293,7 @@ export class ThemeService {
|
|
|
143
293
|
return;
|
|
144
294
|
}
|
|
145
295
|
this.setupSystemThemeListener();
|
|
296
|
+
this._starred$.next(this.loadStarredIds());
|
|
146
297
|
const savedPreference = await this.loadSetting();
|
|
147
298
|
this._preference$.next(savedPreference);
|
|
148
299
|
const resolvedThemeId = this.resolveTheme(savedPreference);
|
|
@@ -174,9 +325,11 @@ export class ThemeService {
|
|
|
174
325
|
this._preference$.next('system');
|
|
175
326
|
this._appliedTheme$.next('light');
|
|
176
327
|
this._initialized = false;
|
|
177
|
-
// Remove theme
|
|
328
|
+
// Remove the base-theme attribute only. We deliberately KEEP the org brand
|
|
329
|
+
// overlay (data-theme-overlay + its CSS + _brandOverlayId) so the brand
|
|
330
|
+
// survives logout and stays on the login screen.
|
|
331
|
+
// The overlay is re-emitted at the next login bootstrap.
|
|
178
332
|
document.documentElement.removeAttribute('data-theme');
|
|
179
|
-
document.documentElement.removeAttribute('data-theme-overlay');
|
|
180
333
|
// NOTE: we deliberately do NOT clear PRELOAD_BASE_THEME_KEY here.
|
|
181
334
|
// The unified theme key ('mj-theme') is the single source of truth
|
|
182
335
|
// shared with the login-screen toggle and the inline pre-paint script.
|
|
@@ -184,8 +337,9 @@ export class ThemeService {
|
|
|
184
337
|
// default and cause a theme flash for the post-logout/pre-login
|
|
185
338
|
// window. The auth provider's clearClientCaches() preserves it for
|
|
186
339
|
// exactly this reason (see preservedLocalStorageKeys).
|
|
187
|
-
// Disable
|
|
188
|
-
|
|
340
|
+
// Disable custom CSS links EXCEPT the active org brand overlay, which must
|
|
341
|
+
// survive logout so the brand persists on the login screen.
|
|
342
|
+
this.disableAllCustomCss(this._brandOverlayId);
|
|
189
343
|
}
|
|
190
344
|
// ========================================
|
|
191
345
|
// THEME APPLICATION
|
|
@@ -206,12 +360,27 @@ export class ThemeService {
|
|
|
206
360
|
// Set the base theme attribute (drives existing [data-theme="dark"] selectors)
|
|
207
361
|
this.applyBaseThemeAttribute(themeDef.BaseTheme);
|
|
208
362
|
if (themeDef.IsBuiltIn) {
|
|
209
|
-
// Built-in
|
|
210
|
-
|
|
211
|
-
|
|
363
|
+
// Built-in (light/dark): the user's mode changes, but the org brand overlay
|
|
364
|
+
// stays layered under it. Re-assert the brand overlay if one is active;
|
|
365
|
+
// otherwise clear any overlay.
|
|
366
|
+
if (this._brandOverlayId) {
|
|
367
|
+
const brandDef = this.brandRegistry.get(this._brandOverlayId);
|
|
368
|
+
if (brandDef?.CssUrl) {
|
|
369
|
+
await this.loadThemeCss(brandDef);
|
|
370
|
+
document.documentElement.setAttribute('data-theme-overlay', this._brandOverlayId);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
document.documentElement.removeAttribute('data-theme-overlay');
|
|
374
|
+
this.disableAllCustomCss();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
document.documentElement.removeAttribute('data-theme-overlay');
|
|
379
|
+
this.disableAllCustomCss();
|
|
380
|
+
}
|
|
212
381
|
}
|
|
213
382
|
else if (themeDef.CssUrl) {
|
|
214
|
-
//
|
|
383
|
+
// Explicit custom theme selected — its overlay overrides any brand overlay.
|
|
215
384
|
await this.loadThemeCss(themeDef);
|
|
216
385
|
document.documentElement.setAttribute('data-theme-overlay', themeDef.Id);
|
|
217
386
|
}
|
|
@@ -288,11 +457,15 @@ export class ThemeService {
|
|
|
288
457
|
});
|
|
289
458
|
}
|
|
290
459
|
/**
|
|
291
|
-
* Disable (not remove)
|
|
460
|
+
* Disable (not remove) custom theme CSS <link> elements.
|
|
292
461
|
* Disabling rather than removing avoids re-downloading when switching back.
|
|
462
|
+
* @param keepId optional theme id to leave enabled (e.g. the active brand overlay).
|
|
293
463
|
*/
|
|
294
|
-
disableAllCustomCss() {
|
|
295
|
-
for (const link of this.loadedCssLinks.
|
|
464
|
+
disableAllCustomCss(keepId) {
|
|
465
|
+
for (const [id, link] of this.loadedCssLinks.entries()) {
|
|
466
|
+
if (keepId && id === keepId) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
296
469
|
link.disabled = true;
|
|
297
470
|
}
|
|
298
471
|
}
|
|
@@ -345,6 +518,80 @@ export class ThemeService {
|
|
|
345
518
|
}
|
|
346
519
|
}
|
|
347
520
|
// ========================================
|
|
521
|
+
// STARRED (FAVORITE) BRAND THEMES
|
|
522
|
+
// ========================================
|
|
523
|
+
/** Reactive list of the user's starred brand theme ids. */
|
|
524
|
+
get Starred$() {
|
|
525
|
+
return this._starred$.asObservable();
|
|
526
|
+
}
|
|
527
|
+
/** Emits after any saved-theme mutation so open dashboards can refresh their lists. */
|
|
528
|
+
get ThemesChanged$() {
|
|
529
|
+
return this._themesChanged$.asObservable();
|
|
530
|
+
}
|
|
531
|
+
/** Broadcast that the set of saved themes changed (create/rename/delete/duplicate/default). */
|
|
532
|
+
NotifyThemesChanged() {
|
|
533
|
+
this._themesChanged$.next();
|
|
534
|
+
}
|
|
535
|
+
/** The user's starred brand theme ids (synchronous). */
|
|
536
|
+
GetStarredThemeIds() {
|
|
537
|
+
return this._starred$.value;
|
|
538
|
+
}
|
|
539
|
+
/** Whether a theme id is starred by the user. */
|
|
540
|
+
IsStarred(id) {
|
|
541
|
+
return this._starred$.value.includes(id);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Toggle a theme's starred state and persist to User Settings.
|
|
545
|
+
* @returns the new starred state (true = now starred).
|
|
546
|
+
*/
|
|
547
|
+
async ToggleStar(id) {
|
|
548
|
+
const set = new Set(this._starred$.value);
|
|
549
|
+
const nowStarred = !set.has(id);
|
|
550
|
+
if (nowStarred) {
|
|
551
|
+
set.add(id);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
set.delete(id);
|
|
555
|
+
}
|
|
556
|
+
const ids = Array.from(set);
|
|
557
|
+
this._starred$.next(ids);
|
|
558
|
+
try {
|
|
559
|
+
await UserInfoEngine.Instance.SetSetting(STARRED_THEMES_KEY, JSON.stringify(ids));
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
console.warn('Failed to save starred themes:', error);
|
|
563
|
+
}
|
|
564
|
+
return nowStarred;
|
|
565
|
+
}
|
|
566
|
+
loadStarredIds() {
|
|
567
|
+
try {
|
|
568
|
+
const raw = UserInfoEngine.Instance.GetSetting(STARRED_THEMES_KEY);
|
|
569
|
+
const parsed = raw ? JSON.parse(raw) : [];
|
|
570
|
+
return Array.isArray(parsed) ? parsed.filter((x) => typeof x === 'string') : [];
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/** The brand theme the user last applied (or null to follow the org default). */
|
|
577
|
+
GetSelectedBrandThemeId() {
|
|
578
|
+
try {
|
|
579
|
+
return UserInfoEngine.Instance.GetSetting(SELECTED_BRAND_KEY) || null;
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/** Persist (or clear, with null) the user's chosen brand theme so login restores it. */
|
|
586
|
+
async SetSelectedBrandTheme(id) {
|
|
587
|
+
try {
|
|
588
|
+
await UserInfoEngine.Instance.SetSetting(SELECTED_BRAND_KEY, id ?? '');
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
console.warn('Failed to save selected brand theme:', error);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// ========================================
|
|
348
595
|
// PERSISTENCE
|
|
349
596
|
// ========================================
|
|
350
597
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.service.js","sourceRoot":"","sources":["../../src/lib/theme.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\n\n/**\n * Defines a theme available in the application.\n * Built-in themes (light/dark) have no CssUrl.\n * Custom themes specify a BaseTheme to inherit from and a CssUrl with overrides.\n */\nexport interface ThemeDefinition {\n /** Unique identifier (e.g. 'light', 'dark', 'izzy-dark') */\n Id: string;\n /** Human-readable display name (e.g. 'Light', 'Dark', 'Izzy Dark') */\n Name: string;\n /** Which built-in theme this inherits from */\n BaseTheme: 'light' | 'dark';\n /** URL to the CSS file with token overrides (omit for built-in themes) */\n CssUrl?: string;\n /** Whether this is a built-in theme (light/dark) */\n IsBuiltIn: boolean;\n /** Optional description shown in theme picker */\n Description?: string;\n /** Optional preview swatch colors for a future theme picker UI */\n PreviewColors?: string[];\n}\n\n/**\n * Setting key for theme preference in MJ: User Settings entity\n */\nconst THEME_SETTING_KEY = 'Explorer.Theme';\n\n/**\n * localStorage key the inline theme-preload script reads on first paint\n * (before Angular boots) to apply the correct theme without flashing.\n *\n * The value is the **base theme** ('dark' or 'light'), NOT the full theme\n * preference (which can be 'system' or a custom theme ID like 'izzy-dark').\n * The preload script needs an unambiguous answer: should I set\n * data-theme=\"dark\" or not? Storing the base theme is the simplest\n * contract: the script does `if (value === 'dark') setAttribute(...)`.\n *\n * Mirrored by ThemeService whenever the applied base theme changes,\n * cleared on logout via Reset().\n */\nconst PRELOAD_BASE_THEME_KEY = 'mj-theme';\n\n/**\n * Built-in light theme definition\n */\nconst LIGHT_THEME: ThemeDefinition = {\n Id: 'light',\n Name: 'Light',\n BaseTheme: 'light',\n IsBuiltIn: true,\n Description: 'Default light theme'\n};\n\n/**\n * Built-in dark theme definition\n */\nconst DARK_THEME: ThemeDefinition = {\n Id: 'dark',\n Name: 'Dark',\n BaseTheme: 'dark',\n IsBuiltIn: true,\n Description: 'Default dark theme'\n};\n\n/**\n * Service to manage application themes with pluggable custom theme support.\n *\n * Built-in themes (light/dark) work identically to before. Custom themes\n * inherit from a base theme and overlay additional CSS token overrides via\n * a dynamically loaded stylesheet.\n *\n * CSS resolution for custom themes (e.g. \"Izzy Dark\" extending dark):\n * 1. `:root` light defaults (from _tokens.scss in this package)\n * 2. `[data-theme=\"dark\"]` dark overrides (from _tokens.scss in this package)\n * 3. `[data-theme-overlay=\"izzy-dark\"]` custom overrides (loaded dynamically)\n *\n * Follows the DeveloperModeService pattern:\n * - Settings persisted via UserInfoEngine\n * - BehaviorSubject for reactive state\n * - Initialize after login, Reset on logout\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeService {\n private _preference$ = new BehaviorSubject<string>('system');\n private _appliedTheme$ = new BehaviorSubject<string>('light');\n private _initialized = false;\n private systemMediaQuery: MediaQueryList | null = null;\n private boundSystemThemeHandler: (() => void) | null = null;\n\n /** Registry of available themes, seeded with built-in light and dark */\n private themeRegistry = new Map<string, ThemeDefinition>([\n ['light', LIGHT_THEME],\n ['dark', DARK_THEME]\n ]);\n\n /** Cache of loaded <link> elements by theme ID to avoid re-downloading */\n private loadedCssLinks = new Map<string, HTMLLinkElement>();\n\n /**\n * Observable for user's theme preference (theme ID or 'system')\n */\n public get Preference$(): Observable<string> {\n return this._preference$.asObservable();\n }\n\n /**\n * Observable for the actually applied theme (resolved theme ID)\n */\n public get AppliedTheme$(): Observable<string> {\n return this._appliedTheme$.asObservable();\n }\n\n /**\n * Current theme preference (synchronous access)\n */\n public get Preference(): string {\n return this._preference$.value;\n }\n\n /**\n * Currently applied theme ID (synchronous access)\n */\n public get AppliedTheme(): string {\n return this._appliedTheme$.value;\n }\n\n /**\n * Whether the service has been initialized\n */\n public get IsInitialized(): boolean {\n return this._initialized;\n }\n\n /**\n * All registered themes, for UI consumption (e.g. theme picker menus)\n */\n public get AvailableThemes(): ThemeDefinition[] {\n return Array.from(this.themeRegistry.values());\n }\n\n /**\n * Look up a theme definition by ID.\n * Returns undefined if the theme ID is not registered.\n */\n public GetThemeDefinition(id: string): ThemeDefinition | undefined {\n return this.themeRegistry.get(id);\n }\n\n // ========================================\n // THEME REGISTRATION\n // ========================================\n\n /**\n * Register a custom theme. If a theme with the same ID already exists,\n * it is replaced (allowing override of built-in themes if desired).\n */\n public RegisterTheme(theme: ThemeDefinition): void {\n this.themeRegistry.set(theme.Id, theme);\n }\n\n /**\n * Register multiple custom themes at once.\n */\n public RegisterThemes(themes: ThemeDefinition[]): void {\n for (const theme of themes) {\n this.RegisterTheme(theme);\n }\n }\n\n // ========================================\n // LIFECYCLE\n // ========================================\n\n /**\n * Initialize the theme service.\n * Call after login when UserInfoEngine is available.\n */\n public async Initialize(): Promise<void> {\n if (this._initialized) {\n return;\n }\n\n this.setupSystemThemeListener();\n\n const savedPreference = await this.loadSetting();\n this._preference$.next(savedPreference);\n\n const resolvedThemeId = this.resolveTheme(savedPreference);\n await this.applyTheme(resolvedThemeId);\n\n this._initialized = true;\n }\n\n /**\n * Set the theme preference and apply it.\n * @param preference - A registered theme ID or 'system'\n */\n public async SetTheme(preference: string): Promise<void> {\n if (preference === this._preference$.value) {\n return;\n }\n\n this._preference$.next(preference);\n\n const resolvedThemeId = this.resolveTheme(preference);\n await this.applyTheme(resolvedThemeId);\n\n await this.saveSetting(preference);\n }\n\n /**\n * Reset the service (call on logout)\n */\n public Reset(): void {\n if (this.systemMediaQuery && this.boundSystemThemeHandler) {\n this.systemMediaQuery.removeEventListener('change', this.boundSystemThemeHandler);\n }\n this.systemMediaQuery = null;\n this.boundSystemThemeHandler = null;\n\n this._preference$.next('system');\n this._appliedTheme$.next('light');\n this._initialized = false;\n\n // Remove theme attributes\n document.documentElement.removeAttribute('data-theme');\n document.documentElement.removeAttribute('data-theme-overlay');\n\n // NOTE: we deliberately do NOT clear PRELOAD_BASE_THEME_KEY here.\n // The unified theme key ('mj-theme') is the single source of truth\n // shared with the login-screen toggle and the inline pre-paint script.\n // Clearing it on logout would force the login screen back to OS\n // default and cause a theme flash for the post-logout/pre-login\n // window. The auth provider's clearClientCaches() preserves it for\n // exactly this reason (see preservedLocalStorageKeys).\n\n // Disable all custom CSS links\n this.disableAllCustomCss();\n }\n\n // ========================================\n // THEME APPLICATION\n // ========================================\n\n /**\n * Apply a resolved theme ID to the DOM.\n * Sets `data-theme` based on BaseTheme and `data-theme-overlay` for custom themes.\n * Loads custom CSS if needed, disables previous custom CSS.\n */\n private async applyTheme(themeId: string): Promise<void> {\n const themeDef = this.themeRegistry.get(themeId);\n\n // Fall back to 'light' if the theme ID isn't recognized\n if (!themeDef) {\n this.applyBuiltInTheme('light');\n this._appliedTheme$.next('light');\n return;\n }\n\n // Set the base theme attribute (drives existing [data-theme=\"dark\"] selectors)\n this.applyBaseThemeAttribute(themeDef.BaseTheme);\n\n if (themeDef.IsBuiltIn) {\n // Built-in theme: remove overlay, disable custom CSS\n document.documentElement.removeAttribute('data-theme-overlay');\n this.disableAllCustomCss();\n } else if (themeDef.CssUrl) {\n // Custom theme: load/enable CSS and set overlay attribute\n await this.loadThemeCss(themeDef);\n document.documentElement.setAttribute('data-theme-overlay', themeDef.Id);\n }\n\n this._appliedTheme$.next(themeId);\n }\n\n /**\n * Apply the base theme attribute to <html>.\n * 'dark' sets data-theme=\"dark\"; 'light' removes it (matching existing convention).\n *\n * Also mirrors the base theme to localStorage under PRELOAD_BASE_THEME_KEY\n * so the inline pre-bootstrap theme-preload script in index.html can apply\n * the correct theme on first paint of the next page load — eliminating\n * the brief light-mode flash dark-mode users would otherwise see.\n */\n private applyBaseThemeAttribute(baseTheme: 'light' | 'dark'): void {\n if (baseTheme === 'dark') {\n document.documentElement.setAttribute('data-theme', 'dark');\n } else {\n document.documentElement.removeAttribute('data-theme');\n }\n try {\n window.localStorage.setItem(PRELOAD_BASE_THEME_KEY, baseTheme);\n } catch (e) {\n // localStorage exceptions (private mode, quota) are non-fatal here —\n // the app still works, the user just may see a brief theme flash on\n // their next reload.\n }\n }\n\n /**\n * Shorthand for built-in theme application (no custom CSS)\n */\n private applyBuiltInTheme(baseTheme: 'light' | 'dark'): void {\n this.applyBaseThemeAttribute(baseTheme);\n document.documentElement.removeAttribute('data-theme-overlay');\n this.disableAllCustomCss();\n }\n\n // ========================================\n // DYNAMIC CSS LOADING\n // ========================================\n\n /**\n * Load (or re-enable) a custom theme's CSS file.\n * Injects a <link> element into <head> with a data-mj-theme attribute.\n * Caches the link element to avoid re-downloading on theme switches.\n * Returns a Promise that resolves once the stylesheet is loaded.\n */\n private loadThemeCss(theme: ThemeDefinition): Promise<void> {\n if (!theme.CssUrl) {\n return Promise.resolve();\n }\n\n // Disable all other custom CSS first\n this.disableAllCustomCss();\n\n // Check cache — if already loaded, just re-enable\n const existingLink = this.loadedCssLinks.get(theme.Id);\n if (existingLink) {\n existingLink.disabled = false;\n return Promise.resolve();\n }\n\n // Create and inject new <link> element\n return new Promise<void>((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = theme.CssUrl!;\n link.setAttribute('data-mj-theme', theme.Id);\n\n link.onload = () => resolve();\n link.onerror = () => {\n console.warn(`Failed to load theme CSS: ${theme.CssUrl}`);\n // Still resolve so the theme switch isn't blocked\n resolve();\n };\n\n document.head.appendChild(link);\n this.loadedCssLinks.set(theme.Id, link);\n });\n }\n\n /**\n * Disable (not remove) all custom theme CSS <link> elements.\n * Disabling rather than removing avoids re-downloading when switching back.\n */\n private disableAllCustomCss(): void {\n for (const link of this.loadedCssLinks.values()) {\n link.disabled = true;\n }\n }\n\n // ========================================\n // THEME RESOLUTION\n // ========================================\n\n /**\n * Resolve a preference string to an actual theme ID.\n * 'system' resolves to 'light' or 'dark' based on OS preference.\n * Unrecognized IDs fall back to 'light'.\n */\n private resolveTheme(preference: string): string {\n if (preference === 'system') {\n return this.getSystemTheme();\n }\n\n // If the preference is a registered theme, use it directly\n if (this.themeRegistry.has(preference)) {\n return preference;\n }\n\n // Unrecognized theme ID — fall back to light\n return 'light';\n }\n\n /**\n * Get system theme preference from OS\n */\n private getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') {\n return 'light';\n }\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n /**\n * Setup listener for system theme changes\n */\n private setupSystemThemeListener(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n this.systemMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.boundSystemThemeHandler = () => this.onSystemThemeChange();\n this.systemMediaQuery.addEventListener('change', this.boundSystemThemeHandler);\n }\n\n /**\n * Handle system theme change (only applies if in 'system' mode)\n */\n private async onSystemThemeChange(): Promise<void> {\n if (this._preference$.value === 'system') {\n const newThemeId = this.getSystemTheme();\n await this.applyTheme(newThemeId);\n }\n }\n\n // ========================================\n // PERSISTENCE\n // ========================================\n\n /**\n * Load theme preference from User Settings.\n * Accepts any registered theme ID or 'system'.\n * Falls back to 'system' if saved value is not recognized.\n */\n private async loadSetting(): Promise<string> {\n try {\n const engine = UserInfoEngine.Instance;\n const settingValue = engine.GetSetting(THEME_SETTING_KEY);\n\n if (!settingValue) {\n return 'system';\n }\n\n // Accept 'system' or any registered theme ID\n if (settingValue === 'system' || this.themeRegistry.has(settingValue)) {\n return settingValue;\n }\n\n // Saved theme no longer registered — fall back\n return 'system';\n } catch (error) {\n console.warn('Failed to load theme setting:', error);\n return 'system';\n }\n }\n\n /**\n * Save theme preference to User Settings\n */\n private async saveSetting(preference: string): Promise<void> {\n try {\n const engine = UserInfoEngine.Instance;\n await engine.SetSetting(THEME_SETTING_KEY, preference);\n } catch (error) {\n console.warn('Failed to save theme setting:', error);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"theme.service.js","sourceRoot":"","sources":["../../src/lib/theme.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable, Subject } from 'rxjs';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { derive, DerivedTheme, emitLogoOverlayCss, emitOverlayCss, ThemeLogos, ThemeSeeds } from '@memberjunction/theme-engine';\n\n/**\n * Defines a theme available in the application.\n * Built-in themes (light/dark) have no CssUrl.\n * Custom themes specify a BaseTheme to inherit from and a CssUrl with overrides.\n */\nexport interface ThemeDefinition {\n /** Unique identifier (e.g. 'light', 'dark', 'izzy-dark') */\n Id: string;\n /** Human-readable display name (e.g. 'Light', 'Dark', 'Izzy Dark') */\n Name: string;\n /** Which built-in theme this inherits from */\n BaseTheme: 'light' | 'dark';\n /** URL to the CSS file with token overrides (omit for built-in themes) */\n CssUrl?: string;\n /**\n * Derived --mj-* token overrides. Populated for brand themes emitted\n * from seeds; an alternative/companion to CssUrl for token-based application and\n * for feeding the Theme Builder preview without a round-trip through CSS.\n */\n Tokens?: Record<string, string>;\n /** Whether this is a built-in theme (light/dark) */\n IsBuiltIn: boolean;\n /** Optional description shown in theme picker */\n Description?: string;\n /** Optional preview swatch colors for a future theme picker UI */\n PreviewColors?: string[];\n}\n\n/**\n * Setting key for theme preference in MJ: User Settings entity\n */\nconst THEME_SETTING_KEY = 'Explorer.Theme';\n\n/**\n * localStorage key the inline theme-preload script reads on first paint\n * (before Angular boots) to apply the correct theme without flashing.\n *\n * The value is the **base theme** ('dark' or 'light'), NOT the full theme\n * preference (which can be 'system' or a custom theme ID like 'izzy-dark').\n * The preload script needs an unambiguous answer: should I set\n * data-theme=\"dark\" or not? Storing the base theme is the simplest\n * contract: the script does `if (value === 'dark') setAttribute(...)`.\n *\n * Mirrored by ThemeService whenever the applied base theme changes,\n * cleared on logout via Reset().\n */\nconst PRELOAD_BASE_THEME_KEY = 'mj-theme';\n\n/**\n * localStorage key holding the active org **brand overlay** id, mirrored so the\n * inline pre-paint script can restore `data-theme-overlay` on first paint (the org\n * brand persists across the user's light/dark choice AND across logout). The overlay\n * CSS itself is a session-scoped Blob re-emitted at login bootstrap, so this key only\n * restores the attribute — brand colors repaint once bootstrap re-injects the CSS.\n */\nconst PRELOAD_OVERLAY_KEY = 'mj-theme-overlay';\n\n/** User-settings key: JSON array of the user's starred (favorited) brand theme ids. */\nconst STARRED_THEMES_KEY = 'Explorer.StarredThemes';\n\n/** User-settings key: the brand theme id the user last applied (restored over org default). */\nconst SELECTED_BRAND_KEY = 'Explorer.SelectedBrandTheme';\n\n/**\n * Built-in light theme definition\n */\nconst LIGHT_THEME: ThemeDefinition = {\n Id: 'light',\n Name: 'Light',\n BaseTheme: 'light',\n IsBuiltIn: true,\n Description: 'Default light theme'\n};\n\n/**\n * Built-in dark theme definition\n */\nconst DARK_THEME: ThemeDefinition = {\n Id: 'dark',\n Name: 'Dark',\n BaseTheme: 'dark',\n IsBuiltIn: true,\n Description: 'Default dark theme'\n};\n\n/**\n * Service to manage application themes with pluggable custom theme support.\n *\n * Built-in themes (light/dark) work identically to before. Custom themes\n * inherit from a base theme and overlay additional CSS token overrides via\n * a dynamically loaded stylesheet.\n *\n * CSS resolution for custom themes (e.g. \"Izzy Dark\" extending dark):\n * 1. `:root` light defaults (from _tokens.scss in this package)\n * 2. `[data-theme=\"dark\"]` dark overrides (from _tokens.scss in this package)\n * 3. `[data-theme-overlay=\"izzy-dark\"]` custom overrides (loaded dynamically)\n *\n * Follows the DeveloperModeService pattern:\n * - Settings persisted via UserInfoEngine\n * - BehaviorSubject for reactive state\n * - Initialize after login, Reset on logout\n */\n@Injectable({ providedIn: 'root' })\nexport class ThemeService {\n private _preference$ = new BehaviorSubject<string>('system');\n private _appliedTheme$ = new BehaviorSubject<string>('light');\n /**\n * The active org brand overlay id, applied independently of the user's light/dark\n * choice and preserved across Reset()/logout.\n */\n private _brandOverlayId: string | null = null;\n /** The user's starred (favorited) brand theme ids, reactive for menus/badges. */\n private _starred$ = new BehaviorSubject<string[]>([]);\n /** Emits when the set of saved themes changes (create/rename/delete/duplicate/default)\n * so open dashboards (Theme Studio, Manage Themes) refresh without a page reload. */\n private _themesChanged$ = new Subject<void>();\n private _initialized = false;\n private systemMediaQuery: MediaQueryList | null = null;\n private boundSystemThemeHandler: (() => void) | null = null;\n\n /** Registry of USER-SELECTABLE themes (shown in the theme picker), seeded with light + dark. */\n private themeRegistry = new Map<string, ThemeDefinition>([\n ['light', LIGHT_THEME],\n ['dark', DARK_THEME]\n ]);\n\n /**\n * Registry of ORG BRAND overlays (from RegisterBrandTheme). Kept separate from\n * themeRegistry so brand overlays do NOT appear in the user's light/dark theme\n * picker — the org brand is applied independently of the user's mode (decision #1).\n */\n private brandRegistry = new Map<string, ThemeDefinition>();\n\n /** Cache of loaded <link> elements by theme ID to avoid re-downloading */\n private loadedCssLinks = new Map<string, HTMLLinkElement>();\n\n /** Session-scoped Blob object URLs for brand overlays, keyed by theme ID (for revocation on re-register). */\n private brandBlobUrls = new Map<string, string>();\n\n /**\n * Observable for user's theme preference (theme ID or 'system')\n */\n public get Preference$(): Observable<string> {\n return this._preference$.asObservable();\n }\n\n /**\n * Observable for the actually applied theme (resolved theme ID)\n */\n public get AppliedTheme$(): Observable<string> {\n return this._appliedTheme$.asObservable();\n }\n\n /**\n * Current theme preference (synchronous access)\n */\n public get Preference(): string {\n return this._preference$.value;\n }\n\n /**\n * Currently applied theme ID (synchronous access)\n */\n public get AppliedTheme(): string {\n return this._appliedTheme$.value;\n }\n\n /**\n * Whether the service has been initialized\n */\n public get IsInitialized(): boolean {\n return this._initialized;\n }\n\n /**\n * All registered themes, for UI consumption (e.g. theme picker menus)\n */\n public get AvailableThemes(): ThemeDefinition[] {\n return Array.from(this.themeRegistry.values());\n }\n\n /**\n * Look up a theme definition by ID.\n * Returns undefined if the theme ID is not registered.\n */\n public GetThemeDefinition(id: string): ThemeDefinition | undefined {\n return this.themeRegistry.get(id);\n }\n\n // ========================================\n // THEME REGISTRATION\n // ========================================\n\n /**\n * Register a custom theme. If a theme with the same ID already exists,\n * it is replaced (allowing override of built-in themes if desired).\n */\n public RegisterTheme(theme: ThemeDefinition): void {\n this.themeRegistry.set(theme.Id, theme);\n }\n\n /**\n * Register multiple custom themes at once.\n */\n public RegisterThemes(themes: ThemeDefinition[]): void {\n for (const theme of themes) {\n this.RegisterTheme(theme);\n }\n }\n\n /**\n * Register a brand theme from its seeds.\n *\n * Derives the full --mj-* token contract with @memberjunction/theme-engine, emits\n * the `[data-theme-overlay=\"<id>\"]` CSS, and publishes it as a session-scoped Blob\n * URL that plugs into the existing CssUrl overlay path — so brand themes apply\n * through the same machinery as file-based custom themes. Returns the derived\n * theme so callers can surface the a11y contrast report / previews.\n *\n * The emitted overlay overrides only the primitive ramps (plus viz/shadow/font/\n * radius); every semantic token and the `[data-theme=\"dark\"]` block reference those\n * primitives, so both light and dark re-point automatically off one block.\n *\n * @param params.seeds ThemeSeeds object or the entity's Seeds JSON string.\n */\n public RegisterBrandTheme(params: {\n id: string;\n name: string;\n seeds: ThemeSeeds | string;\n description?: string;\n baseTheme?: 'light' | 'dark';\n /** Optional logo variant URLs + geometry, emitted as --mj-logo-*. */\n logos?: ThemeLogos;\n /** Advanced: per-token overrides (JSON map or object) merged over the derived vars. */\n overrides?: Record<string, string> | string | null;\n /** Advanced: raw CSS appended to the overlay, auto-scoped to this theme. */\n customCss?: string | null;\n }): DerivedTheme {\n const seeds = typeof params.seeds === 'string' ? (JSON.parse(params.seeds) as ThemeSeeds) : params.seeds;\n const derived = derive(seeds);\n const overrides = this.parseOverrides(params.overrides);\n // One Blob carries the whole brand: color/shape/font overlay (+ advanced layer) + logo tokens.\n const css = emitOverlayCss(params.id, derived, { overrides, customCss: params.customCss })\n + (params.logos ? emitLogoOverlayCss(params.id, params.logos) : '');\n\n // Replace any prior Blob URL + cached <link> for this id so a re-registration\n // (e.g. live preview) loads the new CSS instead of re-enabling stale bytes.\n const priorUrl = this.brandBlobUrls.get(params.id);\n if (priorUrl) {\n URL.revokeObjectURL(priorUrl);\n }\n const cachedLink = this.loadedCssLinks.get(params.id);\n if (cachedLink) {\n cachedLink.remove();\n this.loadedCssLinks.delete(params.id);\n }\n\n const url = URL.createObjectURL(new Blob([css], { type: 'text/css' }));\n this.brandBlobUrls.set(params.id, url);\n\n // Register into the brand registry only — NOT themeRegistry — so the org brand\n // does not surface as a user-selectable option in the theme picker.\n this.brandRegistry.set(params.id, {\n Id: params.id,\n Name: params.name,\n BaseTheme: params.baseTheme ?? 'light',\n CssUrl: url,\n Tokens: derived.overlayVars,\n IsBuiltIn: false,\n Description: params.description,\n PreviewColors: [\n derived.tokens.light['--mj-brand-primary'],\n derived.tokens.light['--mj-brand-accent'],\n derived.tokens.light['--mj-brand-tertiary'],\n ],\n });\n\n return derived;\n }\n\n /** Normalize the advanced overrides input (JSON string, object, or null) to a token map. */\n private parseOverrides(input?: Record<string, string> | string | null): Record<string, string> | undefined {\n if (!input) return undefined;\n if (typeof input === 'string') {\n try {\n const parsed = JSON.parse(input);\n return parsed && typeof parsed === 'object' ? (parsed as Record<string, string>) : undefined;\n } catch {\n return undefined;\n }\n }\n return input;\n }\n\n /**\n * The active org brand overlay id, or null if none is applied.\n */\n public get BrandOverlayId(): string | null {\n return this._brandOverlayId;\n }\n\n /**\n * Apply an org brand overlay **independently of the user's light/dark mode.\n * The overlay layers under whatever base theme is\n * active and is re-asserted whenever the user toggles light/dark. Persists the id\n * to localStorage so the pre-paint script restores the attribute on next load.\n *\n * @param id id of a brand theme registered via {@link RegisterBrandTheme}.\n * @param options.persist pass `false` for session-only overlays backed by Blob URLs\n * (e.g. Theme Studio's draft workspace preview) — a Blob URL doesn't survive a\n * reload, so persisting its id would leave the pre-paint script pointing at a\n * dangling overlay and clobber the user's real persisted selection. Defaults true.\n */\n public async ApplyBrandOverlay(id: string, options?: { persist?: boolean }): Promise<void> {\n const def = this.brandRegistry.get(id);\n if (!def || !def.CssUrl) {\n console.warn(`ApplyBrandOverlay: no registered brand theme \"${id}\" with CSS.`);\n return;\n }\n this._brandOverlayId = id;\n await this.loadThemeCss(def);\n document.documentElement.setAttribute('data-theme-overlay', id);\n if (options?.persist === false) {\n return;\n }\n try {\n window.localStorage.setItem(PRELOAD_OVERLAY_KEY, id);\n } catch (e) {\n // non-fatal: a brief brand flash on next reload at worst\n }\n }\n\n /**\n * Remove the active org brand overlay (revert to the base MJ tokens).\n */\n public ClearBrandOverlay(): void {\n this._brandOverlayId = null;\n document.documentElement.removeAttribute('data-theme-overlay');\n this.disableAllCustomCss();\n try {\n window.localStorage.removeItem(PRELOAD_OVERLAY_KEY);\n } catch (e) {\n // non-fatal\n }\n }\n\n // ========================================\n // LIFECYCLE\n // ========================================\n\n /**\n * Initialize the theme service.\n * Call after login when UserInfoEngine is available.\n */\n public async Initialize(): Promise<void> {\n if (this._initialized) {\n return;\n }\n\n this.setupSystemThemeListener();\n\n this._starred$.next(this.loadStarredIds());\n\n const savedPreference = await this.loadSetting();\n this._preference$.next(savedPreference);\n\n const resolvedThemeId = this.resolveTheme(savedPreference);\n await this.applyTheme(resolvedThemeId);\n\n this._initialized = true;\n }\n\n /**\n * Set the theme preference and apply it.\n * @param preference - A registered theme ID or 'system'\n */\n public async SetTheme(preference: string): Promise<void> {\n if (preference === this._preference$.value) {\n return;\n }\n\n this._preference$.next(preference);\n\n const resolvedThemeId = this.resolveTheme(preference);\n await this.applyTheme(resolvedThemeId);\n\n await this.saveSetting(preference);\n }\n\n /**\n * Reset the service (call on logout)\n */\n public Reset(): void {\n if (this.systemMediaQuery && this.boundSystemThemeHandler) {\n this.systemMediaQuery.removeEventListener('change', this.boundSystemThemeHandler);\n }\n this.systemMediaQuery = null;\n this.boundSystemThemeHandler = null;\n\n this._preference$.next('system');\n this._appliedTheme$.next('light');\n this._initialized = false;\n\n // Remove the base-theme attribute only. We deliberately KEEP the org brand\n // overlay (data-theme-overlay + its CSS + _brandOverlayId) so the brand\n // survives logout and stays on the login screen.\n // The overlay is re-emitted at the next login bootstrap.\n document.documentElement.removeAttribute('data-theme');\n\n // NOTE: we deliberately do NOT clear PRELOAD_BASE_THEME_KEY here.\n // The unified theme key ('mj-theme') is the single source of truth\n // shared with the login-screen toggle and the inline pre-paint script.\n // Clearing it on logout would force the login screen back to OS\n // default and cause a theme flash for the post-logout/pre-login\n // window. The auth provider's clearClientCaches() preserves it for\n // exactly this reason (see preservedLocalStorageKeys).\n\n // Disable custom CSS links EXCEPT the active org brand overlay, which must\n // survive logout so the brand persists on the login screen.\n this.disableAllCustomCss(this._brandOverlayId);\n }\n\n // ========================================\n // THEME APPLICATION\n // ========================================\n\n /**\n * Apply a resolved theme ID to the DOM.\n * Sets `data-theme` based on BaseTheme and `data-theme-overlay` for custom themes.\n * Loads custom CSS if needed, disables previous custom CSS.\n */\n private async applyTheme(themeId: string): Promise<void> {\n const themeDef = this.themeRegistry.get(themeId);\n\n // Fall back to 'light' if the theme ID isn't recognized\n if (!themeDef) {\n this.applyBuiltInTheme('light');\n this._appliedTheme$.next('light');\n return;\n }\n\n // Set the base theme attribute (drives existing [data-theme=\"dark\"] selectors)\n this.applyBaseThemeAttribute(themeDef.BaseTheme);\n\n if (themeDef.IsBuiltIn) {\n // Built-in (light/dark): the user's mode changes, but the org brand overlay\n // stays layered under it. Re-assert the brand overlay if one is active;\n // otherwise clear any overlay.\n if (this._brandOverlayId) {\n const brandDef = this.brandRegistry.get(this._brandOverlayId);\n if (brandDef?.CssUrl) {\n await this.loadThemeCss(brandDef);\n document.documentElement.setAttribute('data-theme-overlay', this._brandOverlayId);\n } else {\n document.documentElement.removeAttribute('data-theme-overlay');\n this.disableAllCustomCss();\n }\n } else {\n document.documentElement.removeAttribute('data-theme-overlay');\n this.disableAllCustomCss();\n }\n } else if (themeDef.CssUrl) {\n // Explicit custom theme selected — its overlay overrides any brand overlay.\n await this.loadThemeCss(themeDef);\n document.documentElement.setAttribute('data-theme-overlay', themeDef.Id);\n }\n\n this._appliedTheme$.next(themeId);\n }\n\n /**\n * Apply the base theme attribute to <html>.\n * 'dark' sets data-theme=\"dark\"; 'light' removes it (matching existing convention).\n *\n * Also mirrors the base theme to localStorage under PRELOAD_BASE_THEME_KEY\n * so the inline pre-bootstrap theme-preload script in index.html can apply\n * the correct theme on first paint of the next page load — eliminating\n * the brief light-mode flash dark-mode users would otherwise see.\n */\n private applyBaseThemeAttribute(baseTheme: 'light' | 'dark'): void {\n if (baseTheme === 'dark') {\n document.documentElement.setAttribute('data-theme', 'dark');\n } else {\n document.documentElement.removeAttribute('data-theme');\n }\n try {\n window.localStorage.setItem(PRELOAD_BASE_THEME_KEY, baseTheme);\n } catch (e) {\n // localStorage exceptions (private mode, quota) are non-fatal here —\n // the app still works, the user just may see a brief theme flash on\n // their next reload.\n }\n }\n\n /**\n * Shorthand for built-in theme application (no custom CSS)\n */\n private applyBuiltInTheme(baseTheme: 'light' | 'dark'): void {\n this.applyBaseThemeAttribute(baseTheme);\n document.documentElement.removeAttribute('data-theme-overlay');\n this.disableAllCustomCss();\n }\n\n // ========================================\n // DYNAMIC CSS LOADING\n // ========================================\n\n /**\n * Load (or re-enable) a custom theme's CSS file.\n * Injects a <link> element into <head> with a data-mj-theme attribute.\n * Caches the link element to avoid re-downloading on theme switches.\n * Returns a Promise that resolves once the stylesheet is loaded.\n */\n private loadThemeCss(theme: ThemeDefinition): Promise<void> {\n if (!theme.CssUrl) {\n return Promise.resolve();\n }\n\n // Disable all other custom CSS first\n this.disableAllCustomCss();\n\n // Check cache — if already loaded, just re-enable\n const existingLink = this.loadedCssLinks.get(theme.Id);\n if (existingLink) {\n existingLink.disabled = false;\n return Promise.resolve();\n }\n\n // Create and inject new <link> element\n return new Promise<void>((resolve, reject) => {\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = theme.CssUrl!;\n link.setAttribute('data-mj-theme', theme.Id);\n\n link.onload = () => resolve();\n link.onerror = () => {\n console.warn(`Failed to load theme CSS: ${theme.CssUrl}`);\n // Still resolve so the theme switch isn't blocked\n resolve();\n };\n\n document.head.appendChild(link);\n this.loadedCssLinks.set(theme.Id, link);\n });\n }\n\n /**\n * Disable (not remove) custom theme CSS <link> elements.\n * Disabling rather than removing avoids re-downloading when switching back.\n * @param keepId optional theme id to leave enabled (e.g. the active brand overlay).\n */\n private disableAllCustomCss(keepId?: string | null): void {\n for (const [id, link] of this.loadedCssLinks.entries()) {\n if (keepId && id === keepId) {\n continue;\n }\n link.disabled = true;\n }\n }\n\n // ========================================\n // THEME RESOLUTION\n // ========================================\n\n /**\n * Resolve a preference string to an actual theme ID.\n * 'system' resolves to 'light' or 'dark' based on OS preference.\n * Unrecognized IDs fall back to 'light'.\n */\n private resolveTheme(preference: string): string {\n if (preference === 'system') {\n return this.getSystemTheme();\n }\n\n // If the preference is a registered theme, use it directly\n if (this.themeRegistry.has(preference)) {\n return preference;\n }\n\n // Unrecognized theme ID — fall back to light\n return 'light';\n }\n\n /**\n * Get system theme preference from OS\n */\n private getSystemTheme(): 'light' | 'dark' {\n if (typeof window === 'undefined') {\n return 'light';\n }\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n }\n\n /**\n * Setup listener for system theme changes\n */\n private setupSystemThemeListener(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n this.systemMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n this.boundSystemThemeHandler = () => this.onSystemThemeChange();\n this.systemMediaQuery.addEventListener('change', this.boundSystemThemeHandler);\n }\n\n /**\n * Handle system theme change (only applies if in 'system' mode)\n */\n private async onSystemThemeChange(): Promise<void> {\n if (this._preference$.value === 'system') {\n const newThemeId = this.getSystemTheme();\n await this.applyTheme(newThemeId);\n }\n }\n\n // ========================================\n // STARRED (FAVORITE) BRAND THEMES\n // ========================================\n\n /** Reactive list of the user's starred brand theme ids. */\n public get Starred$(): Observable<string[]> {\n return this._starred$.asObservable();\n }\n\n /** Emits after any saved-theme mutation so open dashboards can refresh their lists. */\n public get ThemesChanged$(): Observable<void> {\n return this._themesChanged$.asObservable();\n }\n\n /** Broadcast that the set of saved themes changed (create/rename/delete/duplicate/default). */\n public NotifyThemesChanged(): void {\n this._themesChanged$.next();\n }\n\n /** The user's starred brand theme ids (synchronous). */\n public GetStarredThemeIds(): string[] {\n return this._starred$.value;\n }\n\n /** Whether a theme id is starred by the user. */\n public IsStarred(id: string): boolean {\n return this._starred$.value.includes(id);\n }\n\n /**\n * Toggle a theme's starred state and persist to User Settings.\n * @returns the new starred state (true = now starred).\n */\n public async ToggleStar(id: string): Promise<boolean> {\n const set = new Set(this._starred$.value);\n const nowStarred = !set.has(id);\n if (nowStarred) {\n set.add(id);\n } else {\n set.delete(id);\n }\n const ids = Array.from(set);\n this._starred$.next(ids);\n try {\n await UserInfoEngine.Instance.SetSetting(STARRED_THEMES_KEY, JSON.stringify(ids));\n } catch (error) {\n console.warn('Failed to save starred themes:', error);\n }\n return nowStarred;\n }\n\n private loadStarredIds(): string[] {\n try {\n const raw = UserInfoEngine.Instance.GetSetting(STARRED_THEMES_KEY);\n const parsed = raw ? JSON.parse(raw) : [];\n return Array.isArray(parsed) ? parsed.filter((x) => typeof x === 'string') : [];\n } catch {\n return [];\n }\n }\n\n /** The brand theme the user last applied (or null to follow the org default). */\n public GetSelectedBrandThemeId(): string | null {\n try {\n return UserInfoEngine.Instance.GetSetting(SELECTED_BRAND_KEY) || null;\n } catch {\n return null;\n }\n }\n\n /** Persist (or clear, with null) the user's chosen brand theme so login restores it. */\n public async SetSelectedBrandTheme(id: string | null): Promise<void> {\n try {\n await UserInfoEngine.Instance.SetSetting(SELECTED_BRAND_KEY, id ?? '');\n } catch (error) {\n console.warn('Failed to save selected brand theme:', error);\n }\n }\n\n // ========================================\n // PERSISTENCE\n // ========================================\n\n /**\n * Load theme preference from User Settings.\n * Accepts any registered theme ID or 'system'.\n * Falls back to 'system' if saved value is not recognized.\n */\n private async loadSetting(): Promise<string> {\n try {\n const engine = UserInfoEngine.Instance;\n const settingValue = engine.GetSetting(THEME_SETTING_KEY);\n\n if (!settingValue) {\n return 'system';\n }\n\n // Accept 'system' or any registered theme ID\n if (settingValue === 'system' || this.themeRegistry.has(settingValue)) {\n return settingValue;\n }\n\n // Saved theme no longer registered — fall back\n return 'system';\n } catch (error) {\n console.warn('Failed to load theme setting:', error);\n return 'system';\n }\n }\n\n /**\n * Save theme preference to User Settings\n */\n private async saveSetting(preference: string): Promise<void> {\n try {\n const engine = UserInfoEngine.Instance;\n await engine.SetSetting(THEME_SETTING_KEY, preference);\n } catch (error) {\n console.warn('Failed to save theme setting:', error);\n }\n }\n}\n"]}
|
package/dist/public-api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './lib/module';
|
|
2
2
|
export * from './lib/recent-access.service';
|
|
3
3
|
export * from './lib/theme.service';
|
|
4
|
+
export * from './lib/ag-grid-theme-params';
|
|
4
5
|
export * from './lib/loading/loading.component';
|
|
5
6
|
export * from './lib/rich-text/rich-text-sanitize';
|
|
6
7
|
export * from './lib/rich-text/safe-rich-html.pipe';
|
package/dist/public-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,cAAc,CAAC;AAG7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAGpC,cAAc,iCAAiC,CAAC;AAGhD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAKA,cAAc,cAAc,CAAC;AAG7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAGpC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,iCAAiC,CAAC;AAGhD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC"}
|
package/dist/public-api.js
CHANGED
|
@@ -6,6 +6,8 @@ export * from './lib/module';
|
|
|
6
6
|
// Services
|
|
7
7
|
export * from './lib/recent-access.service';
|
|
8
8
|
export * from './lib/theme.service';
|
|
9
|
+
// Theming constants
|
|
10
|
+
export * from './lib/ag-grid-theme-params';
|
|
9
11
|
// Components
|
|
10
12
|
export * from './lib/loading/loading.component';
|
|
11
13
|
// Rich-text rendering (safe HTML/SVG sanitization)
|
package/dist/public-api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,cAAc,cAAc,CAAC;AAE7B,WAAW;AACX,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAEpC,aAAa;AACb,cAAc,iCAAiC,CAAC;AAEhD,mDAAmD;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC","sourcesContent":["/*\n * Public API Surface for @memberjunction/ng-shared-generic\n */\n\n// Module\nexport * from './lib/module';\n\n// Services\nexport * from './lib/recent-access.service';\nexport * from './lib/theme.service';\n\n// Components\nexport * from './lib/loading/loading.component';\n\n// Rich-text rendering (safe HTML/SVG sanitization)\nexport * from './lib/rich-text/rich-text-sanitize';\nexport * from './lib/rich-text/safe-rich-html.pipe';\n"]}
|
|
1
|
+
{"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,SAAS;AACT,cAAc,cAAc,CAAC;AAE7B,WAAW;AACX,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAEpC,oBAAoB;AACpB,cAAc,4BAA4B,CAAC;AAE3C,aAAa;AACb,cAAc,iCAAiC,CAAC;AAEhD,mDAAmD;AACnD,cAAc,oCAAoC,CAAC;AACnD,cAAc,qCAAqC,CAAC","sourcesContent":["/*\n * Public API Surface for @memberjunction/ng-shared-generic\n */\n\n// Module\nexport * from './lib/module';\n\n// Services\nexport * from './lib/recent-access.service';\nexport * from './lib/theme.service';\n\n// Theming constants\nexport * from './lib/ag-grid-theme-params';\n\n// Components\nexport * from './lib/loading/loading.component';\n\n// Rich-text rendering (safe HTML/SVG sanitization)\nexport * from './lib/rich-text/rich-text-sanitize';\nexport * from './lib/rich-text/safe-rich-html.pipe';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-shared-generic",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.49.0",
|
|
4
4
|
"description": "MemberJunction: Generic Angular Shared Package - utility services and reusable elements used in any Angular application.",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@angular/compiler": "21.1.3",
|
|
25
25
|
"@angular/compiler-cli": "21.1.3",
|
|
26
|
-
"@memberjunction/ng-test-utils": "5.
|
|
26
|
+
"@memberjunction/ng-test-utils": "5.49.0",
|
|
27
27
|
"vitest": "^4.0.18"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
@@ -33,10 +33,11 @@
|
|
|
33
33
|
"rxjs": "^7.8.2"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@memberjunction/core": "5.
|
|
37
|
-
"@memberjunction/ng-base-types": "5.
|
|
38
|
-
"@memberjunction/core-entities": "5.
|
|
39
|
-
"@memberjunction/global": "5.
|
|
36
|
+
"@memberjunction/core": "5.49.0",
|
|
37
|
+
"@memberjunction/ng-base-types": "5.49.0",
|
|
38
|
+
"@memberjunction/core-entities": "5.49.0",
|
|
39
|
+
"@memberjunction/global": "5.49.0",
|
|
40
|
+
"@memberjunction/theme-engine": "5.49.0",
|
|
40
41
|
"dompurify": "^3.3.0",
|
|
41
42
|
"tslib": "^2.8.1"
|
|
42
43
|
},
|