ngx-theme-stack 2.0.0 → 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.
- package/README.md +79 -12
- package/fesm2022/ngx-theme-stack.mjs +50 -32
- package/fesm2022/ngx-theme-stack.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/ng-add/anti-flash.d.ts +2 -0
- package/schematics/ng-add/anti-flash.js +56 -19
- package/schematics/ng-add/anti-flash.js.map +1 -1
- package/schematics/ng-add/app-config.js +5 -3
- package/schematics/ng-add/app-config.js.map +1 -1
- package/schematics/ng-add/constants.d.ts +1 -0
- package/schematics/ng-add/constants.js +1 -0
- package/schematics/ng-add/constants.js.map +1 -1
- package/schematics/ng-add/index.js +88 -11
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +2 -0
- package/schematics/ng-add/schema.json +7 -2
- package/schematics/ng-add/utils.d.ts +0 -5
- package/schematics/ng-add/utils.js +9 -26
- package/schematics/ng-add/utils.js.map +1 -1
- package/schematics/sync/index.d.ts +0 -9
- package/schematics/sync/index.js +198 -37
- package/schematics/sync/index.js.map +1 -1
- package/schematics/sync/schema.d.ts +2 -0
- package/schematics/sync/schema.json +5 -0
- package/types/ngx-theme-stack.d.ts +70 -31
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/constants.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEtD,QAAA,QAAQ,GAAG;IACtB,YAAY,EAAE,QAAQ;IACtB,UAAU,EAAE,uBAAuB;IACnC,IAAI,EAAE,OAAO;IACb,MAAM,EAAE,CAAC,GAAG,sBAAc,CAAC;CACnB,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/constants.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACU,QAAA,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEtD,QAAA,QAAQ,GAAG;IACtB,YAAY,EAAE,QAAQ;IACtB,UAAU,EAAE,uBAAuB;IACnC,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,CAAC,GAAG,sBAAc,CAAC;CACnB,CAAC"}
|
|
@@ -40,8 +40,14 @@ function collectCustomOptions() {
|
|
|
40
40
|
const storageKey = rawKey || constants_1.DEFAULTS.storageKey;
|
|
41
41
|
const MODES = ['class', 'attribute', 'both'];
|
|
42
42
|
const mode = yield (0, utils_1.askList)(rl, 'How to apply theme on <html>:', MODES, 0);
|
|
43
|
+
const STRATEGIES = ['critters', 'blocking'];
|
|
43
44
|
process.stdout.write('\n');
|
|
44
|
-
|
|
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 };
|
|
45
51
|
}
|
|
46
52
|
finally {
|
|
47
53
|
rl.close();
|
|
@@ -77,33 +83,104 @@ function ngAdd(options) {
|
|
|
77
83
|
context.logger.info('');
|
|
78
84
|
let provideCall;
|
|
79
85
|
let scriptOptions;
|
|
86
|
+
let finalThemes;
|
|
87
|
+
let themesToScaffold;
|
|
88
|
+
let finalStrategy;
|
|
80
89
|
if (options.mode === 'quick') {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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).');
|
|
88
99
|
}
|
|
89
100
|
else {
|
|
90
101
|
context.logger.info('🛠 Custom setup — answer the prompts below:');
|
|
91
102
|
const opts = yield collectCustomOptions();
|
|
92
|
-
const { defaultTheme, storageKey, mode, themes } = opts;
|
|
103
|
+
const { defaultTheme, storageKey, mode, themes, strategy } = opts;
|
|
93
104
|
context.logger.info(' Applying your configuration:');
|
|
94
105
|
context.logger.info(` defaultTheme : ${defaultTheme}`);
|
|
95
106
|
context.logger.info(` themes : [${themes.join(', ')}]`);
|
|
96
107
|
context.logger.info(` storageKey : ${storageKey}`);
|
|
97
108
|
context.logger.info(` mode : ${mode}`);
|
|
109
|
+
context.logger.info(` strategy : ${strategy}`);
|
|
98
110
|
provideCall = (0, utils_1.buildProvideCall)(defaultTheme, storageKey, mode, themes);
|
|
99
111
|
scriptOptions = { storageKey, defaultTheme, mode };
|
|
112
|
+
finalThemes = themes;
|
|
113
|
+
themesToScaffold = themes.filter((t) => t !== 'system');
|
|
114
|
+
finalStrategy = strategy;
|
|
100
115
|
}
|
|
101
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
|
+
},
|
|
102
179
|
(t, ctx) => {
|
|
103
180
|
(0, app_config_1.patchAppConfig)(t, ctx, projectSourceRoot, provideCall);
|
|
104
|
-
(0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, scriptOptions);
|
|
181
|
+
(0, anti_flash_1.patchIndexHtml)(t, ctx, projectSourceRoot, Object.assign(Object.assign({}, scriptOptions), { themes: finalThemes, strategy: options.strategy || finalStrategy }));
|
|
105
182
|
ctx.logger.info('');
|
|
106
|
-
ctx.logger.info('✅ Done!
|
|
183
|
+
ctx.logger.info('✅ Done! ngx-theme-stack is ready with automatic sync on build.');
|
|
107
184
|
ctx.logger.info('');
|
|
108
185
|
},
|
|
109
186
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":";;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../projects/ngx-theme-stack/schematics/ng-add/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
|
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
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
42
|
function buildProvideCall(defaultTheme, storageKey, mode, themes) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (defaultTheme !== constants_1.DEFAULTS.defaultTheme)
|
|
59
|
-
parts.push(`defaultTheme: '${defaultTheme}'`);
|
|
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(', ')} })`;
|
|
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":";;;;;;;;;;;AAMA,4BAEC;AAKD,kBAEC;AAMD,0BAWC;
|
|
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"}
|
|
@@ -1,12 +1,3 @@
|
|
|
1
1
|
import { Rule } from '@angular-devkit/schematics';
|
|
2
2
|
import { Schema } from './schema';
|
|
3
|
-
/**
|
|
4
|
-
* `ng generate ngx-theme-stack:sync`
|
|
5
|
-
*
|
|
6
|
-
* Reads the current `provideThemeStack()` configuration from `app.config.ts`
|
|
7
|
-
* and regenerates the anti-flash inline script in `index.html` to match.
|
|
8
|
-
*
|
|
9
|
-
* Run this whenever you change `mode`, `storageKey`, or `defaultTheme`
|
|
10
|
-
* inside `provideThemeStack()` to keep the anti-flash script in sync.
|
|
11
|
-
*/
|
|
12
3
|
export declare function sync(options: Schema): Rule;
|
package/schematics/sync/index.js
CHANGED
|
@@ -5,36 +5,36 @@ const constants_1 = require("../ng-add/constants");
|
|
|
5
5
|
// ── Regex patterns ────────────────────────────────────────────────────────────
|
|
6
6
|
/**
|
|
7
7
|
* Matches the provideThemeStack() call in app.config.ts.
|
|
8
|
-
* Captures the full options object string (may be empty).
|
|
8
|
+
* Captures the full options object string (may be empty or span multiple lines).
|
|
9
9
|
*
|
|
10
10
|
* Examples matched:
|
|
11
11
|
* provideThemeStack()
|
|
12
12
|
* provideThemeStack({ mode: 'attribute', themes: ['dark', 'light'] })
|
|
13
|
+
* provideThemeStack({ ← multi-line call (new explicit format)
|
|
14
|
+
* themes: ['light', 'dark'],
|
|
15
|
+
* defaultTheme: 'system',
|
|
16
|
+
* })
|
|
13
17
|
*/
|
|
14
|
-
const PROVIDE_CALL_RE = /provideThemeStack\s*\(([
|
|
15
|
-
/**
|
|
16
|
-
* Extracts "mode" value from the captured options string.
|
|
17
|
-
* e.g. { mode: 'attribute', ... } → 'attribute'
|
|
18
|
-
*/
|
|
18
|
+
const PROVIDE_CALL_RE = /provideThemeStack\s*\(([\s\S]*?)\)/;
|
|
19
|
+
/** Extracts "mode" value from the captured options string. */
|
|
19
20
|
const OPTION_MODE_RE = /mode\s*:\s*['"]([^'"]+)['"]/;
|
|
20
|
-
/**
|
|
21
|
-
* Extracts "storageKey" value from the captured options string.
|
|
22
|
-
*/
|
|
21
|
+
/** Extracts "storageKey" value from the captured options string. */
|
|
23
22
|
const OPTION_KEY_RE = /storageKey\s*:\s*['"]([^'"]+)['"]/;
|
|
24
|
-
/**
|
|
25
|
-
* Extracts "defaultTheme" value from the captured options string.
|
|
26
|
-
*/
|
|
23
|
+
/** Extracts "defaultTheme" value from the captured options string. */
|
|
27
24
|
const OPTION_DEFAULT_THEME_RE = /defaultTheme\s*:\s*['"]([^'"]+)['"]/;
|
|
25
|
+
const OPTION_STRATEGY_RE = /strategy\s*:\s*['"]([^'"]+)['"]/;
|
|
28
26
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*/
|
|
32
|
-
const SCRIPT_MARKER = 'ngx-theme-stack anti-flash';
|
|
33
|
-
/**
|
|
34
|
-
* Matches the complete <script> block that contains the anti-flash script.
|
|
35
|
-
* Captures everything between the comment marker and the closing </script>.
|
|
27
|
+
* Extracts the themes array from the options string.
|
|
28
|
+
* Matches: themes: ['light', 'dark', 'sunset']
|
|
36
29
|
*/
|
|
30
|
+
const OPTION_THEMES_RE = /themes\s*:\s*\[([^\]]*)\]/;
|
|
31
|
+
/** Matches the complete <script> anti-flash block (identified by its marker comment). */
|
|
37
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
38
|
/**
|
|
39
39
|
* Reads `app.config.ts` (or `main.ts`) and extracts the current
|
|
40
40
|
* `provideThemeStack()` configuration using regex.
|
|
@@ -42,7 +42,7 @@ const SCRIPT_BLOCK_RE = /<!-- ngx-theme-stack anti-flash -->\s*<script>[\s\S]*?<
|
|
|
42
42
|
* Falls back to library defaults for any option that is not found.
|
|
43
43
|
*/
|
|
44
44
|
function extractConfig(tree, sourceRoot, context) {
|
|
45
|
-
var _a, _b, _c, _d, _e, _f;
|
|
45
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
46
46
|
const candidates = [
|
|
47
47
|
`${sourceRoot}/app/app.config.ts`,
|
|
48
48
|
`${sourceRoot}/main.ts`,
|
|
@@ -53,29 +53,42 @@ function extractConfig(tree, sourceRoot, context) {
|
|
|
53
53
|
const content = tree.readText(filePath);
|
|
54
54
|
const provideMatch = PROVIDE_CALL_RE.exec(content);
|
|
55
55
|
if (!provideMatch) {
|
|
56
|
-
context.logger.warn(`⚠ provideThemeStack() not found in ${filePath}. Using library defaults
|
|
56
|
+
context.logger.warn(`⚠ provideThemeStack() not found in ${filePath}. Using library defaults.\n` +
|
|
57
|
+
` Tip: Add provideThemeStack({...}) to your providers for explicit control.`);
|
|
57
58
|
break;
|
|
58
59
|
}
|
|
59
60
|
const opts = provideMatch[1]; // everything inside provideThemeStack(...)
|
|
60
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;
|
|
61
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;
|
|
62
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];
|
|
63
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)'}`);
|
|
64
75
|
context.logger.info(` Detected storageKey : ${storageKey}`);
|
|
65
76
|
context.logger.info(` Detected defaultTheme : ${defaultTheme}`);
|
|
66
|
-
|
|
77
|
+
context.logger.info(` Detected themes : [${themes.join(', ')}]`);
|
|
78
|
+
return { mode, strategy, storageKey, defaultTheme, themes };
|
|
67
79
|
}
|
|
68
80
|
// Fallback to defaults if no config file found
|
|
69
81
|
return {
|
|
70
82
|
mode: constants_1.DEFAULTS.mode,
|
|
71
83
|
storageKey: constants_1.DEFAULTS.storageKey,
|
|
72
84
|
defaultTheme: constants_1.DEFAULTS.defaultTheme,
|
|
85
|
+
themes: [...constants_1.DEFAULTS.themes],
|
|
73
86
|
};
|
|
74
87
|
}
|
|
75
|
-
// ──
|
|
88
|
+
// ── Anti-flash script generation ──────────────────────────────────────────────
|
|
76
89
|
/**
|
|
77
90
|
* Builds the minimal blocking inline script — identical logic to anti-flash.ts
|
|
78
|
-
* but
|
|
91
|
+
* but kept here to avoid cross-directory dependencies in the schematic build.
|
|
79
92
|
*/
|
|
80
93
|
function buildScript(config) {
|
|
81
94
|
const { storageKey, defaultTheme, mode } = config;
|
|
@@ -92,40 +105,183 @@ function buildScript(config) {
|
|
|
92
105
|
`if(t==='dark'||t==='light')e.style.setProperty('color-scheme',t);` +
|
|
93
106
|
`}catch(x){}})();`);
|
|
94
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
|
+
}
|
|
95
141
|
// ── index.html patching ───────────────────────────────────────────────────────
|
|
96
|
-
function syncIndexHtml(tree, context, sourceRoot, config) {
|
|
142
|
+
function syncIndexHtml(tree, context, sourceRoot, config, strategy) {
|
|
97
143
|
const candidates = [`${sourceRoot}/index.html`, 'public/index.html'].map((p) => p.startsWith('/') ? p.slice(1) : p);
|
|
98
144
|
for (const path of candidates) {
|
|
99
145
|
if (!tree.exists(path))
|
|
100
146
|
continue;
|
|
101
|
-
|
|
102
|
-
if (!content.includes(
|
|
147
|
+
let content = tree.readText(path);
|
|
148
|
+
if (!content.includes('ngx-theme-stack anti-flash')) {
|
|
103
149
|
context.logger.warn(`⚠ Anti-flash script marker not found in ${path}.\n` +
|
|
104
150
|
` Run 'ng add ngx-theme-stack' first, or add the script manually.`);
|
|
105
151
|
return;
|
|
106
152
|
}
|
|
153
|
+
// ── 1. Sync the anti-flash JS script ───────────────────────────────────
|
|
107
154
|
const newScriptBlock = `<!-- ngx-theme-stack anti-flash -->\n <script>${buildScript(config)}</script>`;
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
155
|
+
const updatedScript = content.replace(SCRIPT_BLOCK_RE, newScriptBlock);
|
|
156
|
+
if (updatedScript === content) {
|
|
110
157
|
context.logger.warn(`⚠ Could not replace script block in ${path}. The format may have changed.`);
|
|
111
158
|
return;
|
|
112
159
|
}
|
|
113
|
-
|
|
160
|
+
content = updatedScript;
|
|
114
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);
|
|
115
191
|
return;
|
|
116
192
|
}
|
|
117
193
|
context.logger.warn(`⚠ Could not find index.html (tried: ${candidates.join(', ')}).`);
|
|
118
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
|
+
}
|
|
119
239
|
// ── Schematic factory ─────────────────────────────────────────────────────────
|
|
120
240
|
/**
|
|
121
241
|
* `ng generate ngx-theme-stack:sync`
|
|
122
242
|
*
|
|
123
243
|
* Reads the current `provideThemeStack()` configuration from `app.config.ts`
|
|
124
|
-
* and regenerates
|
|
244
|
+
* and regenerates:
|
|
125
245
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
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.
|
|
128
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
|
+
}
|
|
129
285
|
function sync(options) {
|
|
130
286
|
return (tree, context) => {
|
|
131
287
|
var _a, _b;
|
|
@@ -140,13 +296,18 @@ function sync(options) {
|
|
|
140
296
|
throw new Error(`Project "${projectName}" not found in angular.json.`);
|
|
141
297
|
}
|
|
142
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));
|
|
143
301
|
context.logger.info('');
|
|
144
|
-
context.logger.info(`🔄 ngx-theme-stack sync [project: ${projectName}]`);
|
|
302
|
+
context.logger.info(`🔄 ngx-theme-stack sync [project: ${projectName}, strategy: ${strategy}]`);
|
|
145
303
|
context.logger.info('');
|
|
146
|
-
|
|
147
|
-
|
|
304
|
+
syncIndexHtml(tree, context, sourceRoot, config, strategy);
|
|
305
|
+
syncAngularJson(tree, context, projectName, strategy);
|
|
148
306
|
context.logger.info('');
|
|
149
|
-
context.logger.info('✅ Done! The anti-flash
|
|
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
|
+
}
|
|
150
311
|
context.logger.info('');
|
|
151
312
|
};
|
|
152
313
|
}
|