holygrail5 1.0.2

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/src/parser.js ADDED
@@ -0,0 +1,624 @@
1
+ // Parseador JSON a CSS
2
+ // Convierte la configuración JSON en CSS con variables compartidas y clases responsive
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { toKebabCase, pxToRem, getFontFamilyName } = require('./utils');
7
+ const { BREAKPOINTS } = require('./config');
8
+
9
+ // Lista de propiedades de tipografía que se procesan
10
+ const PROPERTIES = ['fontFamily', 'fontWeight', 'fontSize', 'lineHeight', 'letterSpacing', 'textTransform'];
11
+
12
+ // Genera el nombre de variable CSS para una fuente
13
+ // Usa el nombre de la fuente (como "primary" o "secondary") para crear la variable
14
+ function getFontFamilyVarName(fontFamilyName, prefix, category) {
15
+ return `--${prefix}-${category}-font-family-${fontFamilyName}`;
16
+ }
17
+
18
+ // Genera el nombre de variable CSS para line-height
19
+ // Convierte el valor numérico a un formato válido para nombres de variables
20
+ function getLineHeightVarName(lineHeightValue, prefix, category) {
21
+ return `--${prefix}-${category}-line-height-${lineHeightValue.toString().replace('.', '-')}`;
22
+ }
23
+
24
+ // Genera el nombre de variable CSS compartida para cualquier propiedad
25
+ // Convierte el nombre de la propiedad a kebab-case y el valor a un formato válido
26
+ // Para fontSize y letterSpacing, limpia las unidades (px, rem) y puntos
27
+ function getSharedVarName(prop, value, prefix, category) {
28
+ const propName = toKebabCase(prop);
29
+ let name = value.toString();
30
+
31
+ if (prop === 'fontSize') {
32
+ name = name.endsWith('px') ? name.replace('px', '').replace('.', '-') : name.replace(/rem/g, '').replace('.', '-');
33
+ } else if (prop === 'letterSpacing') {
34
+ name = name.replace(/rem/g, '').replace('.', '-');
35
+ }
36
+
37
+ return `--${prefix}-${category}-${propName}-${name}`;
38
+ }
39
+
40
+ // Carga las variables CSS históricas guardadas previamente
41
+ // Esto asegura que las variables nunca se eliminen aunque se borren las clases que las usaban
42
+ function loadHistoricalVariables(historicalVarsPath) {
43
+ try {
44
+ if (fs.existsSync(historicalVarsPath)) {
45
+ const content = fs.readFileSync(historicalVarsPath, 'utf8');
46
+ return JSON.parse(content);
47
+ }
48
+ } catch (error) {
49
+ // Si no existe o hay error, devuelve objeto vacío
50
+ }
51
+ return {
52
+ fontFamilyVars: {},
53
+ lineHeightVars: {},
54
+ fontWeightVars: {},
55
+ letterSpacingVars: {},
56
+ textTransformVars: {},
57
+ fontSizeVars: {}
58
+ };
59
+ }
60
+
61
+ // Guarda las variables CSS actuales para mantenerlas en el futuro
62
+ // Esto asegura que las variables nunca se eliminen aunque se borren las clases
63
+ function saveHistoricalVariables(variables, historicalVarsPath) {
64
+ try {
65
+ const dir = path.dirname(historicalVarsPath);
66
+ if (!fs.existsSync(dir)) {
67
+ fs.mkdirSync(dir, { recursive: true });
68
+ }
69
+
70
+ // Convertir Maps a objetos para guardar en JSON
71
+ const varsToSave = {
72
+ fontFamilyVars: {},
73
+ lineHeightVars: {},
74
+ fontWeightVars: {},
75
+ letterSpacingVars: {},
76
+ textTransformVars: {},
77
+ fontSizeVars: {}
78
+ };
79
+
80
+ // Convertir cada Map a objeto
81
+ variables.fontFamilyVars.forEach((value, key) => {
82
+ varsToSave.fontFamilyVars[key] = value;
83
+ });
84
+ variables.lineHeightVars.forEach((value, key) => {
85
+ varsToSave.lineHeightVars[key] = value;
86
+ });
87
+ variables.fontWeightVars.forEach((value, key) => {
88
+ varsToSave.fontWeightVars[key] = value;
89
+ });
90
+ variables.letterSpacingVars.forEach((value, key) => {
91
+ varsToSave.letterSpacingVars[key] = value;
92
+ });
93
+ variables.textTransformVars.forEach((value, key) => {
94
+ varsToSave.textTransformVars[key] = value;
95
+ });
96
+ variables.fontSizeVars.forEach((value, key) => {
97
+ varsToSave.fontSizeVars[key] = value;
98
+ });
99
+
100
+ fs.writeFileSync(historicalVarsPath, JSON.stringify(varsToSave, null, 2), 'utf8');
101
+ } catch (error) {
102
+ console.warn('⚠️ No se pudo guardar las variables históricas:', error.message);
103
+ }
104
+ }
105
+
106
+ // Construye un mapa de todas las variables CSS compartidas y sus valores
107
+ // Recorre todas las clases y agrupa valores únicos para crear variables compartidas
108
+ // Esto evita duplicar variables cuando varias clases usan el mismo valor
109
+ // También carga variables históricas para que nunca se eliminen
110
+ function buildValueMap(classes, fontFamilyMap, prefix, category, historicalVarsPath = null) {
111
+ // Cargar variables históricas si existe el archivo
112
+ const historicalVarsPathDefault = historicalVarsPath || path.join(__dirname, '..', '.data', '.historical-variables.json');
113
+ const historicalVars = loadHistoricalVariables(historicalVarsPathDefault);
114
+
115
+ // Inicializar Maps con variables históricas
116
+ const fontFamilyVars = new Map(Object.entries(historicalVars.fontFamilyVars || {}));
117
+ const lineHeightVars = new Map(Object.entries(historicalVars.lineHeightVars || {}));
118
+ const fontWeightVars = new Map(Object.entries(historicalVars.fontWeightVars || {}));
119
+ const letterSpacingVars = new Map(Object.entries(historicalVars.letterSpacingVars || {}));
120
+ const textTransformVars = new Map(Object.entries(historicalVars.textTransformVars || {}));
121
+ const fontSizeVars = new Map(Object.entries(historicalVars.fontSizeVars || {}));
122
+
123
+ const valueMap = {};
124
+
125
+ Object.entries(classes).forEach(([className, cls]) => {
126
+ valueMap[className] = {};
127
+
128
+ // Procesa la fuente de la clase
129
+ // Si la fuente ya existe en el mapa, reutiliza la variable
130
+ if (cls.fontFamily !== undefined) {
131
+ const fontFamilyName = getFontFamilyName(cls.fontFamily, fontFamilyMap);
132
+ const varName = getFontFamilyVarName(fontFamilyName, prefix, category);
133
+ if (!fontFamilyVars.has(cls.fontFamily)) {
134
+ fontFamilyVars.set(cls.fontFamily, { varName, value: cls.fontFamily, name: fontFamilyName });
135
+ }
136
+ valueMap[className].fontFamily = {
137
+ varName: fontFamilyVars.get(cls.fontFamily).varName,
138
+ value: cls.fontFamily
139
+ };
140
+ }
141
+
142
+ // Procesa propiedades base que se comparten entre breakpoints
143
+ // Estas propiedades no cambian entre mobile y desktop
144
+ ['fontWeight', 'letterSpacing', 'textTransform'].forEach(prop => {
145
+ if (cls[prop] !== undefined) {
146
+ const varName = getSharedVarName(prop, cls[prop], prefix, category);
147
+ const varMap = prop === 'fontWeight' ? fontWeightVars : prop === 'letterSpacing' ? letterSpacingVars : textTransformVars;
148
+
149
+ // Solo crea la variable si no existe ya
150
+ if (!varMap.has(cls[prop])) {
151
+ varMap.set(cls[prop], { varName, value: cls[prop] });
152
+ }
153
+ valueMap[className][prop] = {
154
+ varName: varMap.get(cls[prop]).varName,
155
+ value: cls[prop]
156
+ };
157
+ }
158
+ });
159
+
160
+ // Procesa propiedades que cambian según el breakpoint
161
+ // fontSize y lineHeight pueden ser diferentes en mobile y desktop
162
+ BREAKPOINTS.forEach(bp => {
163
+ if (cls[bp]) {
164
+ if (cls[bp].fontSize !== undefined) {
165
+ const fontSizeValue = cls[bp].fontSize;
166
+ const fontSizeRem = pxToRem(fontSizeValue);
167
+ const varName = getSharedVarName('fontSize', fontSizeValue, prefix, category);
168
+
169
+ // Solo crea la variable si no existe ya
170
+ if (!fontSizeVars.has(fontSizeValue)) {
171
+ fontSizeVars.set(fontSizeValue, { varName, value: fontSizeRem });
172
+ }
173
+ if (!valueMap[className].fontSize) valueMap[className].fontSize = {};
174
+ valueMap[className].fontSize[bp] = {
175
+ varName: fontSizeVars.get(fontSizeValue).varName,
176
+ value: fontSizeRem
177
+ };
178
+ }
179
+
180
+ if (cls[bp].lineHeight !== undefined) {
181
+ const lineHeightValue = cls[bp].lineHeight;
182
+ const varName = getLineHeightVarName(lineHeightValue, prefix, category);
183
+
184
+ // Solo crea la variable si no existe ya
185
+ if (!lineHeightVars.has(lineHeightValue)) {
186
+ lineHeightVars.set(lineHeightValue, { varName, value: lineHeightValue });
187
+ }
188
+ if (!valueMap[className].lineHeight) valueMap[className].lineHeight = {};
189
+ valueMap[className].lineHeight[bp] = {
190
+ varName: lineHeightVars.get(lineHeightValue).varName,
191
+ value: lineHeightValue
192
+ };
193
+ }
194
+ }
195
+ });
196
+ });
197
+
198
+ // Guardar las variables actuales (incluyendo las históricas) para la próxima vez
199
+ const allVariables = {
200
+ fontFamilyVars,
201
+ lineHeightVars,
202
+ fontWeightVars,
203
+ letterSpacingVars,
204
+ textTransformVars,
205
+ fontSizeVars
206
+ };
207
+ saveHistoricalVariables(allVariables, historicalVarsPathDefault);
208
+
209
+ return { valueMap, fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars };
210
+ }
211
+
212
+ // Genera variables CSS para spacing (padding y margin)
213
+ // Crea variables como --hg-spacing-4, --hg-spacing-8, etc.
214
+ function generateSpacingVariables(spacingMap, prefix, baseFontSize = 16) {
215
+ if (!spacingMap || typeof spacingMap !== 'object') {
216
+ return [];
217
+ }
218
+
219
+ const variables = [];
220
+
221
+ Object.entries(spacingMap).forEach(([key, value]) => {
222
+ // Si el valor termina en %, no lo convierte a rem
223
+ const finalValue = value.endsWith('%') ? value : pxToRem(value, baseFontSize);
224
+ const varName = `--${prefix}-spacing-${key}`;
225
+ variables.push({ varName, value: finalValue, key });
226
+ });
227
+
228
+ return variables;
229
+ }
230
+
231
+ // Genera variables CSS para colores
232
+ // Crea variables como --hg-color-white, --hg-color-black, etc.
233
+ function generateColorVariables(colorsMap, prefix) {
234
+ if (!colorsMap || typeof colorsMap !== 'object') {
235
+ return [];
236
+ }
237
+
238
+ const variables = [];
239
+
240
+ Object.entries(colorsMap).forEach(([key, value]) => {
241
+ const varName = `--${prefix}-color-${key}`;
242
+ variables.push({ varName, value, key });
243
+ });
244
+
245
+ return variables;
246
+ }
247
+
248
+ // Genera todas las variables CSS en el bloque :root
249
+ // Recorre todos los mapas de variables y las convierte en declaraciones CSS
250
+ function generateRootVariables(fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars, spacingVars = [], colorVars = []) {
251
+ const variables = [];
252
+ const allMaps = [
253
+ { map: fontFamilyVars, name: 'font-family' },
254
+ { map: lineHeightVars, name: 'line-height' },
255
+ { map: fontWeightVars, name: 'font-weight' },
256
+ { map: letterSpacingVars, name: 'letter-spacing' },
257
+ { map: textTransformVars, name: 'text-transform' },
258
+ { map: fontSizeVars, name: 'font-size' }
259
+ ];
260
+
261
+ allMaps.forEach(({ map }) => {
262
+ Array.from(map.values()).forEach(item => {
263
+ variables.push(` ${item.varName}: ${item.value};`);
264
+ });
265
+ });
266
+
267
+ // Agregar variables de spacing
268
+ spacingVars.forEach(item => {
269
+ variables.push(` ${item.varName}: ${item.value};`);
270
+ });
271
+
272
+ // Agregar variables de colores
273
+ colorVars.forEach(item => {
274
+ variables.push(` ${item.varName}: ${item.value};`);
275
+ });
276
+
277
+ return variables.join('\n');
278
+ }
279
+
280
+ // Obtiene las propiedades finales de una clase para un breakpoint específico
281
+ // Combina las propiedades base (que no cambian) con las del breakpoint (que sí cambian)
282
+ function getFinalProps(cls, breakpoint, baseFontSize) {
283
+ const props = {};
284
+
285
+ // Propiedades base que se aplican a todos los breakpoints
286
+ ['fontFamily', 'fontWeight', 'letterSpacing', 'textTransform'].forEach(prop => {
287
+ if (cls[prop] !== undefined) props[prop] = cls[prop];
288
+ });
289
+
290
+ // Propiedades específicas del breakpoint (fontSize y lineHeight)
291
+ if (cls[breakpoint]) {
292
+ if (cls[breakpoint].fontSize !== undefined) props.fontSize = pxToRem(cls[breakpoint].fontSize, baseFontSize);
293
+ if (cls[breakpoint].lineHeight !== undefined) props.lineHeight = cls[breakpoint].lineHeight;
294
+ }
295
+
296
+ return props;
297
+ }
298
+
299
+ // Genera el CSS para una clase específica en un breakpoint
300
+ // Crea las propiedades CSS usando las variables compartidas
301
+ function generateClassCSS(className, cls, breakpointName, valueMap, prefix, category, baseFontSize, fontFamilyMap) {
302
+ const props = getFinalProps(cls, breakpointName, baseFontSize);
303
+ const cssProps = [];
304
+
305
+ PROPERTIES.forEach(prop => {
306
+ if (props[prop] === undefined) return;
307
+
308
+ // Obtiene el nombre de la variable CSS según el tipo de propiedad
309
+ let varName;
310
+ if (prop === 'fontFamily') {
311
+ const fontFamilyName = getFontFamilyName(props[prop], fontFamilyMap);
312
+ varName = getFontFamilyVarName(fontFamilyName, prefix, category);
313
+ } else if (prop === 'lineHeight') {
314
+ varName = getLineHeightVarName(props[prop], prefix, category);
315
+ } else if (prop === 'fontSize') {
316
+ // Para fontSize, usa el valor original en px para generar el nombre de variable
317
+ const originalValue = cls[breakpointName]?.fontSize ?? cls.fontSize;
318
+ varName = getSharedVarName(prop, originalValue, prefix, category);
319
+ } else {
320
+ varName = getSharedVarName(prop, props[prop], prefix, category);
321
+ }
322
+
323
+ cssProps.push(` ${toKebabCase(prop)}: var(${varName});`);
324
+ });
325
+
326
+ return cssProps.length ? ` .${className} {\n${cssProps.join('\n')}\n }` : '';
327
+ }
328
+
329
+ // Genera una media query con todas las clases para un breakpoint
330
+ // Convierte el breakpoint a rem y genera el CSS de todas las clases
331
+ function generateMediaQuery(breakpointName, minWidth, classes, valueMap, prefix, category, baseFontSize, fontFamilyMap) {
332
+ const minWidthRem = pxToRem(minWidth, baseFontSize);
333
+
334
+ const cssClasses = Object.entries(classes)
335
+ .map(([className, cls]) => generateClassCSS(className, cls, breakpointName, valueMap, prefix, category, baseFontSize, fontFamilyMap))
336
+ .filter(Boolean);
337
+
338
+ return `@media (min-width: ${minWidthRem}) {\n\n${cssClasses.join('\n\n')}\n\n}`;
339
+ }
340
+
341
+ // Genera un bloque de tipografías para un breakpoint específico con comentario descriptivo
342
+ // Incluye un comentario que indica qué breakpoint es y el min-width
343
+ function generateTypographyBlock(breakpointName, minWidth, classes, valueMap, prefix, category, baseFontSize, fontFamilyMap) {
344
+ const minWidthRem = pxToRem(minWidth, baseFontSize);
345
+ const breakpointLabel = breakpointName === 'mobile' ? 'Mobile' : 'Desktop';
346
+
347
+ const cssClasses = Object.entries(classes)
348
+ .map(([className, cls]) => generateClassCSS(className, cls, breakpointName, valueMap, prefix, category, baseFontSize, fontFamilyMap))
349
+ .filter(Boolean);
350
+
351
+ return `/* Tipografías - ${breakpointLabel} (min-width: ${minWidthRem}) */
352
+ @media (min-width: ${minWidthRem}) {
353
+
354
+ ${cssClasses.join('\n\n')}
355
+
356
+ }`;
357
+ }
358
+
359
+ // Genera el reset CSS mínimo necesario para que todo funcione correctamente
360
+ // Establece el tamaño de fuente base del HTML para que los rem funcionen
361
+ function generateResetCSS(baseFontSize = 16) {
362
+ return `/* Reset CSS Mínimo */
363
+ html {
364
+ box-sizing: border-box;
365
+ font-size: ${baseFontSize}px;
366
+ }
367
+
368
+ @media (prefers-reduced-motion: no-preference) {
369
+ html {
370
+ interpolate-size: allow-keywords;
371
+ }
372
+ }
373
+
374
+ *,
375
+ *:before,
376
+ *:after {
377
+ box-sizing: inherit;
378
+ -webkit-text-size-adjust: 100%;
379
+ -moz-tab-size: 4;
380
+ tab-size: 4;
381
+ }
382
+
383
+ * {
384
+ margin: 0;
385
+ }
386
+
387
+ html,
388
+ body {
389
+ margin: 0;
390
+ padding: 0;
391
+ }
392
+
393
+ body {
394
+ line-height: calc(1em + 0.5rem);
395
+ -webkit-font-smoothing: antialiased;
396
+ min-height: 100vh;
397
+ }
398
+
399
+ ol,
400
+ ul,
401
+ dl {
402
+ list-style: none;
403
+ padding-inline-start: unset;
404
+ }
405
+
406
+ img, picture, video, canvas, svg {
407
+ display: inline-block;
408
+ max-width: 100%;
409
+ }
410
+
411
+ input, button, textarea, select {
412
+ font: inherit;
413
+ }
414
+
415
+ p, h1, h2, h3, h4, h5, h6 {
416
+ overflow-wrap: break-word;
417
+ }
418
+ `;
419
+ }
420
+
421
+ // Genera helpers de padding y margin basados en el spacingMap del config.json
422
+ // Crea clases como p-4, pr-4, pl-4, pb-4, pt-4 para padding
423
+ // y m-4, mr-4, ml-4, mb-4, mt-4 para margin
424
+ // También genera versiones con prefijo md: para el breakpoint desktop
425
+ // Usa las variables CSS definidas en :root
426
+ function generateSpacingHelpers(spacingMap, prefix, desktopBreakpoint, baseFontSize = 16, spacingImportant = []) {
427
+ if (!spacingMap || typeof spacingMap !== 'object') {
428
+ return '';
429
+ }
430
+
431
+ const helpers = [];
432
+ const desktopHelpers = [];
433
+ const desktopBreakpointRem = pxToRem(desktopBreakpoint, baseFontSize);
434
+
435
+ // Generar helpers para cada valor en spacingMap
436
+ Object.entries(spacingMap).forEach(([key, value]) => {
437
+ const varName = `--${prefix}-spacing-${key}`;
438
+
439
+ // Padding helpers base (mobile) usando variables CSS con propiedades lógicas para RTL
440
+ helpers.push(` .p-${key} { padding: var(${varName}); }`);
441
+ helpers.push(` .pr-${key} { padding-inline-end: var(${varName}); }`);
442
+ helpers.push(` .pl-${key} { padding-inline-start: var(${varName}); }`);
443
+ helpers.push(` .pb-${key} { padding-bottom: var(${varName}); }`);
444
+ helpers.push(` .pt-${key} { padding-top: var(${varName}); }`);
445
+
446
+ // Margin helpers base (mobile) usando variables CSS con propiedades lógicas para RTL
447
+ helpers.push(` .m-${key} { margin: var(${varName}); }`);
448
+ helpers.push(` .mr-${key} { margin-inline-end: var(${varName}); }`);
449
+ helpers.push(` .ml-${key} { margin-inline-start: var(${varName}); }`);
450
+ helpers.push(` .mb-${key} { margin-bottom: var(${varName}); }`);
451
+ helpers.push(` .mt-${key} { margin-top: var(${varName}); }`);
452
+
453
+ // Padding helpers con prefijo md: (desktop) usando variables CSS con propiedades lógicas para RTL
454
+ desktopHelpers.push(` .md\\:p-${key} { padding: var(${varName}); }`);
455
+ desktopHelpers.push(` .md\\:pr-${key} { padding-inline-end: var(${varName}); }`);
456
+ desktopHelpers.push(` .md\\:pl-${key} { padding-inline-start: var(${varName}); }`);
457
+ desktopHelpers.push(` .md\\:pb-${key} { padding-bottom: var(${varName}); }`);
458
+ desktopHelpers.push(` .md\\:pt-${key} { padding-top: var(${varName}); }`);
459
+
460
+ // Margin helpers con prefijo md: (desktop) usando variables CSS con propiedades lógicas para RTL
461
+ desktopHelpers.push(` .md\\:m-${key} { margin: var(${varName}); }`);
462
+ desktopHelpers.push(` .md\\:mr-${key} { margin-inline-end: var(${varName}); }`);
463
+ desktopHelpers.push(` .md\\:ml-${key} { margin-inline-start: var(${varName}); }`);
464
+ desktopHelpers.push(` .md\\:mb-${key} { margin-bottom: var(${varName}); }`);
465
+ desktopHelpers.push(` .md\\:mt-${key} { margin-top: var(${varName}); }`);
466
+ });
467
+
468
+ // Generar helpers con !important para los valores especificados en spacingImportant
469
+ if (spacingImportant && Array.isArray(spacingImportant)) {
470
+ spacingImportant.forEach(key => {
471
+ if (spacingMap[key]) {
472
+ const varName = `--${prefix}-spacing-${key}`;
473
+
474
+ // Padding helpers con !important (mobile)
475
+ helpers.push(` .p-${key}\\! { padding: var(${varName}) !important; }`);
476
+ helpers.push(` .pr-${key}\\! { padding-inline-end: var(${varName}) !important; }`);
477
+ helpers.push(` .pl-${key}\\! { padding-inline-start: var(${varName}) !important; }`);
478
+ helpers.push(` .pb-${key}\\! { padding-bottom: var(${varName}) !important; }`);
479
+ helpers.push(` .pt-${key}\\! { padding-top: var(${varName}) !important; }`);
480
+
481
+ // Margin helpers con !important (mobile)
482
+ helpers.push(` .m-${key}\\! { margin: var(${varName}) !important; }`);
483
+ helpers.push(` .mr-${key}\\! { margin-inline-end: var(${varName}) !important; }`);
484
+ helpers.push(` .ml-${key}\\! { margin-inline-start: var(${varName}) !important; }`);
485
+ helpers.push(` .mb-${key}\\! { margin-bottom: var(${varName}) !important; }`);
486
+ helpers.push(` .mt-${key}\\! { margin-top: var(${varName}) !important; }`);
487
+
488
+ // Padding helpers con !important y prefijo md: (desktop)
489
+ desktopHelpers.push(` .md\\:p-${key}\\! { padding: var(${varName}) !important; }`);
490
+ desktopHelpers.push(` .md\\:pr-${key}\\! { padding-inline-end: var(${varName}) !important; }`);
491
+ desktopHelpers.push(` .md\\:pl-${key}\\! { padding-inline-start: var(${varName}) !important; }`);
492
+ desktopHelpers.push(` .md\\:pb-${key}\\! { padding-bottom: var(${varName}) !important; }`);
493
+ desktopHelpers.push(` .md\\:pt-${key}\\! { padding-top: var(${varName}) !important; }`);
494
+
495
+ // Margin helpers con !important y prefijo md: (desktop)
496
+ desktopHelpers.push(` .md\\:m-${key}\\! { margin: var(${varName}) !important; }`);
497
+ desktopHelpers.push(` .md\\:mr-${key}\\! { margin-inline-end: var(${varName}) !important; }`);
498
+ desktopHelpers.push(` .md\\:ml-${key}\\! { margin-inline-start: var(${varName}) !important; }`);
499
+ desktopHelpers.push(` .md\\:mb-${key}\\! { margin-bottom: var(${varName}) !important; }`);
500
+ desktopHelpers.push(` .md\\:mt-${key}\\! { margin-top: var(${varName}) !important; }`);
501
+ }
502
+ });
503
+ }
504
+
505
+ if (helpers.length === 0) {
506
+ return '';
507
+ }
508
+
509
+ // Generar bloque base (mobile) y bloque con media query (desktop)
510
+ const baseHelpers = `/* Helpers de Spacing (Padding y Margin) - Mobile */
511
+ ${helpers.join('\n')}
512
+
513
+ /* Helpers de Spacing (Padding y Margin) - Desktop (md:) */
514
+ @media (min-width: ${desktopBreakpointRem}) {
515
+ ${desktopHelpers.join('\n')}
516
+ }
517
+
518
+ `;
519
+
520
+ return baseHelpers;
521
+ }
522
+
523
+ function generateLayoutHelpers(helpers, spacingMap, prefix, desktopBreakpoint, baseFontSize = 16) {
524
+ if (!helpers || typeof helpers !== 'object') {
525
+ return '';
526
+ }
527
+
528
+ let css = '\n/* Layout Helpers */\n';
529
+
530
+ Object.entries(helpers).forEach(([helperName, config]) => {
531
+ const { property, class: className, responsive, values, useSpacing } = config;
532
+
533
+ if (useSpacing && spacingMap) {
534
+ Object.entries(spacingMap).forEach(([key, value]) => {
535
+ const finalValue = value.endsWith('%') ? value : pxToRem(value, baseFontSize);
536
+ css += `.${prefix}-${className}-${key} { ${property}: ${finalValue}; }\n`;
537
+ });
538
+
539
+ if (responsive) {
540
+ css += `\n@media (min-width: ${desktopBreakpoint}) {\n`;
541
+ Object.entries(spacingMap).forEach(([key, value]) => {
542
+ const finalValue = value.endsWith('%') ? value : pxToRem(value, baseFontSize);
543
+ css += ` .md\\:${prefix}-${className}-${key} { ${property}: ${finalValue}; }\n`;
544
+ });
545
+ css += '}\n';
546
+ }
547
+ } else if (values) {
548
+ if (Array.isArray(values)) {
549
+ values.forEach(value => {
550
+ css += `.${prefix}-${className}-${value} { ${property}: ${value}; }\n`;
551
+ });
552
+
553
+ if (responsive) {
554
+ css += `\n@media (min-width: ${desktopBreakpoint}) {\n`;
555
+ values.forEach(value => {
556
+ css += ` .md\\:${prefix}-${className}-${value} { ${property}: ${value}; }\n`;
557
+ });
558
+ css += '}\n';
559
+ }
560
+ } else if (typeof values === 'object') {
561
+ Object.entries(values).forEach(([key, value]) => {
562
+ css += `.${prefix}-${className}-${key} { ${property}: ${value}; }\n`;
563
+ });
564
+
565
+ if (responsive) {
566
+ css += `\n@media (min-width: ${desktopBreakpoint}) {\n`;
567
+ Object.entries(values).forEach(([key, value]) => {
568
+ css += ` .md\\:${prefix}-${className}-${key} { ${property}: ${value}; }\n`;
569
+ });
570
+ css += '}\n';
571
+ }
572
+ }
573
+ }
574
+
575
+ css += '\n';
576
+ });
577
+
578
+ return css;
579
+ }
580
+
581
+ // Función principal que genera todo el CSS desde la configuración JSON
582
+ // Organiza el CSS en bloques separados: reset, variables, tipografías mobile y desktop
583
+ function generateCSS(configData) {
584
+ const prefix = configData.prefix || 'hg';
585
+ const category = configData.category || 'typo';
586
+ const baseFontSize = configData.baseFontSize || 16;
587
+
588
+ // Construye el mapa de variables compartidas
589
+ const { valueMap, fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars } =
590
+ buildValueMap(configData.classes, configData.fontFamilyMap, prefix, category);
591
+
592
+ // Genera variables de spacing
593
+ const spacingVars = generateSpacingVariables(configData.spacingMap, prefix, baseFontSize);
594
+
595
+ // Genera variables de colores
596
+ const colorVars = generateColorVariables(configData.colors, prefix);
597
+
598
+ // Genera cada bloque del CSS
599
+ const resetCSS = generateResetCSS(baseFontSize);
600
+ const rootVars = generateRootVariables(fontFamilyVars, lineHeightVars, fontWeightVars, letterSpacingVars, textTransformVars, fontSizeVars, spacingVars, colorVars);
601
+ const spacingHelpers = generateSpacingHelpers(configData.spacingMap, prefix, configData.breakpoints.desktop, baseFontSize, configData.spacingImportant);
602
+ const layoutHelpers = configData.helpers ? generateLayoutHelpers(configData.helpers, configData.spacingMap, prefix, configData.breakpoints.desktop, baseFontSize) : '';
603
+ const mobileTypography = generateTypographyBlock('mobile', configData.breakpoints.mobile, configData.classes, valueMap, prefix, category, baseFontSize, configData.fontFamilyMap);
604
+ const desktopTypography = generateTypographyBlock('desktop', configData.breakpoints.desktop, configData.classes, valueMap, prefix, category, baseFontSize, configData.fontFamilyMap);
605
+
606
+ // Combina todos los bloques en el orden correcto
607
+ return `${resetCSS}/* Variables CSS Compartidas */
608
+ :root {
609
+ ${rootVars}
610
+ }
611
+
612
+ ${spacingHelpers}${layoutHelpers}${mobileTypography}
613
+
614
+ ${desktopTypography}`;
615
+ }
616
+
617
+ module.exports = {
618
+ generateCSS,
619
+ buildValueMap,
620
+ generateResetCSS,
621
+ generateSpacingVariables,
622
+ generateColorVariables
623
+ };
624
+
package/src/utils.js ADDED
@@ -0,0 +1,74 @@
1
+ // Utilidades compartidas
2
+ // Funciones auxiliares utilizadas por el parseador y el generador de guía
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ // Convierte nombres de propiedades de JavaScript (camelCase) a formato CSS (kebab-case)
8
+ // Por ejemplo, "fontSize" se convierte en "font-size" para usarlo en CSS
9
+ function toKebabCase(str) {
10
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase();
11
+ }
12
+
13
+ // Convierte valores en píxeles a unidades rem para hacer el CSS responsive
14
+
15
+ function pxToRem(value, baseFontSize = 16) {
16
+ if (typeof value === 'string' && value.endsWith('px')) {
17
+ const pxValue = parseFloat(value);
18
+ const remValue = pxValue / baseFontSize;
19
+ return `${parseFloat(remValue.toFixed(4))}rem`;
20
+ }
21
+ return value;
22
+ }
23
+
24
+ // Convierte valores rem de vuelta a píxeles para mostrarlos en la guía HTML
25
+ // Esto ayuda a los desarrolladores a entender mejor los valores, ya que
26
+ // muchos están más familiarizados con píxeles que con rem
27
+ function remToPx(remValue, baseFontSize = 16) {
28
+ const remMatch = remValue.toString().match(/^([\d.]+)rem$/);
29
+ return remMatch ? `${parseFloat(remMatch[1]) * baseFontSize}px` : '-';
30
+ }
31
+
32
+ // Busca el nombre de una fuente en el mapa de fuentes configurado
33
+ // Si la fuente está en el mapa (como "primary" o "secondary"), devuelve ese nombre
34
+ // Si no está, genera un nombre automático desde el valor de la fuente
35
+ // Esto se usa para crear variables CSS compartidas con nombres consistentes
36
+ function getFontFamilyName(fontFamilyValue, fontFamilyMap) {
37
+ // Primero busca en el mapa de fuentes si existe
38
+ if (fontFamilyMap) {
39
+ for (const [name, value] of Object.entries(fontFamilyMap)) {
40
+ if (value === fontFamilyValue) return name;
41
+ }
42
+ }
43
+ // Si no está en el mapa, genera un nombre automático limpiando el valor
44
+ // Elimina comillas, espacios y toma solo el primer nombre de la lista de fuentes
45
+ return fontFamilyValue.replace(/["']/g, '').replace(/\s+/g, '-').toLowerCase().split(',')[0].trim();
46
+ }
47
+
48
+ // Escribe un archivo en el sistema de archivos con validación y mensajes de confirmación
49
+ // Crea el directorio si no existe y verifica que el archivo se escribió correctamente
50
+ function writeFile(filePath, content, description) {
51
+ try {
52
+ const dir = path.dirname(filePath);
53
+ if (!fs.existsSync(dir)) {
54
+ fs.mkdirSync(dir, { recursive: true });
55
+ }
56
+ // Forzar escritura del archivo para asegurar que se actualice
57
+ fs.writeFileSync(filePath, content, 'utf8');
58
+ // Verificar que el archivo se escribió correctamente
59
+ const stats = fs.statSync(filePath);
60
+ console.log(`✅ ${description} generado exitosamente en ${filePath} (${stats.size} bytes)`);
61
+ } catch (error) {
62
+ console.error(`❌ Error al escribir ${description} en ${filePath}:`, error.message);
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ module.exports = {
68
+ toKebabCase,
69
+ pxToRem,
70
+ remToPx,
71
+ getFontFamilyName,
72
+ writeFile
73
+ };
74
+