meno-core 1.0.45 → 1.0.46
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/build-astro.ts +214 -63
- package/dist/bin/cli.js +2 -2
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-NZTSJS5C.js → chunk-2QK6U5UK.js} +3 -2
- package/dist/chunks/{chunk-NZTSJS5C.js.map → chunk-2QK6U5UK.js.map} +2 -2
- package/dist/chunks/{chunk-BZQKEJQY.js → chunk-77ZB6353.js} +29 -18
- package/dist/chunks/chunk-77ZB6353.js.map +7 -0
- package/dist/chunks/{chunk-TVH3TC2T.js → chunk-C6U5T5S5.js} +6 -6
- package/dist/chunks/{chunk-5ZASE4IG.js → chunk-FED5MME6.js} +234 -11
- package/dist/chunks/{chunk-5ZASE4IG.js.map → chunk-FED5MME6.js.map} +3 -3
- package/dist/chunks/{chunk-5Z5VQRTJ.js → chunk-I7YIGZXT.js} +4 -4
- package/dist/chunks/{chunk-5Z5VQRTJ.js.map → chunk-I7YIGZXT.js.map} +2 -2
- package/dist/chunks/{chunk-OUNJ76QM.js → chunk-ORN7S4AP.js} +5 -5
- package/dist/chunks/{chunk-GYF3ABI3.js → chunk-UUA5LEWF.js} +3 -3
- package/dist/chunks/{chunk-GYF3ABI3.js.map → chunk-UUA5LEWF.js.map} +2 -2
- package/dist/chunks/{chunk-WQSG5WHC.js → chunk-ZTKHJQ2Z.js} +2 -2
- package/dist/chunks/{chunk-F7MA62WG.js → chunk-ZWYDT3QJ.js} +3 -3
- package/dist/chunks/{configService-6KTT6GRT.js → configService-DYCUEURL.js} +3 -3
- package/dist/chunks/{constants-L5IKLB6U.js → constants-GWBAD66U.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +4 -4
- package/dist/lib/server/index.js +586 -142
- package/dist/lib/server/index.js.map +3 -3
- package/dist/lib/shared/index.js +7 -3
- package/dist/lib/shared/index.js.map +2 -2
- package/dist/lib/test-utils/index.js +1 -1
- package/lib/client/templateEngine.test.ts +64 -0
- package/lib/server/astro/astroEmitHelpers.ts +18 -0
- package/lib/server/astro/cmsPageEmitter.ts +31 -1
- package/lib/server/astro/componentEmitter.test.ts +59 -0
- package/lib/server/astro/componentEmitter.ts +43 -10
- package/lib/server/astro/cssCollector.ts +58 -11
- package/lib/server/astro/nodeToAstro.test.ts +397 -5
- package/lib/server/astro/nodeToAstro.ts +478 -63
- package/lib/server/astro/pageEmitter.ts +31 -1
- package/lib/server/astro/tailwindMapper.test.ts +119 -0
- package/lib/server/astro/tailwindMapper.ts +67 -1
- package/lib/server/runtime/httpServer.ts +12 -4
- package/lib/server/ssr/htmlGenerator.ts +1 -1
- package/lib/server/ssr/jsCollector.ts +2 -2
- package/lib/server/ssr/ssrRenderer.test.ts +32 -0
- package/lib/server/ssr/ssrRenderer.ts +26 -11
- package/lib/shared/constants.ts +1 -0
- package/lib/shared/cssGeneration.test.ts +109 -3
- package/lib/shared/cssGeneration.ts +98 -13
- package/lib/shared/cssNamedColors.ts +47 -0
- package/lib/shared/cssProperties.ts +2 -2
- package/lib/shared/index.ts +1 -0
- package/package.json +1 -1
- package/dist/chunks/chunk-BZQKEJQY.js.map +0 -7
- /package/dist/chunks/{chunk-TVH3TC2T.js.map → chunk-C6U5T5S5.js.map} +0 -0
- /package/dist/chunks/{chunk-OUNJ76QM.js.map → chunk-ORN7S4AP.js.map} +0 -0
- /package/dist/chunks/{chunk-WQSG5WHC.js.map → chunk-ZTKHJQ2Z.js.map} +0 -0
- /package/dist/chunks/{chunk-F7MA62WG.js.map → chunk-ZWYDT3QJ.js.map} +0 -0
- /package/dist/chunks/{configService-6KTT6GRT.js.map → configService-DYCUEURL.js.map} +0 -0
- /package/dist/chunks/{constants-L5IKLB6U.js.map → constants-GWBAD66U.js.map} +0 -0
|
@@ -10,6 +10,7 @@ import { nodeToAstro, type AstroEmitContext } from './nodeToAstro';
|
|
|
10
10
|
import { transformCMSTemplate } from './templateTransformer';
|
|
11
11
|
import type { ImageMetadataMap } from '../ssr/imageMetadata';
|
|
12
12
|
import type { SlugMap } from '../../shared/slugTranslator';
|
|
13
|
+
import type { ResponsiveScales } from '../../shared/responsiveScaling';
|
|
13
14
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Types
|
|
@@ -46,6 +47,8 @@ export interface CMSPageEmitOptions {
|
|
|
46
47
|
pageName: string;
|
|
47
48
|
/** Breakpoint config for responsive Tailwind classes */
|
|
48
49
|
breakpoints?: BreakpointConfig;
|
|
50
|
+
/** Responsive scales config for auto-scaling at breakpoints */
|
|
51
|
+
responsiveScales?: ResponsiveScales;
|
|
49
52
|
/** Image metadata map for responsive image generation */
|
|
50
53
|
imageMetadataMap?: ImageMetadataMap;
|
|
51
54
|
/** Internationalization config */
|
|
@@ -56,6 +59,12 @@ export interface CMSPageEmitOptions {
|
|
|
56
59
|
slugMappings?: SlugMap[];
|
|
57
60
|
/** Image format: 'webp' uses plain <img>, 'avif' uses <picture> */
|
|
58
61
|
imageFormat?: 'webp' | 'avif';
|
|
62
|
+
/**
|
|
63
|
+
* Raw-HTML slice → processed HTML captured during the template's SSR pass.
|
|
64
|
+
* Ensures `<Fragment set:html>` matches SSR output for rich-text content
|
|
65
|
+
* (image rewriting, component expansion, link localization).
|
|
66
|
+
*/
|
|
67
|
+
processedRawHtml?: Map<string, string>;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
// ---------------------------------------------------------------------------
|
|
@@ -63,7 +72,12 @@ export interface CMSPageEmitOptions {
|
|
|
63
72
|
// ---------------------------------------------------------------------------
|
|
64
73
|
|
|
65
74
|
function escapeTemplateLiteral(s: string): string {
|
|
66
|
-
return s
|
|
75
|
+
return s
|
|
76
|
+
.replace(/\\/g, '\\\\')
|
|
77
|
+
.replace(/`/g, '\\`')
|
|
78
|
+
.replace(/\$\{/g, '\\${')
|
|
79
|
+
.replace(/\u2028/g, '\\u2028')
|
|
80
|
+
.replace(/\u2029/g, '\\u2029');
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
function escapeJSX(s: string): string {
|
|
@@ -247,10 +261,12 @@ export function emitCMSPage(options: CMSPageEmitOptions): string {
|
|
|
247
261
|
ssrFallbacks,
|
|
248
262
|
pageName,
|
|
249
263
|
breakpoints: breakpointsOpt,
|
|
264
|
+
responsiveScales,
|
|
250
265
|
imageMetadataMap,
|
|
251
266
|
i18nConfig,
|
|
252
267
|
isMultiLocale,
|
|
253
268
|
slugMappings,
|
|
269
|
+
processedRawHtml,
|
|
254
270
|
} = options;
|
|
255
271
|
|
|
256
272
|
const breakpoints = breakpointsOpt ?? DEFAULT_BREAKPOINTS;
|
|
@@ -289,6 +305,7 @@ export function emitCMSPage(options: CMSPageEmitOptions): string {
|
|
|
289
305
|
fileType: 'page',
|
|
290
306
|
fileName: pageName,
|
|
291
307
|
breakpoints,
|
|
308
|
+
responsiveScales,
|
|
292
309
|
imageMetadataMap,
|
|
293
310
|
locale,
|
|
294
311
|
cmsMode: true,
|
|
@@ -298,6 +315,9 @@ export function emitCMSPage(options: CMSPageEmitOptions): string {
|
|
|
298
315
|
slugMappings,
|
|
299
316
|
i18nDefaultLocale: i18nConfig.defaultLocale,
|
|
300
317
|
imageFormat: options.imageFormat,
|
|
318
|
+
processedRawHtml,
|
|
319
|
+
imageImports: new Map<string, string>(),
|
|
320
|
+
fileDepth,
|
|
301
321
|
};
|
|
302
322
|
|
|
303
323
|
// Emit the template body
|
|
@@ -308,6 +328,16 @@ export function emitCMSPage(options: CMSPageEmitOptions): string {
|
|
|
308
328
|
importLines.push(`import { getCollection } from 'astro:content';`);
|
|
309
329
|
importLines.push(`import BaseLayout from '${layoutImportPath}';`);
|
|
310
330
|
|
|
331
|
+
// Static image imports (astro:assets <Picture>). Map size is the single
|
|
332
|
+
// source of truth — a separate boolean would be lost by child-ctx spreads.
|
|
333
|
+
if (ctx.imageImports && ctx.imageImports.size > 0) {
|
|
334
|
+
importLines.push(`import { Picture } from 'astro:assets';`);
|
|
335
|
+
const sortedImages = Array.from(ctx.imageImports.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
336
|
+
for (const [varName, importPath] of sortedImages) {
|
|
337
|
+
importLines.push(`import ${varName} from '${importPath}';`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
311
341
|
// Sort component imports alphabetically
|
|
312
342
|
const componentImports = Array.from(ctx.imports).sort();
|
|
313
343
|
for (const comp of componentImports) {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { emitAstroComponent } from './componentEmitter';
|
|
3
|
+
import type { ComponentDefinition } from '../../shared/types';
|
|
4
|
+
|
|
5
|
+
function makeComponent(overrides: Partial<ComponentDefinition['component']> = {}): ComponentDefinition {
|
|
6
|
+
return {
|
|
7
|
+
component: {
|
|
8
|
+
interface: {},
|
|
9
|
+
structure: { type: 'node', tag: 'div', children: [] } as unknown as ComponentDefinition['component']['structure'],
|
|
10
|
+
...overrides,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('emitAstroComponent — RAW_HTML_PREFIX stripping', () => {
|
|
16
|
+
test('strips marker from rich-text string default in frontmatter', () => {
|
|
17
|
+
const def = makeComponent({
|
|
18
|
+
interface: {
|
|
19
|
+
text2: {
|
|
20
|
+
type: 'rich-text',
|
|
21
|
+
default: '<!--MENO_RAW_HTML-->SEO<br>Audit Blueprint',
|
|
22
|
+
} as unknown as import('../../shared/types').PropDefinition,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const out = emitAstroComponent('SeoAudit', def, {});
|
|
26
|
+
expect(out).not.toContain('MENO_RAW_HTML');
|
|
27
|
+
expect(out).toContain('text2 = "SEO<br>Audit Blueprint"');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('strips marker from i18n default values', () => {
|
|
31
|
+
const def = makeComponent({
|
|
32
|
+
interface: {
|
|
33
|
+
text2: {
|
|
34
|
+
type: 'rich-text',
|
|
35
|
+
default: {
|
|
36
|
+
_i18n: true,
|
|
37
|
+
en: '<!--MENO_RAW_HTML-->SEO<br>Audit Blueprint',
|
|
38
|
+
pl: '<!--MENO_RAW_HTML-->Audyt SEO',
|
|
39
|
+
},
|
|
40
|
+
} as unknown as import('../../shared/types').PropDefinition,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const out = emitAstroComponent('SeoAudit', def, {});
|
|
44
|
+
expect(out).not.toContain('MENO_RAW_HTML');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('leaves non-rich-text string defaults unchanged', () => {
|
|
48
|
+
const def = makeComponent({
|
|
49
|
+
interface: {
|
|
50
|
+
title: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
default: 'Hello world',
|
|
53
|
+
} as unknown as import('../../shared/types').PropDefinition,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const out = emitAstroComponent('Heading', def, {});
|
|
57
|
+
expect(out).toContain('title = "Hello world"');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -12,6 +12,8 @@ import type { BasePropDefinition, ListPropDefinition, LinkPropValue } from '../.
|
|
|
12
12
|
import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
13
13
|
import { DEFAULT_BREAKPOINTS } from '../../shared/breakpoints';
|
|
14
14
|
import { nodeToAstro, type AstroEmitContext } from './nodeToAstro';
|
|
15
|
+
import type { ResponsiveScales } from '../../shared/responsiveScaling';
|
|
16
|
+
import { stripRawHtmlPrefixDeep } from './astroEmitHelpers';
|
|
15
17
|
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
17
19
|
// Helpers
|
|
@@ -49,7 +51,7 @@ function propDefToTSType(def: PropDefinition): string {
|
|
|
49
51
|
*/
|
|
50
52
|
function formatDefault(def: PropDefinition): string | null {
|
|
51
53
|
if (!('default' in def) || def.default === undefined) return null;
|
|
52
|
-
const val = def.default;
|
|
54
|
+
const val = stripRawHtmlPrefixDeep(def.default);
|
|
53
55
|
|
|
54
56
|
if (typeof val === 'string') return JSON.stringify(val);
|
|
55
57
|
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
|
@@ -129,7 +131,8 @@ export function emitAstroComponent(
|
|
|
129
131
|
def: ComponentDefinition,
|
|
130
132
|
allComponents: Record<string, ComponentDefinition>,
|
|
131
133
|
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS,
|
|
132
|
-
defaultLocale: string = 'en'
|
|
134
|
+
defaultLocale: string = 'en',
|
|
135
|
+
responsiveScales?: ResponsiveScales
|
|
133
136
|
): string {
|
|
134
137
|
const comp = def.component;
|
|
135
138
|
const propDefs = comp.interface || {};
|
|
@@ -152,7 +155,10 @@ export function emitAstroComponent(
|
|
|
152
155
|
fileType: 'component',
|
|
153
156
|
fileName: name,
|
|
154
157
|
breakpoints,
|
|
158
|
+
responsiveScales,
|
|
155
159
|
defaultLocale,
|
|
160
|
+
imageImports: new Map<string, string>(),
|
|
161
|
+
fileDepth: 0, // components live at src/components/
|
|
156
162
|
};
|
|
157
163
|
|
|
158
164
|
// Emit the template body
|
|
@@ -162,7 +168,14 @@ export function emitAstroComponent(
|
|
|
162
168
|
templateBody = mergeClassNameOntoRoot(templateBody);
|
|
163
169
|
|
|
164
170
|
// Build frontmatter (includes class prop for instance style support)
|
|
165
|
-
const frontmatter = buildFrontmatter(
|
|
171
|
+
const frontmatter = buildFrontmatter(
|
|
172
|
+
name,
|
|
173
|
+
propDefs,
|
|
174
|
+
ctx.imports,
|
|
175
|
+
ctx.dynamicTags,
|
|
176
|
+
ctx.needsI18nResolver ? defaultLocale : undefined,
|
|
177
|
+
ctx.imageImports
|
|
178
|
+
);
|
|
166
179
|
|
|
167
180
|
// Build style/script sections
|
|
168
181
|
const styleSection = comp.css ? `\n<style>\n${comp.css}\n</style>\n` : '';
|
|
@@ -181,7 +194,8 @@ function buildFrontmatter(
|
|
|
181
194
|
propDefs: Record<string, PropDefinition>,
|
|
182
195
|
imports: Set<string>,
|
|
183
196
|
dynamicTags?: Map<string, string>,
|
|
184
|
-
i18nDefaultLocale?: string
|
|
197
|
+
i18nDefaultLocale?: string,
|
|
198
|
+
imageImports?: Map<string, string>
|
|
185
199
|
): string {
|
|
186
200
|
const lines: string[] = [];
|
|
187
201
|
|
|
@@ -190,6 +204,16 @@ function buildFrontmatter(
|
|
|
190
204
|
lines.push(`import ${imp} from './${imp}.astro';`);
|
|
191
205
|
}
|
|
192
206
|
|
|
207
|
+
// Static image imports (astro:assets <Picture>). Map size is the single
|
|
208
|
+
// source of truth — a separate boolean would be lost by child-ctx spreads.
|
|
209
|
+
if (imageImports && imageImports.size > 0) {
|
|
210
|
+
lines.push(`import { Picture } from 'astro:assets';`);
|
|
211
|
+
const sortedImages = Array.from(imageImports.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
212
|
+
for (const [varName, importPath] of sortedImages) {
|
|
213
|
+
lines.push(`import ${varName} from '${importPath}';`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
193
217
|
if (lines.length > 0) lines.push('');
|
|
194
218
|
|
|
195
219
|
const propEntries = Object.entries(propDefs);
|
|
@@ -314,16 +338,25 @@ function transformDefineVarsJS(js: string, varNames: string[]): string {
|
|
|
314
338
|
|
|
315
339
|
/**
|
|
316
340
|
* Build the script section with proper el/props initialization.
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
341
|
+
* Captures el immediately via document.currentScript.previousElementSibling,
|
|
342
|
+
* then defers actual JS execution until DOMContentLoaded so that external
|
|
343
|
+
* libraries (e.g. Swiper loaded with defer) are available.
|
|
320
344
|
*/
|
|
321
345
|
function buildScriptSection(
|
|
322
346
|
js: string,
|
|
323
347
|
comp: StructuredComponentDefinition,
|
|
324
348
|
propDefs: Record<string, PropDefinition>
|
|
325
349
|
): string {
|
|
326
|
-
|
|
350
|
+
// Capture el immediately (document.currentScript is only valid during script execution)
|
|
351
|
+
// but defer the component init until DOMContentLoaded so deferred libraries are ready
|
|
352
|
+
// Remove whitespace-only text nodes so Astro's formatted HTML matches SSR's minified output.
|
|
353
|
+
// Without this, scripts that use firstChild/childNodes pick up whitespace between elements.
|
|
354
|
+
const cleanTextNodes = `(function _w(n){var c=n.firstChild,x;while(c){x=c.nextSibling;if(c.nodeType===3){if(!c.textContent.trim())n.removeChild(c)}else if(c.nodeType===1)_w(c);c=x}})(el);`;
|
|
355
|
+
|
|
356
|
+
const deferWrapper = (innerJS: string) =>
|
|
357
|
+
`var el = document.currentScript.previousElementSibling;\n` +
|
|
358
|
+
`function __init__() {\n${cleanTextNodes}\n${innerJS}\n}\n` +
|
|
359
|
+
`if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', __init__); } else { __init__(); }`;
|
|
327
360
|
|
|
328
361
|
if (comp.defineVars) {
|
|
329
362
|
const vars = comp.defineVars === true
|
|
@@ -333,9 +366,9 @@ function buildScriptSection(
|
|
|
333
366
|
if (vars.length > 0) {
|
|
334
367
|
const transformedJS = transformDefineVarsJS(js, vars);
|
|
335
368
|
const defineVarsObj = `{ ${vars.join(', ')} }`;
|
|
336
|
-
return `\n<script define:vars={${defineVarsObj}}>\n
|
|
369
|
+
return `\n<script define:vars={${defineVarsObj}}>\n(function(){\n${deferWrapper(transformedJS)}\n})();\n</script>\n`;
|
|
337
370
|
}
|
|
338
371
|
}
|
|
339
372
|
|
|
340
|
-
return `\n<script is:inline>\n
|
|
373
|
+
return `\n<script is:inline>\n(function(){\n${deferWrapper(js)}\n})();\n</script>\n`;
|
|
341
374
|
}
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
15
15
|
import { DEFAULT_BREAKPOINTS } from '../../shared/breakpoints';
|
|
16
16
|
import { propertyToTailwind } from './tailwindMapper';
|
|
17
|
+
import type { ResponsiveScales, CSSPropertyType } from '../../shared/responsiveScaling';
|
|
18
|
+
import { getScaleMultiplier, scalePropertyValue } from '../../shared/responsiveScaling';
|
|
17
19
|
|
|
18
20
|
// ---------------------------------------------------------------------------
|
|
19
21
|
// Helpers
|
|
@@ -40,7 +42,8 @@ function isResponsiveStyle(
|
|
|
40
42
|
function collectFromStyle(
|
|
41
43
|
style: StyleObject | ResponsiveStyleObject | undefined,
|
|
42
44
|
classes: Set<string>,
|
|
43
|
-
breakpoints: BreakpointConfig
|
|
45
|
+
breakpoints: BreakpointConfig,
|
|
46
|
+
responsiveScales?: ResponsiveScales
|
|
44
47
|
): void {
|
|
45
48
|
if (!style) return;
|
|
46
49
|
|
|
@@ -48,24 +51,46 @@ function collectFromStyle(
|
|
|
48
51
|
for (const [bp, bpStyle] of Object.entries(style)) {
|
|
49
52
|
if (!bpStyle) continue;
|
|
50
53
|
let prefix = '';
|
|
54
|
+
let bpName: string | undefined;
|
|
51
55
|
if (bp !== 'base') {
|
|
52
56
|
const bpValue = breakpoints[bp]?.breakpoint;
|
|
53
57
|
if (bpValue) {
|
|
54
58
|
prefix = `max-[${bpValue}px]:`;
|
|
59
|
+
bpName = bp;
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
|
-
collectFromFlatStyle(bpStyle, prefix, classes);
|
|
62
|
+
collectFromFlatStyle(bpStyle, prefix, classes, breakpoints, responsiveScales, bpName);
|
|
58
63
|
}
|
|
59
64
|
} else {
|
|
60
|
-
collectFromFlatStyle(style, '', classes);
|
|
65
|
+
collectFromFlatStyle(style, '', classes, breakpoints, responsiveScales);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
function collectFromFlatStyle(
|
|
65
70
|
style: StyleObject,
|
|
66
71
|
prefix: string,
|
|
67
|
-
classes: Set<string
|
|
72
|
+
classes: Set<string>,
|
|
73
|
+
breakpoints: BreakpointConfig,
|
|
74
|
+
responsiveScales?: ResponsiveScales,
|
|
75
|
+
sourceBreakpoint?: string
|
|
68
76
|
): void {
|
|
77
|
+
// Only auto-scale when mapping value lives in the base branch (or a flat
|
|
78
|
+
// top-level style). Mappings inside an explicit breakpoint branch already
|
|
79
|
+
// describe the override the author wants — don't re-scale them.
|
|
80
|
+
const shouldAutoScale =
|
|
81
|
+
responsiveScales?.enabled === true && sourceBreakpoint === undefined;
|
|
82
|
+
|
|
83
|
+
const scaleBreakpoints = shouldAutoScale
|
|
84
|
+
? Object.entries(breakpoints)
|
|
85
|
+
.map(([name, cfg]) => ({ name, value: cfg?.breakpoint }))
|
|
86
|
+
.filter((bp): bp is { name: string; value: number } =>
|
|
87
|
+
typeof bp.value === 'number' && bp.value > 0
|
|
88
|
+
)
|
|
89
|
+
.sort((a, b) => b.value - a.value)
|
|
90
|
+
: [];
|
|
91
|
+
|
|
92
|
+
const baseRef = responsiveScales?.baseReference ?? 16;
|
|
93
|
+
|
|
69
94
|
for (const [property, value] of Object.entries(style)) {
|
|
70
95
|
if (!isStyleMapping(value)) continue;
|
|
71
96
|
|
|
@@ -75,6 +100,26 @@ function collectFromFlatStyle(
|
|
|
75
100
|
if (twClass) {
|
|
76
101
|
classes.add(prefix ? `${prefix}${twClass}` : twClass);
|
|
77
102
|
}
|
|
103
|
+
|
|
104
|
+
// Safelist auto-scaled `max-[Npx]:` variants so Tailwind generates
|
|
105
|
+
// CSS for each breakpoint-scaled class the runtime might produce.
|
|
106
|
+
if (shouldAutoScale && responsiveScales) {
|
|
107
|
+
const strValue = String(cssValue);
|
|
108
|
+
if (strValue === '') continue;
|
|
109
|
+
for (const { name: bpName, value: bpPixels } of scaleBreakpoints) {
|
|
110
|
+
const scale = getScaleMultiplier(
|
|
111
|
+
responsiveScales,
|
|
112
|
+
property as CSSPropertyType,
|
|
113
|
+
bpName
|
|
114
|
+
);
|
|
115
|
+
if (scale == null) continue;
|
|
116
|
+
const scaledValue = scalePropertyValue(strValue, baseRef, scale);
|
|
117
|
+
if (scaledValue == null || scaledValue === strValue) continue;
|
|
118
|
+
const scaledClass = propertyToTailwind(property, scaledValue);
|
|
119
|
+
if (!scaledClass) continue;
|
|
120
|
+
classes.add(`max-[${bpPixels}px]:${scaledClass}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
78
123
|
}
|
|
79
124
|
}
|
|
80
125
|
}
|
|
@@ -85,27 +130,28 @@ function collectFromFlatStyle(
|
|
|
85
130
|
function walkNode(
|
|
86
131
|
node: ComponentNode | ComponentNode[] | string | number | null | undefined,
|
|
87
132
|
classes: Set<string>,
|
|
88
|
-
breakpoints: BreakpointConfig
|
|
133
|
+
breakpoints: BreakpointConfig,
|
|
134
|
+
responsiveScales?: ResponsiveScales
|
|
89
135
|
): void {
|
|
90
136
|
if (!node || typeof node === 'string' || typeof node === 'number') return;
|
|
91
137
|
|
|
92
138
|
if (Array.isArray(node)) {
|
|
93
139
|
for (const child of node) {
|
|
94
|
-
walkNode(child, classes, breakpoints);
|
|
140
|
+
walkNode(child, classes, breakpoints, responsiveScales);
|
|
95
141
|
}
|
|
96
142
|
return;
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
// Collect from style
|
|
100
146
|
if ('style' in node && node.style) {
|
|
101
|
-
collectFromStyle(node.style as StyleObject | ResponsiveStyleObject, classes, breakpoints);
|
|
147
|
+
collectFromStyle(node.style as StyleObject | ResponsiveStyleObject, classes, breakpoints, responsiveScales);
|
|
102
148
|
}
|
|
103
149
|
|
|
104
150
|
// Collect from interactive styles
|
|
105
151
|
if ('interactiveStyles' in node && Array.isArray((node as any).interactiveStyles)) {
|
|
106
152
|
for (const rule of (node as any).interactiveStyles) {
|
|
107
153
|
if (rule.style) {
|
|
108
|
-
collectFromStyle(rule.style, classes, breakpoints);
|
|
154
|
+
collectFromStyle(rule.style, classes, breakpoints, responsiveScales);
|
|
109
155
|
}
|
|
110
156
|
}
|
|
111
157
|
}
|
|
@@ -114,7 +160,7 @@ function walkNode(
|
|
|
114
160
|
if ('children' in node && node.children) {
|
|
115
161
|
if (Array.isArray(node.children)) {
|
|
116
162
|
for (const child of node.children) {
|
|
117
|
-
walkNode(child as ComponentNode, classes, breakpoints);
|
|
163
|
+
walkNode(child as ComponentNode, classes, breakpoints, responsiveScales);
|
|
118
164
|
}
|
|
119
165
|
}
|
|
120
166
|
}
|
|
@@ -132,14 +178,15 @@ function walkNode(
|
|
|
132
178
|
*/
|
|
133
179
|
export function collectAllMappingClasses(
|
|
134
180
|
componentDefs: Record<string, ComponentDefinition>,
|
|
135
|
-
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS
|
|
181
|
+
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS,
|
|
182
|
+
responsiveScales?: ResponsiveScales
|
|
136
183
|
): Set<string> {
|
|
137
184
|
const classes = new Set<string>();
|
|
138
185
|
|
|
139
186
|
for (const def of Object.values(componentDefs)) {
|
|
140
187
|
const structure = def.component?.structure;
|
|
141
188
|
if (structure) {
|
|
142
|
-
walkNode(structure, classes, breakpoints);
|
|
189
|
+
walkNode(structure, classes, breakpoints, responsiveScales);
|
|
143
190
|
}
|
|
144
191
|
}
|
|
145
192
|
|