holygrail5 1.0.19 → 1.0.21
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 +88 -18
- package/config.json +205 -77
- package/dist/assets/fonts/suisse-intl-light.woff +0 -0
- package/dist/assets/fonts/suisse-intl-light.woff2 +0 -0
- package/dist/assets/fonts/suisse-intl-medium.woff +0 -0
- package/dist/assets/fonts/suisse-intl-medium.woff2 +0 -0
- package/dist/assets/fonts/suisse-intl-regular.woff +0 -0
- package/dist/assets/fonts/suisse-intl-regular.woff2 +0 -0
- package/dist/assets/fonts/suisse-intl-semibold.woff +0 -0
- package/dist/assets/fonts/suisse-intl-semibold.woff2 +0 -0
- package/dist/assets/fonts/suisse-works-bold.woff +0 -0
- package/dist/assets/fonts/suisse-works-bold.woff2 +0 -0
- package/dist/assets/fonts/suisse-works-medium.woff +0 -0
- package/dist/assets/fonts/suisse-works-medium.woff2 +0 -0
- package/dist/assets/fonts/suisse-works-regular.woff +0 -0
- package/dist/assets/fonts/suisse-works-regular.woff2 +0 -0
- package/dist/componentes.html +429 -0
- package/dist/developer-guide.md +7 -7
- package/dist/guide-styles.css +197 -25
- package/dist/index.html +807 -689
- package/dist/output.css +217 -114
- package/dist/skills.html +14 -8
- package/dist/themes/dutti-demo.html +713 -19
- package/dist/themes/dutti.css +67 -16
- package/dist/themes/limited-demo.html +1121 -0
- package/dist/themes/limited.css +2493 -0
- package/package.json +1 -1
- package/src/.data/.previous-values.json +151 -84
- package/src/assets/fonts/suisse-intl-light.woff +0 -0
- package/src/assets/fonts/suisse-intl-light.woff2 +0 -0
- package/src/assets/fonts/suisse-intl-medium.woff +0 -0
- package/src/assets/fonts/suisse-intl-medium.woff2 +0 -0
- package/src/assets/fonts/suisse-intl-regular.woff +0 -0
- package/src/assets/fonts/suisse-intl-regular.woff2 +0 -0
- package/src/assets/fonts/suisse-intl-semibold.woff +0 -0
- package/src/assets/fonts/suisse-intl-semibold.woff2 +0 -0
- package/src/assets/fonts/suisse-works-bold.woff +0 -0
- package/src/assets/fonts/suisse-works-bold.woff2 +0 -0
- package/src/assets/fonts/suisse-works-medium.woff +0 -0
- package/src/assets/fonts/suisse-works-medium.woff2 +0 -0
- package/src/assets/fonts/suisse-works-regular.woff +0 -0
- package/src/assets/fonts/suisse-works-regular.woff2 +0 -0
- package/src/build/asset-manager.js +94 -3
- package/src/build/build-orchestrator.js +74 -12
- package/src/build/components-generator.js +584 -0
- package/src/build/skills-generator.js +43 -5
- package/src/build/theme-config-loader.js +58 -0
- package/src/build/theme-transformer.js +109 -16
- package/src/build/theme-vars-table-generator.js +349 -0
- package/src/build/typo-table-generator.js +154 -0
- package/src/docs-generator/guide-styles.css +197 -24
- package/src/docs-generator/html-generator.js +92 -246
- package/src/docs-generator/sections/colors-section.js +109 -0
- package/src/docs-generator/value-tracker.js +154 -0
- package/src/generators/typo-generator.js +2 -1
- package/src/generators/utils.js +90 -1
- package/src/skills.html +1 -0
- package/src/watch-config.js +99 -32
- package/themes/{dutti → _base}/_buttons.css +2 -2
- package/themes/{dutti → _base}/_checkboxes.css +1 -1
- package/themes/{dutti → _base}/_forms.css +1 -1
- package/themes/{dutti → _base}/_inputs.css +55 -10
- package/themes/{dutti → _base}/_labels.css +1 -1
- package/themes/dutti/README.md +67 -21
- package/themes/dutti/_variables.css +7 -1
- package/themes/dutti/demo.html +18 -14
- package/themes/dutti/theme.css +22 -44
- package/themes/dutti/theme.json +86 -0
- package/themes/limited/_variables.css +123 -0
- package/themes/limited/demo.html +296 -0
- package/themes/limited/theme.css +32 -0
- package/themes/limited/theme.json +105 -0
- /package/themes/{dutti → _base}/_containers.css +0 -0
- /package/themes/{dutti → _base}/_radios.css +0 -0
- /package/themes/{dutti → _base}/_switches.css +0 -0
- /package/themes/{dutti → _base}/components/_icons.css +0 -0
- /package/themes/{dutti → _base}/objects/_grid.css +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// value-tracker.js
|
|
2
|
+
// Persistencia y diff de valores del design system entre builds.
|
|
3
|
+
//
|
|
4
|
+
// Antes vivía dentro de html-generator.js como ~160 líneas de lógica
|
|
5
|
+
// mezclada con la generación de HTML. Se extrae aquí porque:
|
|
6
|
+
// - No depende del HTML en absoluto: es I/O + comparación.
|
|
7
|
+
// - Se puede testear de forma aislada sin construir toda la guía.
|
|
8
|
+
// - Mantiene html-generator.js enfocado en... generar HTML.
|
|
9
|
+
//
|
|
10
|
+
// Contrato:
|
|
11
|
+
// - `loadPreviousValues(path)` devuelve el JSON previo o null.
|
|
12
|
+
// - `saveCurrentValues(values, path)` persiste el estado actual.
|
|
13
|
+
// - `getChangedValues(current, previous)` devuelve un Set con las
|
|
14
|
+
// claves cambiadas, usando el mismo formato que esperaba el
|
|
15
|
+
// callsite original ("className.prop", "className.bp.prop",
|
|
16
|
+
// "colors.key", "spacingMap.key", "fontFamilyMap.key",
|
|
17
|
+
// "breakpoints.mobile|desktop", "variable.<name>").
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
function loadPreviousValues(previousValuesPath) {
|
|
23
|
+
try {
|
|
24
|
+
if (fs.existsSync(previousValuesPath)) {
|
|
25
|
+
const content = fs.readFileSync(previousValuesPath, 'utf8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
} catch (_) {
|
|
29
|
+
// Si no existe o hay error, devuelve null
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function saveCurrentValues(currentValues, previousValuesPath) {
|
|
35
|
+
try {
|
|
36
|
+
const dir = path.dirname(previousValuesPath);
|
|
37
|
+
if (!fs.existsSync(dir)) {
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
fs.writeFileSync(previousValuesPath, JSON.stringify(currentValues, null, 2), 'utf8');
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.warn('⚠️ No se pudo guardar los valores anteriores:', error.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function diffMap(current, previous, prefix, changes) {
|
|
47
|
+
const cur = current || {};
|
|
48
|
+
const prev = previous || {};
|
|
49
|
+
Object.keys(cur).forEach(key => {
|
|
50
|
+
if (cur[key] !== prev[key]) {
|
|
51
|
+
changes.add(`${prefix}.${key}`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Claves eliminadas: el callsite histórico las marca también.
|
|
55
|
+
Object.keys(prev).forEach(key => {
|
|
56
|
+
if (!(key in cur)) {
|
|
57
|
+
changes.add(`${prefix}.${key}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getChangedValues(currentValues, previousValues) {
|
|
63
|
+
const changes = new Set();
|
|
64
|
+
|
|
65
|
+
// Primera ejecución: sin diff (nada se marca cambiado).
|
|
66
|
+
if (!previousValues) {
|
|
67
|
+
return changes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Breakpoints
|
|
71
|
+
if (currentValues.breakpoints) {
|
|
72
|
+
const curBp = currentValues.breakpoints;
|
|
73
|
+
const prevBp = previousValues.breakpoints;
|
|
74
|
+
if (!prevBp) {
|
|
75
|
+
changes.add('breakpoints.mobile');
|
|
76
|
+
changes.add('breakpoints.desktop');
|
|
77
|
+
} else {
|
|
78
|
+
if (curBp.mobile !== prevBp.mobile) changes.add('breakpoints.mobile');
|
|
79
|
+
if (curBp.desktop !== prevBp.desktop) changes.add('breakpoints.desktop');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mapas planos (fontFamilyMap, spacingMap, colors) comparten el mismo
|
|
84
|
+
// patrón de diff: misma clave con valor distinto → marca.
|
|
85
|
+
if (currentValues.fontFamilyMap) {
|
|
86
|
+
diffMap(currentValues.fontFamilyMap, previousValues.fontFamilyMap, 'fontFamilyMap', changes);
|
|
87
|
+
}
|
|
88
|
+
if (currentValues.spacingMap) {
|
|
89
|
+
diffMap(currentValues.spacingMap, previousValues.spacingMap, 'spacingMap', changes);
|
|
90
|
+
}
|
|
91
|
+
if (currentValues.colors) {
|
|
92
|
+
diffMap(currentValues.colors, previousValues.colors, 'colors', changes);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Clases de tipografía (typo): tienen propiedades base + breakpoints
|
|
96
|
+
// anidados (mobile/desktop.fontSize|lineHeight). Mantenemos el
|
|
97
|
+
// comportamiento histórico: aceptar tanto `currentValues.typo` como
|
|
98
|
+
// `currentValues` al raíz, por compatibilidad con callsites antiguos.
|
|
99
|
+
const currentClasses = currentValues.typo || currentValues;
|
|
100
|
+
const previousClasses = previousValues.typo || previousValues;
|
|
101
|
+
Object.keys(currentClasses).forEach(className => {
|
|
102
|
+
const current = currentClasses[className];
|
|
103
|
+
const previous = previousClasses[className];
|
|
104
|
+
if (!current || typeof current !== 'object') return;
|
|
105
|
+
if (!previous) {
|
|
106
|
+
// Clase nueva: marcar todas sus props base (no mobile/desktop).
|
|
107
|
+
Object.keys(current).forEach(prop => {
|
|
108
|
+
if (prop !== 'mobile' && prop !== 'desktop') {
|
|
109
|
+
changes.add(`${className}.${prop}`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
['fontFamily', 'fontWeight', 'letterSpacing', 'textTransform'].forEach(prop => {
|
|
115
|
+
if (current[prop] !== previous[prop]) {
|
|
116
|
+
changes.add(`${className}.${prop}`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
['mobile', 'desktop'].forEach(bp => {
|
|
120
|
+
if (!current[bp]) return;
|
|
121
|
+
if (!previous[bp]) {
|
|
122
|
+
if (current[bp].fontSize) changes.add(`${className}.${bp}.fontSize`);
|
|
123
|
+
if (current[bp].lineHeight) changes.add(`${className}.${bp}.lineHeight`);
|
|
124
|
+
} else {
|
|
125
|
+
if (current[bp].fontSize !== previous[bp].fontSize) {
|
|
126
|
+
changes.add(`${className}.${bp}.fontSize`);
|
|
127
|
+
}
|
|
128
|
+
if (current[bp].lineHeight !== previous[bp].lineHeight) {
|
|
129
|
+
changes.add(`${className}.${bp}.lineHeight`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Variables CSS compartidas: solo marca nuevas o cambiadas
|
|
136
|
+
// (no detecta eliminadas, manteniendo el comportamiento histórico).
|
|
137
|
+
if (currentValues.variables) {
|
|
138
|
+
const curVars = currentValues.variables;
|
|
139
|
+
const prevVars = previousValues.variables || {};
|
|
140
|
+
Object.keys(curVars).forEach(varName => {
|
|
141
|
+
if (prevVars[varName] === undefined || curVars[varName] !== prevVars[varName]) {
|
|
142
|
+
changes.add(`variable.${varName}`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return changes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
loadPreviousValues,
|
|
152
|
+
saveCurrentValues,
|
|
153
|
+
getChangedValues
|
|
154
|
+
};
|
|
@@ -198,7 +198,8 @@ function generateClassCSS(className, cls, breakpointName, valueMap, prefix, cate
|
|
|
198
198
|
varName = getSharedVarName(prop, props[prop], prefix, category);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
const importantFlag = prop === 'lineHeight' ? ' !important' : '';
|
|
202
|
+
cssProps.push(` ${toKebabCase(prop)}: var(${varName})${importantFlag};`);
|
|
202
203
|
});
|
|
203
204
|
|
|
204
205
|
return cssProps.length ? ` .${className} {\n${cssProps.join('\n')}\n }` : '';
|
package/src/generators/utils.js
CHANGED
|
@@ -112,12 +112,101 @@ function combineThemeCSS(themeDir) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Devuelve la lista canónica de temas activos a partir de un config.
|
|
117
|
+
*
|
|
118
|
+
* Soporta dos formatos de configuración:
|
|
119
|
+
* - Nuevo: { themes: [{ name, enabled }, ...] }
|
|
120
|
+
* - Antiguo: { theme: { name, enabled } }
|
|
121
|
+
*
|
|
122
|
+
* Cuando hay ambos, `themes[]` tiene prioridad. Los consumidores que
|
|
123
|
+
* heredan configs antiguos siguen funcionando sin cambios. El objeto
|
|
124
|
+
* devuelto por cada tema se enriquece con `label` (capitalización del
|
|
125
|
+
* slug) para que los generadores de nav no tengan que recalcularlo.
|
|
126
|
+
*
|
|
127
|
+
* @param {Object} configData - Config ya cargado (objeto JS).
|
|
128
|
+
* @returns {Array<{name:string, enabled:boolean, label:string}>}
|
|
129
|
+
*/
|
|
130
|
+
function resolveActiveThemes(configData) {
|
|
131
|
+
if (!configData) return [];
|
|
132
|
+
|
|
133
|
+
const rawList = Array.isArray(configData.themes)
|
|
134
|
+
? configData.themes
|
|
135
|
+
: (configData.theme ? [configData.theme] : []);
|
|
136
|
+
|
|
137
|
+
return rawList
|
|
138
|
+
.filter(t => t && t.enabled && t.name)
|
|
139
|
+
.map(t => ({
|
|
140
|
+
name: t.name,
|
|
141
|
+
enabled: true,
|
|
142
|
+
label: 'Tema ' + t.name.charAt(0).toUpperCase() + t.name.slice(1)
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Aplica los overrides de tipografía de un tema sobre el config base.
|
|
148
|
+
*
|
|
149
|
+
* Motivación: la tabla de tipografía lee `config.typo.*.fontFamily`
|
|
150
|
+
* (strings CSS crudos). Cuando un tema redefine las variables
|
|
151
|
+
* `--hg-typo-font-family-*` en su `_variables.css`, el Preview se
|
|
152
|
+
* renderiza con la fuente del tema (porque las variables ganan), pero
|
|
153
|
+
* las columnas "Font family" seguirían mostrando las de la base. Para
|
|
154
|
+
* que la tabla refleje lo que realmente se pinta, mapeamos cada
|
|
155
|
+
* `fontFamily` del typo a través de su alias en la fontFamilyMap base
|
|
156
|
+
* y lo sustituimos por el override del tema.
|
|
157
|
+
*
|
|
158
|
+
* Convención: el tema declara sus overrides en
|
|
159
|
+
* theme.json → typography.fontFamilyMap
|
|
160
|
+
* con las MISMAS claves que la base (`primary-light`, `primary-regular`,
|
|
161
|
+
* `primary-bold`, `secondary`, …). Así seguimos teniendo una única
|
|
162
|
+
* fuente de verdad para la tabla y evitamos duplicar la lista de clases
|
|
163
|
+
* (`title-xxl`, `title-l`, …) en cada tema.
|
|
164
|
+
*
|
|
165
|
+
* Devuelve un NUEVO objeto (no muta el config original). Si no hay
|
|
166
|
+
* overrides, devuelve el config tal cual.
|
|
167
|
+
*
|
|
168
|
+
* @param {Object} config - Config base (config.json parseado).
|
|
169
|
+
* @param {Object} themeData - theme.json parseado (puede ser null).
|
|
170
|
+
* @returns {Object} Config con `fontFamilyMap` y `typo.*.fontFamily` remapeados.
|
|
171
|
+
*/
|
|
172
|
+
function applyThemeTypographyOverrides(config, themeData) {
|
|
173
|
+
if (!config) return config;
|
|
174
|
+
const overrides =
|
|
175
|
+
themeData &&
|
|
176
|
+
themeData.typography &&
|
|
177
|
+
themeData.typography.fontFamilyMap;
|
|
178
|
+
if (!overrides || Object.keys(overrides).length === 0) return config;
|
|
179
|
+
|
|
180
|
+
const baseMap = config.fontFamilyMap || {};
|
|
181
|
+
const mergedMap = { ...baseMap, ...overrides };
|
|
182
|
+
|
|
183
|
+
const remappedTypo = {};
|
|
184
|
+
Object.entries(config.typo || {}).forEach(([className, cls]) => {
|
|
185
|
+
const alias = getFontFamilyName(cls.fontFamily, baseMap);
|
|
186
|
+
// `alias` será una clave conocida (primary-regular, …) cuando el
|
|
187
|
+
// fontFamily del typo coincida con un valor de la base. Si coincide
|
|
188
|
+
// y el tema la sobreescribe, sustituimos el fontFamily por el del
|
|
189
|
+
// tema. Si no, dejamos el string original (no hay nada que remapear).
|
|
190
|
+
const overriddenFamily =
|
|
191
|
+
overrides[alias] !== undefined ? overrides[alias] : cls.fontFamily;
|
|
192
|
+
remappedTypo[className] = { ...cls, fontFamily: overriddenFamily };
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
...config,
|
|
197
|
+
fontFamilyMap: mergedMap,
|
|
198
|
+
typo: remappedTypo
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
115
202
|
module.exports = {
|
|
116
203
|
toKebabCase,
|
|
117
204
|
pxToRem,
|
|
118
205
|
remToPx,
|
|
119
206
|
getFontFamilyName,
|
|
120
207
|
writeFile,
|
|
121
|
-
combineThemeCSS
|
|
208
|
+
combineThemeCSS,
|
|
209
|
+
resolveActiveThemes,
|
|
210
|
+
applyThemeTypographyOverrides
|
|
122
211
|
};
|
|
123
212
|
|
package/src/skills.html
CHANGED
package/src/watch-config.js
CHANGED
|
@@ -7,8 +7,8 @@ const path = require('path');
|
|
|
7
7
|
const crypto = require('crypto');
|
|
8
8
|
const { BuildOrchestrator } = require('./build/build-orchestrator');
|
|
9
9
|
const { AssetManager } = require('./build/asset-manager');
|
|
10
|
-
const { ThemeTransformer } = require('./build/theme-transformer');
|
|
11
10
|
const { loadConfig } = require('./config-loader');
|
|
11
|
+
const { resolveActiveThemes } = require('./generators/utils');
|
|
12
12
|
|
|
13
13
|
// Constantes
|
|
14
14
|
const DEBOUNCE_DELAY = 300; // ms - tiempo de espera antes de regenerar
|
|
@@ -50,18 +50,41 @@ function generateFiles(configPath, outputPath, htmlPath, silent = false) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Función para copiar archivos CSS e imágenes usando AssetManager
|
|
53
|
-
|
|
53
|
+
// IMPORTANTE: Carga `assets` desde config.json (misma fuente de verdad que
|
|
54
|
+
// usa el BuildOrchestrator). Si no pudiera leerse, AssetManager aplica su
|
|
55
|
+
// fallback interno ASSETS_CONFIG. Esto evita que watch use una lista
|
|
56
|
+
// distinta a la del build principal (incl. woff además de woff2).
|
|
57
|
+
function loadAssetsConfig(configPath) {
|
|
58
|
+
if (!configPath) return null;
|
|
59
|
+
try {
|
|
60
|
+
const cfg = loadConfig(configPath);
|
|
61
|
+
return cfg && cfg.assets ? cfg.assets : null;
|
|
62
|
+
} catch (_) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function copyCSSFiles(silent = false, configPath = null) {
|
|
54
68
|
const projectRoot = path.join(__dirname, '..');
|
|
55
|
-
const
|
|
69
|
+
const assetsConfig = loadAssetsConfig(configPath);
|
|
70
|
+
const assetManager = new AssetManager(projectRoot, assetsConfig);
|
|
56
71
|
assetManager.copyCSS(silent);
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
function copyImageFiles(silent = false) {
|
|
74
|
+
function copyImageFiles(silent = false, configPath = null) {
|
|
60
75
|
const projectRoot = path.join(__dirname, '..');
|
|
61
|
-
const
|
|
76
|
+
const assetsConfig = loadAssetsConfig(configPath);
|
|
77
|
+
const assetManager = new AssetManager(projectRoot, assetsConfig);
|
|
62
78
|
assetManager.copyImages(silent);
|
|
63
79
|
}
|
|
64
80
|
|
|
81
|
+
function copyFontFiles(silent = false, configPath = null) {
|
|
82
|
+
const projectRoot = path.join(__dirname, '..');
|
|
83
|
+
const assetsConfig = loadAssetsConfig(configPath);
|
|
84
|
+
const assetManager = new AssetManager(projectRoot, assetsConfig);
|
|
85
|
+
assetManager.copyFonts(silent);
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
// Función principal de watch optimizada
|
|
66
89
|
function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPath = path.join(__dirname, '..', 'dist', 'output.css'), htmlPath = path.join(__dirname, '..', 'dist', 'index.html'), silent = false) {
|
|
67
90
|
if (!silent) {
|
|
@@ -86,10 +109,54 @@ function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPat
|
|
|
86
109
|
path.join(__dirname, 'docs-generator', 'guide-styles.css')
|
|
87
110
|
];
|
|
88
111
|
|
|
89
|
-
// Archivos de tema a observar
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
]
|
|
112
|
+
// Archivos de tema a observar.
|
|
113
|
+
// Antes solo se vigilaba themes/dutti/demo.html. Con la arquitectura
|
|
114
|
+
// actual:
|
|
115
|
+
// - Puede haber varios temas activos (config.themes[]).
|
|
116
|
+
// - Los componentes compartidos viven en themes/_base/ y cambios ahí
|
|
117
|
+
// también deben disparar un rebuild de todas las demos.
|
|
118
|
+
// Recolectamos dinámicamente los demo.html de cada tema activo +
|
|
119
|
+
// todos los .css de themes/_base/ (recursivo).
|
|
120
|
+
const themeFilesToWatch = (() => {
|
|
121
|
+
const watched = [];
|
|
122
|
+
|
|
123
|
+
// Intentamos leer el config para saber qué temas están activos.
|
|
124
|
+
// Delegamos en `resolveActiveThemes` para que el watch reaccione
|
|
125
|
+
// exactamente a los mismos temas que construye el orquestador.
|
|
126
|
+
// Soporta tanto `config.themes[]` (nuevo) como `config.theme` (antiguo).
|
|
127
|
+
let activeThemes = [];
|
|
128
|
+
try {
|
|
129
|
+
const configData = loadConfig(configPath);
|
|
130
|
+
activeThemes = resolveActiveThemes(configData);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
// Si no podemos leer el config, caemos al fallback histórico
|
|
133
|
+
// para no romper flujos existentes.
|
|
134
|
+
activeThemes = [{ name: 'dutti', enabled: true, label: 'Tema Dutti' }];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
activeThemes.forEach(t => {
|
|
138
|
+
const demoFile = path.join(projectRoot, 'themes', t.name, 'demo.html');
|
|
139
|
+
if (fs.existsSync(demoFile)) watched.push(demoFile);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Componentes compartidos: watch recursivo simple sobre themes/_base/.
|
|
143
|
+
const baseDir = path.join(projectRoot, 'themes', '_base');
|
|
144
|
+
if (fs.existsSync(baseDir)) {
|
|
145
|
+
const walk = (dir) => {
|
|
146
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
147
|
+
const full = path.join(dir, entry.name);
|
|
148
|
+
if (entry.isDirectory()) {
|
|
149
|
+
walk(full);
|
|
150
|
+
} else if (entry.isFile() && full.endsWith('.css')) {
|
|
151
|
+
watched.push(full);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
try { walk(baseDir); } catch (_) { /* ignorar errores de lectura */ }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return watched;
|
|
159
|
+
})();
|
|
93
160
|
|
|
94
161
|
// Estado del watch
|
|
95
162
|
let lastHash = getFileHash(configPath);
|
|
@@ -156,8 +223,9 @@ function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPat
|
|
|
156
223
|
if (!silent) {
|
|
157
224
|
console.log(`🔄 Detectado cambio en ${path.basename(cssFile)}, copiando a dist/...\n`);
|
|
158
225
|
}
|
|
159
|
-
copyCSSFiles(silent);
|
|
160
|
-
copyImageFiles(silent);
|
|
226
|
+
copyCSSFiles(silent, configPath);
|
|
227
|
+
copyImageFiles(silent, configPath);
|
|
228
|
+
copyFontFiles(silent, configPath);
|
|
161
229
|
if (!silent) {
|
|
162
230
|
console.log('✨ CSS actualizado - Recarga el navegador para ver los cambios\n');
|
|
163
231
|
}
|
|
@@ -166,44 +234,43 @@ function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPat
|
|
|
166
234
|
}, DEBOUNCE_DELAY);
|
|
167
235
|
}
|
|
168
236
|
|
|
169
|
-
// Función para manejar cambios en archivos de tema
|
|
237
|
+
// Función para manejar cambios en archivos de tema.
|
|
238
|
+
// Antes el handler intentaba re-transformar SOLO la demo de un único
|
|
239
|
+
// tema (leyendo `configData.theme` singular). Con la arquitectura
|
|
240
|
+
// actual, un cambio puede venir de:
|
|
241
|
+
// - themes/<name>/demo.html → afecta a la demo de ESE tema
|
|
242
|
+
// - themes/_base/**/*.css → afecta al CSS de TODOS los temas
|
|
243
|
+
// Para cubrir ambos casos sin reimplementar la orquestación,
|
|
244
|
+
// delegamos en `generateFiles`, que usa BuildOrchestrator y ya sabe
|
|
245
|
+
// recorrer `config.themes[]` (o el antiguo `config.theme`).
|
|
170
246
|
function handleThemeChange(themeFile) {
|
|
171
247
|
if (debounceTimer) {
|
|
172
248
|
clearTimeout(debounceTimer);
|
|
173
249
|
}
|
|
174
|
-
|
|
250
|
+
|
|
175
251
|
debounceTimer = setTimeout(() => {
|
|
176
252
|
const currentHash = getFileHash(themeFile);
|
|
177
253
|
const lastThemeHash = themeHashes.get(themeFile);
|
|
178
|
-
|
|
254
|
+
|
|
179
255
|
if (currentHash && currentHash !== lastThemeHash && !isRegenerating) {
|
|
180
256
|
isRegenerating = true;
|
|
181
257
|
themeHashes.set(themeFile, currentHash);
|
|
182
258
|
if (!silent) {
|
|
183
|
-
|
|
259
|
+
const rel = path.relative(projectRoot, themeFile);
|
|
260
|
+
console.log(`🔄 Detectado cambio en ${rel}, regenerando...\n`);
|
|
184
261
|
}
|
|
185
|
-
|
|
186
|
-
// Usar ThemeTransformer en lugar de ejecutar script externo
|
|
262
|
+
|
|
187
263
|
try {
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
const outputDir = path.dirname(outputPath);
|
|
192
|
-
const targetFile = path.join(outputDir, 'themes', `${themeName}-demo.html`);
|
|
193
|
-
|
|
194
|
-
const themeTransformer = new ThemeTransformer(projectRoot);
|
|
195
|
-
themeTransformer.transform(themeFile, targetFile, themeName, silent);
|
|
196
|
-
|
|
197
|
-
if (!silent) {
|
|
198
|
-
console.log('✨ Demo actualizado - Recarga el navegador para ver los cambios\n');
|
|
199
|
-
}
|
|
264
|
+
generateFiles(configPath, outputPath, htmlPath, silent);
|
|
265
|
+
if (!silent) {
|
|
266
|
+
console.log('✨ Tema(s) actualizado(s) - Recarga el navegador para ver los cambios\n');
|
|
200
267
|
}
|
|
201
268
|
} catch (error) {
|
|
202
269
|
if (!silent) {
|
|
203
|
-
console.error('❌ Error al regenerar
|
|
270
|
+
console.error('❌ Error al regenerar tema:', error.message);
|
|
204
271
|
}
|
|
205
272
|
}
|
|
206
|
-
|
|
273
|
+
|
|
207
274
|
isRegenerating = false;
|
|
208
275
|
}
|
|
209
276
|
}, DEBOUNCE_DELAY);
|
|
@@ -337,4 +404,4 @@ if (require.main === module) {
|
|
|
337
404
|
watch(configPath, outputPath, htmlPath);
|
|
338
405
|
}
|
|
339
406
|
|
|
340
|
-
module.exports = { watch, generateFiles, copyCSSFiles, copyImageFiles };
|
|
407
|
+
module.exports = { watch, generateFiles, copyCSSFiles, copyImageFiles, copyFontFiles };
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
align-items: center;
|
|
10
10
|
justify-content: center;
|
|
11
11
|
gap: var(--hg-spacing-8);
|
|
12
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
12
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
13
13
|
font-size: var(--hg-typo-font-size-13);
|
|
14
14
|
font-weight: var(--hg-typo-font-weight-400);
|
|
15
15
|
line-height: var(--hg-typo-line-height-1-5);
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
color: var(--hg-color-white);
|
|
46
46
|
background-color: var(--hg-color-black);
|
|
47
47
|
border: var(--border-width) var(--border-style) var(--hg-color-black);
|
|
48
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
48
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
49
49
|
font-weight: var(--hg-typo-font-weight-400);
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
.checkbox-label {
|
|
92
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
92
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
93
93
|
font-size: var(--hg-typo-font-size-13);
|
|
94
94
|
font-weight: var(--hg-typo-font-weight-400);
|
|
95
95
|
line-height: var(--hg-typo-line-height-1-5);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
/* Helper Text / Messages */
|
|
29
29
|
.helper-text {
|
|
30
30
|
display: block;
|
|
31
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
31
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
32
32
|
font-size: var(--hg-typo-font-size-12);
|
|
33
33
|
font-weight: var(--hg-typo-font-weight-400);
|
|
34
34
|
line-height: var(--hg-typo-line-height-1-5);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
.input {
|
|
7
7
|
display: block;
|
|
8
8
|
width: 100%;
|
|
9
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
9
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
10
10
|
font-size: var(--hg-typo-font-size-13);
|
|
11
11
|
font-weight: var(--hg-typo-font-weight-400);
|
|
12
12
|
line-height: var(--hg-typo-line-height-1-5);
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
.form-input-label-2,
|
|
58
58
|
.error-zone {
|
|
59
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
59
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
60
60
|
position: relative;
|
|
61
61
|
|
|
62
62
|
display: block;
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
.form-input-label-2 .help,
|
|
76
76
|
.error-zone .help {
|
|
77
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
77
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
78
78
|
position: relative;
|
|
79
79
|
line-height: 1.5;
|
|
80
80
|
font-size: 12px;
|
|
@@ -104,15 +104,23 @@
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
.form-input-label-2 > input,
|
|
107
|
+
.form-input-label-2 > textarea,
|
|
108
|
+
.form-input-label-2 > select,
|
|
107
109
|
.form-input-label-2 > label,
|
|
108
110
|
.error-zone > input,
|
|
111
|
+
.error-zone > textarea,
|
|
112
|
+
.error-zone > select,
|
|
109
113
|
.error-zone > label {
|
|
110
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
114
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
111
115
|
font-weight: normal;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
.form-input-label-2 > input,
|
|
115
|
-
.
|
|
119
|
+
.form-input-label-2 > textarea,
|
|
120
|
+
.form-input-label-2 > select,
|
|
121
|
+
.error-zone > input,
|
|
122
|
+
.error-zone > textarea,
|
|
123
|
+
.error-zone > select {
|
|
116
124
|
border: none;
|
|
117
125
|
border-bottom: var(--border-width) var(--border-style) var(--hg-color-primary);
|
|
118
126
|
border-radius: 0;
|
|
@@ -120,13 +128,27 @@
|
|
|
120
128
|
padding: var(--hg-spacing-16) 0 var(--hg-spacing-8) 0;
|
|
121
129
|
padding-left: 0;
|
|
122
130
|
padding-right: 0;
|
|
123
|
-
height: 40px;
|
|
124
131
|
box-sizing: border-box;
|
|
125
132
|
transition: border-bottom-color 0.1s ease-in-out, border-bottom-width 0.1s ease-in-out;
|
|
126
133
|
margin: 0;
|
|
127
134
|
vertical-align: top;
|
|
128
135
|
}
|
|
129
136
|
|
|
137
|
+
.form-input-label-2 > input,
|
|
138
|
+
.form-input-label-2 > select,
|
|
139
|
+
.error-zone > input,
|
|
140
|
+
.error-zone > select {
|
|
141
|
+
height: 40px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* El textarea mantiene su altura natural (rows) pero respeta el padding
|
|
145
|
+
superior para dejar aire al label flotado. */
|
|
146
|
+
.form-input-label-2 > textarea,
|
|
147
|
+
.error-zone > textarea {
|
|
148
|
+
min-height: 40px;
|
|
149
|
+
resize: vertical;
|
|
150
|
+
}
|
|
151
|
+
|
|
130
152
|
.form-input-label-2 > label,
|
|
131
153
|
.error-zone > label {
|
|
132
154
|
box-sizing: border-box;
|
|
@@ -147,12 +169,27 @@
|
|
|
147
169
|
}
|
|
148
170
|
|
|
149
171
|
.form-input-label-2 input::placeholder,
|
|
150
|
-
.
|
|
172
|
+
.form-input-label-2 textarea::placeholder,
|
|
173
|
+
.error-zone input::placeholder,
|
|
174
|
+
.error-zone textarea::placeholder {
|
|
151
175
|
color: transparent;
|
|
152
176
|
}
|
|
153
177
|
|
|
154
178
|
.form-input-label-2 input:not(:placeholder-shown) ~ label,
|
|
155
|
-
.
|
|
179
|
+
.form-input-label-2 textarea:not(:placeholder-shown) ~ label,
|
|
180
|
+
.error-zone input:not(:placeholder-shown) ~ label,
|
|
181
|
+
.error-zone textarea:not(:placeholder-shown) ~ label {
|
|
182
|
+
top: 0;
|
|
183
|
+
font-size: 12px;
|
|
184
|
+
color: var(--hg-color-dark-grey);
|
|
185
|
+
transform: translateY(0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* El <select> no soporta :placeholder-shown (siempre tiene una option
|
|
189
|
+
seleccionada), así que forzamos el label a estar permanentemente
|
|
190
|
+
flotado arriba cuando el control es un select. */
|
|
191
|
+
.form-input-label-2 > select ~ label,
|
|
192
|
+
.error-zone > select ~ label {
|
|
156
193
|
top: 0;
|
|
157
194
|
font-size: 12px;
|
|
158
195
|
color: var(--hg-color-dark-grey);
|
|
@@ -160,12 +197,20 @@
|
|
|
160
197
|
}
|
|
161
198
|
|
|
162
199
|
.form-input-label-2 input:focus,
|
|
163
|
-
.
|
|
200
|
+
.form-input-label-2 textarea:focus,
|
|
201
|
+
.form-input-label-2 select:focus,
|
|
202
|
+
.error-zone input:focus,
|
|
203
|
+
.error-zone textarea:focus,
|
|
204
|
+
.error-zone select:focus {
|
|
164
205
|
outline: none;
|
|
165
206
|
}
|
|
166
207
|
|
|
167
208
|
.form-input-label-2 input:focus ~ label,
|
|
168
|
-
.
|
|
209
|
+
.form-input-label-2 textarea:focus ~ label,
|
|
210
|
+
.form-input-label-2 select:focus ~ label,
|
|
211
|
+
.error-zone input:focus ~ label,
|
|
212
|
+
.error-zone textarea:focus ~ label,
|
|
213
|
+
.error-zone select:focus ~ label {
|
|
169
214
|
top: 0;
|
|
170
215
|
font-size: 12px;
|
|
171
216
|
color: var(--hg-color-dark-grey);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
.label {
|
|
7
7
|
display: block;
|
|
8
|
-
font-family: var(--hg-typo-font-family-primary);
|
|
8
|
+
font-family: var(--hg-typo-font-family-primary-regular);
|
|
9
9
|
font-size: var(--hg-typo-font-size-12);
|
|
10
10
|
font-weight: var(--hg-typo-font-weight-700);
|
|
11
11
|
line-height: var(--hg-typo-line-height-1-5);
|