ngx-theme-stack 1.0.1 → 2.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.
@@ -11,9 +11,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.ngAdd = ngAdd;
13
13
  const schematics_1 = require("@angular-devkit/schematics");
14
+ const anti_flash_1 = require("./anti-flash");
15
+ const app_config_1 = require("./app-config");
14
16
  const constants_1 = require("./constants");
15
17
  const utils_1 = require("./utils");
16
- const app_config_1 = require("./app-config");
17
18
  /**
18
19
  * Interactively prompts the user for custom configuration options using readline.
19
20
  * It allows defining additional themes, selecting a default theme from the expanded list,
@@ -34,13 +35,19 @@ function collectCustomOptions() {
34
35
  .filter(Boolean)
35
36
  : [];
36
37
  const allThemes = [...constants_1.DEFAULT_THEMES, ...customThemes];
37
- const theme = yield (0, utils_1.askList)(rl, 'Default theme:', allThemes, 0);
38
+ const defaultTheme = yield (0, utils_1.askList)(rl, 'Default theme:', allThemes, 0);
38
39
  const rawKey = yield (0, utils_1.ask)(rl, ` localStorage key [${constants_1.DEFAULTS.storageKey}]: `);
39
40
  const storageKey = rawKey || constants_1.DEFAULTS.storageKey;
40
41
  const MODES = ['class', 'attribute', 'both'];
41
42
  const mode = yield (0, utils_1.askList)(rl, 'How to apply theme on <html>:', MODES, 0);
43
+ const STRATEGIES = ['critters', 'blocking'];
42
44
  process.stdout.write('\n');
43
- return { theme, storageKey, mode, themes: allThemes };
45
+ process.stdout.write(' Anti-flash strategy:\n');
46
+ process.stdout.write(' - critters: Zero network requests (inlines CSS in <head>)\n');
47
+ process.stdout.write(' - blocking: Standard CSS loading (themes.css)\n');
48
+ const strategy = (yield (0, utils_1.askList)(rl, 'Choose strategy:', STRATEGIES, 0));
49
+ process.stdout.write('\n');
50
+ return { defaultTheme, storageKey, mode, themes: allThemes, strategy };
44
51
  }
45
52
  finally {
46
53
  rl.close();
@@ -56,31 +63,124 @@ function collectCustomOptions() {
56
63
  * @returns A rule that modifies the project's setup.
57
64
  */
58
65
  function ngAdd(options) {
59
- return (_tree, context) => __awaiter(this, void 0, void 0, function* () {
66
+ return (tree, context) => __awaiter(this, void 0, void 0, function* () {
67
+ // ── Workspace Resolution ──────────────────────────────────────────────────
68
+ // In a monorepo, we must find the project's actual root and sourceRoot.
69
+ const workspaceConfig = tree.read('/angular.json');
70
+ if (!workspaceConfig) {
71
+ throw new Error('Could not find angular.json. Are you in an Angular workspace?');
72
+ }
73
+ const workspace = JSON.parse(workspaceConfig.toString());
74
+ const projectName = options.project || workspace.defaultProject;
75
+ const project = workspace.projects[projectName];
76
+ if (!project) {
77
+ throw new Error(`Project "${projectName}" not found in angular.json.`);
78
+ }
79
+ const projectRoot = project.root || '';
80
+ const projectSourceRoot = project.sourceRoot || `${projectRoot}/src`;
60
81
  context.logger.info('');
61
- context.logger.info('🎨 ngx-theme-stack — setup');
82
+ context.logger.info(`🎨 ngx-theme-stack — setup [project: ${projectName}]`);
62
83
  context.logger.info('');
63
84
  let provideCall;
85
+ let scriptOptions;
86
+ let finalThemes;
87
+ let themesToScaffold;
88
+ let finalStrategy;
64
89
  if (options.mode === 'quick') {
65
- provideCall = 'provideThemeStack()';
66
- context.logger.info('⚡ Quick setup defaults applied by the library (DEFAULT_NG_CONFIG).');
90
+ const themes = [...constants_1.DEFAULT_THEMES];
91
+ const { defaultTheme, storageKey, mode } = constants_1.DEFAULTS;
92
+ const strategy = 'critters'; // default for quick
93
+ provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes);
94
+ scriptOptions = { storageKey, defaultTheme, mode };
95
+ finalThemes = themes;
96
+ themesToScaffold = themes.filter((t) => t !== 'system');
97
+ finalStrategy = strategy;
98
+ context.logger.info('⚡ Quick setup — providing explicit defaults (strategy: critters).');
67
99
  }
68
100
  else {
69
101
  context.logger.info('🛠 Custom setup — answer the prompts below:');
70
102
  const opts = yield collectCustomOptions();
71
- const { theme, storageKey, mode, themes } = opts;
103
+ const { defaultTheme, storageKey, mode, themes, strategy } = opts;
72
104
  context.logger.info(' Applying your configuration:');
73
- context.logger.info(` theme : ${theme}`);
74
- context.logger.info(` themes : [${themes.join(', ')}]`);
75
- context.logger.info(` storageKey : ${storageKey}`);
76
- context.logger.info(` mode : ${mode}`);
77
- provideCall = (0, utils_1.buildProvideCall)(theme, storageKey, mode, themes);
105
+ context.logger.info(` defaultTheme : ${defaultTheme}`);
106
+ context.logger.info(` themes : [${themes.join(', ')}]`);
107
+ context.logger.info(` storageKey : ${storageKey}`);
108
+ context.logger.info(` mode : ${mode}`);
109
+ context.logger.info(` strategy : ${strategy}`);
110
+ provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes);
111
+ scriptOptions = { storageKey, defaultTheme, mode };
112
+ finalThemes = themes;
113
+ themesToScaffold = themes.filter((t) => t !== 'system');
114
+ finalStrategy = strategy;
78
115
  }
79
116
  return (0, schematics_1.chain)([
117
+ (t, context) => {
118
+ const themesPath = `${projectSourceRoot}/themes.css`;
119
+ if (!t.exists(themesPath)) {
120
+ let content = '/* ngx-theme-stack tokens */\n\n';
121
+ themesToScaffold.forEach((theme) => {
122
+ const selector = scriptOptions.mode === 'attribute' ? `[data-theme="${theme}"]` : `.${theme}`;
123
+ if (theme === 'light') {
124
+ content += `:root, ${selector} {\n /* Add your light theme variables here */\n}\n\n`;
125
+ }
126
+ else {
127
+ content += `${selector} {\n /* Add your ${theme} theme variables here */\n}\n\n`;
128
+ }
129
+ });
130
+ t.create(themesPath, content);
131
+ context.logger.info(`\n \u001b[36mResume :\u001b[0m \n`);
132
+ context.logger.info(`\u001b[32m✔ Created ${themesPath} with your theme selectors.\u001b[0m`);
133
+ }
134
+ else {
135
+ context.logger.info(`\n \u001b[36mResume :\u001b[0m \n`);
136
+ context.logger.info(`\u001b[33mℹ ${themesPath} already exists. Skipping creation to preserve your styles.\u001b[0m`);
137
+ context.logger.info(` Tip: Make sure to manually add selectors (class or attribute) for any new themes.`);
138
+ }
139
+ },
140
+ (t) => {
141
+ const pkgPath = '/package.json';
142
+ const buffer = t.read(pkgPath);
143
+ if (buffer) {
144
+ const pkg = JSON.parse(buffer.toString());
145
+ pkg.scripts = pkg.scripts || {};
146
+ pkg.scripts.prebuild = `ng generate ngx-theme-stack:sync --project ${projectName}`;
147
+ t.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
148
+ }
149
+ },
150
+ (t, context) => {
151
+ var _a;
152
+ const workspaceConfig = t.read('/angular.json');
153
+ if (workspaceConfig) {
154
+ const workspace = JSON.parse(workspaceConfig.toString());
155
+ const project = workspace.projects[projectName];
156
+ const target = project.architect.build.options;
157
+ const themesPath = `${projectSourceRoot}/themes.css`.replace(/^\//, '');
158
+ // Add themes.css to styles if not already there
159
+ if (target.styles && !target.styles.includes(themesPath)) {
160
+ target.styles.unshift(themesPath);
161
+ }
162
+ // Handle inlineCritical optimization for the blocking strategy
163
+ const prodConfig = (_a = project.architect.build.configurations) === null || _a === void 0 ? void 0 : _a.production;
164
+ if (prodConfig && finalStrategy === 'blocking') {
165
+ if (typeof prodConfig.optimization === 'object') {
166
+ prodConfig.optimization.styles = prodConfig.optimization.styles || {};
167
+ prodConfig.optimization.styles.inlineCritical = false;
168
+ }
169
+ else {
170
+ prodConfig.optimization = {
171
+ styles: { inlineCritical: false },
172
+ };
173
+ }
174
+ context.logger.info(`✔ Disabled inlineCritical in angular.json for blocking strategy.`);
175
+ }
176
+ t.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
177
+ }
178
+ },
80
179
  (t, ctx) => {
81
- (0, app_config_1.patchAppConfig)(t, ctx, provideCall);
180
+ (0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, provideCall);
181
+ (0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, Object.assign(Object.assign({}, scriptOptions), { themes: finalThemes, strategy: options.strategy || finalStrategy }));
82
182
  ctx.logger.info('');
83
- ctx.logger.info('✅ Done! Run `ng serve` to see ngx-theme-stack in action.');
183
+ ctx.logger.info('✅ Done! ngx-theme-stack is ready with automatic sync on build.');
84
184
  ctx.logger.info('');
85
185
  },
86
186
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
@@ -3,4 +3,6 @@ export interface Schema {
3
3
  project: string;
4
4
  /** 'quick' applies defaults silently. 'custom' prompts interactively. */
5
5
  mode: 'quick' | 'custom';
6
+ /** The strategy to prevent theme flicker. */
7
+ strategy: 'critters' | 'blocking';
6
8
  }
@@ -22,14 +22,19 @@
22
22
  "items": [
23
23
  {
24
24
  "value": "quick",
25
- "label": "Quick – apply defaults instantly (theme: system, mode: class)"
25
+ "label": "Quick – apply defaults instantly (theme: system, mode: class, strategy: critters)"
26
26
  },
27
27
  {
28
28
  "value": "custom",
29
- "label": "Custom – choose themes, default theme, storage key and apply mode"
29
+ "label": "Custom – choose themes, default theme, storage key and strategy"
30
30
  }
31
31
  ]
32
32
  }
33
+ },
34
+ "strategy": {
35
+ "type": "string",
36
+ "description": "The strategy to use for flash prevention (critters or blocking).",
37
+ "enum": ["blocking", "critters"]
33
38
  }
34
39
  },
35
40
  "required": ["project"]
@@ -12,9 +12,4 @@ export declare function ask(rl: readline.Interface, question: string): Promise<s
12
12
  * If the input is invalid or ignored, the default index item is returned.
13
13
  */
14
14
  export declare function askList(rl: readline.Interface, label: string, items: readonly string[], defaultIndex?: number): Promise<string>;
15
- /**
16
- * Builds the 'provideThemeStack({...})' string representation.
17
- * It compares the selected values with library defaults to generate a
18
- * minimal call string (omitting properties that match defaults).
19
- */
20
- export declare function buildProvideCall(theme: string, storageKey: string, mode: string, themes: string[]): string;
15
+ export declare function buildProvideCall(defaultTheme: string, storageKey: string, mode: string, themes: string[]): string;
@@ -14,7 +14,6 @@ exports.ask = ask;
14
14
  exports.askList = askList;
15
15
  exports.buildProvideCall = buildProvideCall;
16
16
  const readline = require("readline");
17
- const constants_1 = require("./constants");
18
17
  /**
19
18
  * Creates a readline interface using the system's standard input and output.
20
19
  */
@@ -40,31 +39,15 @@ function askList(rl_1, label_1, items_1) {
40
39
  return isNaN(n) || n < 1 || n > items.length ? items[defaultIndex] : items[n - 1];
41
40
  });
42
41
  }
43
- /**
44
- * Builds the 'provideThemeStack({...})' string representation.
45
- * It compares the selected values with library defaults to generate a
46
- * minimal call string (omitting properties that match defaults).
47
- */
48
- function buildProvideCall(theme, storageKey, mode, themes) {
49
- const defaultThemes = [...constants_1.DEFAULT_THEMES];
50
- const isDefaultThemes = themes.length === defaultThemes.length && themes.every((t, i) => t === defaultThemes[i]);
51
- const isAllDefault = theme === constants_1.DEFAULTS.theme &&
52
- storageKey === constants_1.DEFAULTS.storageKey &&
53
- mode === constants_1.DEFAULTS.mode &&
54
- isDefaultThemes;
55
- if (isAllDefault)
56
- return `provideThemeStack()`;
57
- const parts = [];
58
- if (theme !== constants_1.DEFAULTS.theme)
59
- parts.push(`theme: '${theme}'`);
60
- if (storageKey !== constants_1.DEFAULTS.storageKey)
61
- parts.push(`storageKey: '${storageKey}'`);
62
- if (mode !== constants_1.DEFAULTS.mode)
63
- parts.push(`mode: '${mode}'`);
64
- if (!isDefaultThemes) {
65
- const arr = themes.map((t) => `'${t}'`).join(', ');
66
- parts.push(`themes: [${arr}]`);
67
- }
68
- return `provideThemeStack({ ${parts.join(', ')} })`;
42
+ function buildProvideCall(defaultTheme, storageKey, mode, themes) {
43
+ const themesArr = themes.map((t) => `'${t}'`).join(', ');
44
+ return [
45
+ 'provideThemeStack({',
46
+ ` themes: [${themesArr}],`,
47
+ ` defaultTheme: '${defaultTheme}',`,
48
+ ` storageKey: '${storageKey}',`,
49
+ ` mode: '${mode}',`,
50
+ ' })',
51
+ ].join('\n');
69
52
  }
70
53
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/utils.ts"],"names":[],"mappings":";;;;;;;;;;;AAMA,4BAEC;AAKD,kBAEC;AAMD,0BAWC;AAED,4CAeC;AAjDD,qCAAqC;AAGrC;;GAEG;AACH,SAAgB,QAAQ;IACtB,OAAO,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AACpF,CAAC;AAED;;GAEG;AACH,SAAgB,GAAG,CAAC,EAAsB,EAAE,QAAgB;IAC1D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;GAGG;AACH,SAAsB,OAAO;yDAC3B,EAAsB,EACtB,KAAa,EACb,KAAwB,EACxB,YAAY,GAAG,CAAC;QAEhB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC;QAC5E,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,EAAE,EAAE,aAAa,YAAY,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpF,CAAC;CAAA;AAED,SAAgB,gBAAgB,CAC9B,YAAoB,EACpB,UAAkB,EAClB,IAAY,EACZ,MAAgB;IAEhB,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO;QACL,qBAAqB;QACrB,kBAAkB,SAAS,IAAI;QAC/B,wBAAwB,YAAY,IAAI;QACxC,sBAAsB,UAAU,IAAI;QACpC,gBAAgB,IAAI,IAAI;QACxB,QAAQ;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { Schema } from './schema';
3
+ export declare function sync(options: Schema): Rule;
@@ -0,0 +1,314 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sync = sync;
4
+ const constants_1 = require("../ng-add/constants");
5
+ // ── Regex patterns ────────────────────────────────────────────────────────────
6
+ /**
7
+ * Matches the provideThemeStack() call in app.config.ts.
8
+ * Captures the full options object string (may be empty or span multiple lines).
9
+ *
10
+ * Examples matched:
11
+ * provideThemeStack()
12
+ * provideThemeStack({ mode: 'attribute', themes: ['dark', 'light'] })
13
+ * provideThemeStack({ ← multi-line call (new explicit format)
14
+ * themes: ['light', 'dark'],
15
+ * defaultTheme: 'system',
16
+ * })
17
+ */
18
+ const PROVIDE_CALL_RE = /provideThemeStack\s*\(([\s\S]*?)\)/;
19
+ /** Extracts "mode" value from the captured options string. */
20
+ const OPTION_MODE_RE = /mode\s*:\s*['"]([^'"]+)['"]/;
21
+ /** Extracts "storageKey" value from the captured options string. */
22
+ const OPTION_KEY_RE = /storageKey\s*:\s*['"]([^'"]+)['"]/;
23
+ /** Extracts "defaultTheme" value from the captured options string. */
24
+ const OPTION_DEFAULT_THEME_RE = /defaultTheme\s*:\s*['"]([^'"]+)['"]/;
25
+ const OPTION_STRATEGY_RE = /strategy\s*:\s*['"]([^'"]+)['"]/;
26
+ /**
27
+ * Extracts the themes array from the options string.
28
+ * Matches: themes: ['light', 'dark', 'sunset']
29
+ */
30
+ const OPTION_THEMES_RE = /themes\s*:\s*\[([^\]]*)\]/;
31
+ /** Matches the complete <script> anti-flash block (identified by its marker comment). */
32
+ const SCRIPT_BLOCK_RE = /<!-- ngx-theme-stack anti-flash -->\s*<script>[\s\S]*?<\/script>/;
33
+ /** Marker injected by ng-add that delimits the Critters Trick zone in <body>. */
34
+ const CRITTERS_MARKER = '<!-- ngx-theme-stack critters-trick -->';
35
+ /** Regex that matches the entire Critters Trick block (marker + divs). */
36
+ const CRITTERS_BLOCK_RE = /<!-- ngx-theme-stack critters-trick -->[\s\S]*?<!-- \/ngx-theme-stack critters-trick -->/;
37
+ // ── Config extraction ─────────────────────────────────────────────────────────
38
+ /**
39
+ * Reads `app.config.ts` (or `main.ts`) and extracts the current
40
+ * `provideThemeStack()` configuration using regex.
41
+ *
42
+ * Falls back to library defaults for any option that is not found.
43
+ */
44
+ function extractConfig(tree, sourceRoot, context) {
45
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
46
+ const candidates = [
47
+ `${sourceRoot}/app/app.config.ts`,
48
+ `${sourceRoot}/main.ts`,
49
+ ].map((p) => (p.startsWith('/') ? p.slice(1) : p));
50
+ for (const filePath of candidates) {
51
+ if (!tree.exists(filePath))
52
+ continue;
53
+ const content = tree.readText(filePath);
54
+ const provideMatch = PROVIDE_CALL_RE.exec(content);
55
+ if (!provideMatch) {
56
+ context.logger.warn(`⚠ provideThemeStack() not found in ${filePath}. Using library defaults.\n` +
57
+ ` Tip: Add provideThemeStack({...}) to your providers for explicit control.`);
58
+ break;
59
+ }
60
+ const opts = provideMatch[1]; // everything inside provideThemeStack(...)
61
+ const mode = (_b = (_a = OPTION_MODE_RE.exec(opts)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : constants_1.DEFAULTS.mode;
62
+ const storageKey = (_d = (_c = OPTION_KEY_RE.exec(opts)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : constants_1.DEFAULTS.storageKey;
63
+ const defaultTheme = (_f = (_e = OPTION_DEFAULT_THEME_RE.exec(opts)) === null || _e === void 0 ? void 0 : _e[1]) !== null && _f !== void 0 ? _f : constants_1.DEFAULTS.defaultTheme;
64
+ const strategy = (_h = (_g = OPTION_STRATEGY_RE.exec(opts)) === null || _g === void 0 ? void 0 : _g[1]) !== null && _h !== void 0 ? _h : undefined;
65
+ // Extract themes array: ['light', 'dark', 'sunset'] → ['light', 'dark', 'sunset']
66
+ const themesRaw = (_k = (_j = OPTION_THEMES_RE.exec(opts)) === null || _j === void 0 ? void 0 : _j[1]) !== null && _k !== void 0 ? _k : '';
67
+ const themes = themesRaw
68
+ ? themesRaw
69
+ .split(',')
70
+ .map((t) => t.trim().replace(/^['"]|['"]$/g, ''))
71
+ .filter(Boolean)
72
+ : [...constants_1.DEFAULTS.themes];
73
+ context.logger.info(` Detected mode : ${mode}`);
74
+ context.logger.info(` Detected strategy : ${strategy !== null && strategy !== void 0 ? strategy : '(not in code, using auto-detect)'}`);
75
+ context.logger.info(` Detected storageKey : ${storageKey}`);
76
+ context.logger.info(` Detected defaultTheme : ${defaultTheme}`);
77
+ context.logger.info(` Detected themes : [${themes.join(', ')}]`);
78
+ return { mode, strategy, storageKey, defaultTheme, themes };
79
+ }
80
+ // Fallback to defaults if no config file found
81
+ return {
82
+ mode: constants_1.DEFAULTS.mode,
83
+ storageKey: constants_1.DEFAULTS.storageKey,
84
+ defaultTheme: constants_1.DEFAULTS.defaultTheme,
85
+ themes: [...constants_1.DEFAULTS.themes],
86
+ };
87
+ }
88
+ // ── Anti-flash script generation ──────────────────────────────────────────────
89
+ /**
90
+ * Builds the minimal blocking inline script — identical logic to anti-flash.ts
91
+ * but kept here to avoid cross-directory dependencies in the schematic build.
92
+ */
93
+ function buildScript(config) {
94
+ const { storageKey, defaultTheme, mode } = config;
95
+ return (`(function(){try{` +
96
+ `var k=${JSON.stringify(storageKey)},` +
97
+ `d=${JSON.stringify(defaultTheme)},` +
98
+ `m=${JSON.stringify(mode)},` +
99
+ `t=localStorage.getItem(k)||d,` +
100
+ `e=document.documentElement;` +
101
+ `if(!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t))t=d;` +
102
+ `if(t==='system')t=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';` +
103
+ `if(m==='class'||m==='both')e.classList.add(t);` +
104
+ `if(m==='attribute'||m==='both')e.setAttribute('data-theme',t);` +
105
+ `if(t==='dark'||t==='light')e.style.setProperty('color-scheme',t);` +
106
+ `}catch(x){}})();`);
107
+ }
108
+ // ── Critters Trick div generation ─────────────────────────────────────────────
109
+ /**
110
+ * Generates the hidden divs for the Critters Trick based on the mode.
111
+ *
112
+ * When Angular builds for production, Critters inlines "critical" CSS.
113
+ * It determines "critical" by checking which selectors match elements in the HTML.
114
+ * By placing hidden divs with the theme class/attribute, we trick Critters into
115
+ * inlining ALL theme token blocks — achieving zero-network-request CSS for themes.
116
+ *
117
+ * @param themes - The list of themes to generate divs for (excludes 'system').
118
+ * @param mode - 'class' | 'attribute' | 'both'
119
+ */
120
+ function buildCrittersDivs(themes, mode) {
121
+ // 'system' is a meta-theme that resolves to 'light' or 'dark'; no CSS selector needed.
122
+ const renderableThemes = themes.filter((t) => t !== 'system');
123
+ const divs = renderableThemes
124
+ .map((theme) => {
125
+ if (mode === 'class') {
126
+ return ` <div class="${theme}"></div>`;
127
+ }
128
+ else if (mode === 'attribute') {
129
+ return ` <div data-theme="${theme}"></div>`;
130
+ }
131
+ else {
132
+ // 'both'
133
+ return ` <div class="${theme}" data-theme="${theme}"></div>`;
134
+ }
135
+ })
136
+ .join('\n');
137
+ return (`<!-- ngx-theme-stack critters-trick -->\n` +
138
+ ` <div id="ngx-theme-stack-critters-trick" hidden>\n${divs}\n </div>\n` +
139
+ ` <!-- /ngx-theme-stack critters-trick -->`);
140
+ }
141
+ // ── index.html patching ───────────────────────────────────────────────────────
142
+ function syncIndexHtml(tree, context, sourceRoot, config, strategy) {
143
+ const candidates = [`${sourceRoot}/index.html`, 'public/index.html'].map((p) => p.startsWith('/') ? p.slice(1) : p);
144
+ for (const path of candidates) {
145
+ if (!tree.exists(path))
146
+ continue;
147
+ let content = tree.readText(path);
148
+ if (!content.includes('ngx-theme-stack anti-flash')) {
149
+ context.logger.warn(`⚠ Anti-flash script marker not found in ${path}.\n` +
150
+ ` Run 'ng add ngx-theme-stack' first, or add the script manually.`);
151
+ return;
152
+ }
153
+ // ── 1. Sync the anti-flash JS script ───────────────────────────────────
154
+ const newScriptBlock = `<!-- ngx-theme-stack anti-flash -->\n <script>${buildScript(config)}</script>`;
155
+ const updatedScript = content.replace(SCRIPT_BLOCK_RE, newScriptBlock);
156
+ if (updatedScript === content) {
157
+ context.logger.warn(`⚠ Could not replace script block in ${path}. The format may have changed.`);
158
+ return;
159
+ }
160
+ content = updatedScript;
161
+ context.logger.info(`✔ Anti-flash script synced in ${path}`);
162
+ // ── 2. Sync the Critters Trick divs (only for 'critters' strategy) ─────
163
+ if (strategy === 'critters') {
164
+ const crittersBlock = buildCrittersDivs(config.themes, config.mode);
165
+ const CRITTERS_ID_RE = /<div id="ngx-theme-stack-critters-trick"[\s\S]*?<\/div>/;
166
+ if (CRITTERS_BLOCK_RE.test(content)) {
167
+ content = content.replace(CRITTERS_BLOCK_RE, crittersBlock);
168
+ context.logger.info(`✔ Critters Trick block updated in ${path}`);
169
+ }
170
+ else if (CRITTERS_ID_RE.test(content)) {
171
+ content = content.replace(CRITTERS_ID_RE, crittersBlock);
172
+ context.logger.info(`✔ Critters Trick div wrapped in ${path}`);
173
+ }
174
+ else if (content.includes(CRITTERS_MARKER)) {
175
+ content = content.replace(CRITTERS_MARKER, crittersBlock);
176
+ context.logger.info(`✔ Critters Trick divs injected in ${path}`);
177
+ }
178
+ else {
179
+ content = content.replace('</body>', ` ${crittersBlock}\n </body>`);
180
+ context.logger.info(`✔ Critters Trick block added before </body> in ${path}`);
181
+ }
182
+ }
183
+ else {
184
+ // blocking strategy: remove any existing Critters divs if present
185
+ if (CRITTERS_BLOCK_RE.test(content)) {
186
+ content = content.replace(CRITTERS_BLOCK_RE, '');
187
+ context.logger.info(`✔ Critters Trick divs removed (blocking strategy) in ${path}`);
188
+ }
189
+ }
190
+ tree.overwrite(path, content);
191
+ return;
192
+ }
193
+ context.logger.warn(`⚠ Could not find index.html (tried: ${candidates.join(', ')}).`);
194
+ }
195
+ // ── angular.json patching ───────────────────────────────────────────────────
196
+ function syncAngularJson(tree, context, projectName, strategy) {
197
+ var _a, _b;
198
+ const content = tree.read('/angular.json');
199
+ if (!content)
200
+ return;
201
+ const workspace = JSON.parse(content.toString());
202
+ const project = workspace.projects[projectName];
203
+ if (!project)
204
+ return;
205
+ const buildTarget = (_a = project.architect) === null || _a === void 0 ? void 0 : _a.build;
206
+ if (!buildTarget)
207
+ return;
208
+ const prodConfig = (_b = buildTarget.configurations) === null || _b === void 0 ? void 0 : _b.production;
209
+ if (!prodConfig)
210
+ return;
211
+ if (strategy === 'blocking') {
212
+ // Disable inlineCritical for blocking strategy
213
+ if (typeof prodConfig.optimization === 'object') {
214
+ prodConfig.optimization.styles = prodConfig.optimization.styles || {};
215
+ if (prodConfig.optimization.styles.inlineCritical !== false) {
216
+ prodConfig.optimization.styles.inlineCritical = false;
217
+ context.logger.info(`✔ Disabled inlineCritical in angular.json projects/${projectName} (production).`);
218
+ }
219
+ }
220
+ else {
221
+ // It's either boolean or undefined
222
+ prodConfig.optimization = {
223
+ styles: { inlineCritical: false },
224
+ };
225
+ context.logger.info(`✔ Disabled inlineCritical in angular.json projects/${projectName} (production).`);
226
+ }
227
+ }
228
+ else {
229
+ // Enable inlineCritical (or revert to default) for critters strategy
230
+ if (typeof prodConfig.optimization === 'object' && prodConfig.optimization.styles) {
231
+ if (prodConfig.optimization.styles.inlineCritical === false) {
232
+ prodConfig.optimization.styles.inlineCritical = true;
233
+ context.logger.info(`✔ Re-enabled inlineCritical in angular.json projects/${projectName} (production).`);
234
+ }
235
+ }
236
+ }
237
+ tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2));
238
+ }
239
+ // ── Schematic factory ─────────────────────────────────────────────────────────
240
+ /**
241
+ * `ng generate ngx-theme-stack:sync`
242
+ *
243
+ * Reads the current `provideThemeStack()` configuration from `app.config.ts`
244
+ * and regenerates:
245
+ *
246
+ * 1. **The anti-flash inline script** in `index.html` — keeping `storageKey`,
247
+ * `defaultTheme`, and `mode` in sync with the Angular provider.
248
+ *
249
+ * 2. **The Critters Trick divs** (if `strategy: 'critters'`) — hidden `<div>`
250
+ * elements that trick Angular's built-in CSS inliner (Critters) into treating
251
+ * all theme token blocks as "critical CSS", inlining them in the `<head>`
252
+ * at build time. This achieves zero-flash without any extra network requests.
253
+ *
254
+ * Run this whenever you change `mode`, `storageKey`, `defaultTheme`, or `themes`
255
+ * inside `provideThemeStack()`. Tip: add it as a `prebuild` script in package.json
256
+ * so it runs automatically before every build.
257
+ *
258
+ * @example
259
+ * // One-off sync
260
+ * ng generate ngx-theme-stack:sync
261
+ *
262
+ * @example
263
+ * // Automatic sync (recommended — add to package.json)
264
+ * "prebuild": "ng generate ngx-theme-stack:sync"
265
+ */
266
+ /**
267
+ * Auto-detects the strategy by checking if a Critters marker exists in index.html.
268
+ * This allows the prebuild command to run with zero extra flags.
269
+ */
270
+ function detectStrategy(tree, sourceRoot, explicitStrategy) {
271
+ if (explicitStrategy)
272
+ return explicitStrategy;
273
+ const candidates = [`${sourceRoot}/index.html`, 'public/index.html'].map((p) => p.startsWith('/') ? p.slice(1) : p);
274
+ for (const path of candidates) {
275
+ if (!tree.exists(path))
276
+ continue;
277
+ const content = tree.readText(path);
278
+ // If either the full block OR the bare marker comment exist → critters
279
+ if (content.includes('ngx-theme-stack critters-trick'))
280
+ return 'critters';
281
+ return 'blocking';
282
+ }
283
+ return 'critters'; // safe default
284
+ }
285
+ function sync(options) {
286
+ return (tree, context) => {
287
+ var _a, _b;
288
+ const workspaceConfig = tree.read('/angular.json');
289
+ if (!workspaceConfig) {
290
+ throw new Error('Could not find angular.json. Are you in an Angular workspace?');
291
+ }
292
+ const workspace = JSON.parse(workspaceConfig.toString());
293
+ const projectName = (_a = options.project) !== null && _a !== void 0 ? _a : workspace.defaultProject;
294
+ const project = workspace.projects[projectName];
295
+ if (!project) {
296
+ throw new Error(`Project "${projectName}" not found in angular.json.`);
297
+ }
298
+ const sourceRoot = project.sourceRoot || `${(_b = project.root) !== null && _b !== void 0 ? _b : ''}/src`;
299
+ const config = extractConfig(tree, sourceRoot, context);
300
+ const strategy = (options.strategy || config.strategy || detectStrategy(tree, sourceRoot));
301
+ context.logger.info('');
302
+ context.logger.info(`🔄 ngx-theme-stack sync [project: ${projectName}, strategy: ${strategy}]`);
303
+ context.logger.info('');
304
+ syncIndexHtml(tree, context, sourceRoot, config, strategy);
305
+ syncAngularJson(tree, context, projectName, strategy);
306
+ context.logger.info('');
307
+ context.logger.info('✅ Done! The anti-flash setup is now in sync with your provider config.');
308
+ if (strategy === 'critters') {
309
+ context.logger.info(' Critters Trick: theme CSS will be automatically inlined at build time.');
310
+ }
311
+ context.logger.info('');
312
+ };
313
+ }
314
+ //# sourceMappingURL=index.js.map