tailjng 0.1.2 → 0.1.4

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 CHANGED
@@ -134,24 +134,46 @@ import { JAlertToastComponent } from './tailjng/alert/alert-toast/toast-alert.co
134
134
  <JButton [text]="'Guardar'" classes="primary" [icon]="icons.save" (clicked)="save()" />
135
135
  ```
136
136
 
137
- ### Colores — `JColorsService` + safelist Tailwind
137
+ ### Colores — `src/app/tailjng/colors/`
138
138
 
139
- Las variantes (`primary`, `success_soft`, …) las resuelve **`JColorsService`** en runtime. Tailwind v4 **no ve** esas clases en el HTML estático, así que el paquete incluye `node_modules/tailjng/src/colors.safelist.css` con `@source inline(...)`.
139
+ Las variantes (`primary`, `success_soft`, …) las resuelve **`JColorsService`**. Tailwind v4 no genera clases solo en runtime, así que cada proyecto tiene una carpeta editable:
140
140
 
141
- `init:app` importa ese archivo en tu `styles.css` / `styles.scss` automáticamente.
141
+ | Archivo | Para qué |
142
+ |---------|----------|
143
+ | `colors.config.ts` | Lista de variantes (incluye las 77 por defecto) + las tuyas (`brand`, etc.) |
144
+ | `colors.safelist.css` | Clases Tailwind que el build debe generar (`@apply`) |
142
145
 
143
- **Variantes personalizadas del proyecto:**
146
+ **`init:app`** y **`sync:app`** crean esa carpeta (sin sobrescribir si ya existe), importan `colors.safelist.css` en `styles.css` y registran `tailjngColorsProvider`.
147
+
148
+ Si falta la carpeta en un proyecto existente:
144
149
 
145
150
  ```powershell
146
- npx tailjng add colors-config
151
+ npx tailjng sync:app
152
+ # o solo los archivos:
153
+ npx tailjng add colors
147
154
  ```
148
155
 
149
- Copia `src/app/tailjng/colors/` con:
156
+ **Añadir un color propio** — edita `colors.config.ts` y registra las clases nuevas en el bloque `.__tailjng_custom_colors__` de `colors.safelist.css`:
150
157
 
151
- - `colors.config.ts` — define variantes extra (`TAILJNG_COLORS_CONFIG`)
152
- - `colors.safelist.css` — añade tus clases Tailwind al `@source inline` para que el build las genere
158
+ ```typescript
159
+ // colors.config.ts
160
+ brand: 'bg-indigo-600 text-white hover:bg-indigo-700 border border-indigo-500 shadow-md',
161
+ ```
162
+
163
+ ```css
164
+ /* colors.safelist.css */
165
+ .__tailjng_custom_colors__ {
166
+ @apply bg-indigo-600 text-white hover:bg-indigo-700 border-indigo-500;
167
+ }
168
+ ```
169
+
170
+ Uso en componentes:
171
+
172
+ ```html
173
+ <JButton classes="brand" text="Guardar" />
174
+ ```
153
175
 
154
- Registra el provider en `app.config.ts`:
176
+ `init:app` / `sync:app` registran el provider automáticamente. Manualmente en `app.config.ts`:
155
177
 
156
178
  ```typescript
157
179
  import { tailjngColorsProvider } from './tailjng/colors/colors.config';
@@ -204,6 +226,7 @@ Pasa `endpoint`, `type="searchable"` o `loadOnInit` según el componente; usa `J
204
226
  | `npx tailjng init:app` | Prepara proyecto: Tailwind, deps, providers, estilos |
205
227
  | `npx tailjng init:app --yes` | Sin prompts interactivos |
206
228
  | `npx tailjng init:app --with-components` | init + mode-toggle y alerts |
229
+ | `npx tailjng sync:app` | Tras `npm install tailjng@latest` — solo lo nuevo (estilos, deps, angular.json) |
207
230
  | `npx tailjng add <nombre>` | Instala componente + dependencias |
208
231
  | `npx tailjng install-all` | Instala los 35 componentes |
209
232
  | `npx tailjng list` | Lista registry e indica cuáles ya están instalados |
@@ -1,11 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
4
3
  const readline = require('readline');
5
4
  const { COLORS } = require('../settings/colors');
6
5
  const { addComponent } = require('../component-manager');
7
6
  const { getComponentList } = require('../settings/components-list');
8
7
  const { buildInitFiles } = require('../templates/app.generator');
8
+ const { RUNTIME_PACKAGES, DEV_PACKAGES, installPackages } = require('../settings/init-packages');
9
9
  const {
10
10
  findAngularWorkspace,
11
11
  readJson,
@@ -25,25 +25,14 @@ const {
25
25
  hasTailwindSetup,
26
26
  getTailjngSafelistCssImport,
27
27
  ensureTailjngSafelistImport,
28
+ alignAngularAnimationsInPackageJson,
28
29
  resolveStyleFilePath,
29
30
  fileExists,
30
31
  } = require('../settings/project-utils');
31
-
32
- const RUNTIME_PACKAGES = {
33
- 'lucide-angular': '^0.525.0',
34
- '@ng-icons/lucide': '>=32.0.0',
35
- 'date-fns': '^4.1.0',
36
- 'exceljs': '^4.4.0',
37
- 'xlsx': '^0.18.5',
38
- 'file-saver': '^2.0.5',
39
- };
40
-
41
- const DEV_PACKAGES = {
42
- tailwindcss: '^4.0.9',
43
- '@tailwindcss/postcss': '^4.0.9',
44
- postcss: '^8.5.3',
45
- autoprefixer: '^10.4.20',
46
- };
32
+ const {
33
+ ensureProjectColorsConfig,
34
+ patchAppConfigColors,
35
+ } = require('../settings/colors-config-utils');
47
36
 
48
37
  /** Solo si pasas --with-components (opcional; no es el flujo normal). */
49
38
  const OPTIONAL_BASE_COMPONENTS = ['mode-toggle', 'alert-dialog', 'alert-toast'];
@@ -80,30 +69,6 @@ function askYesNo(question, defaultYes = true) {
80
69
  });
81
70
  }
82
71
 
83
- function installPackages(workspaceRoot, packages, isDev = false) {
84
- const entries = Object.entries(packages);
85
- if (entries.length === 0) return;
86
-
87
- const spec = entries.map(([name, version]) => `${name}@${version}`).join(' ');
88
- const flag = isDev ? '--save-dev' : '--save';
89
- console.log(`${COLORS.blue}[tailjng CLI] Installing ${isDev ? 'dev ' : ''}dependencies...${COLORS.reset}`);
90
-
91
- const run = (extra = '') => {
92
- execSync(`npm install ${flag} ${extra} ${spec}`.replace(/\s+/g, ' ').trim(), {
93
- cwd: workspaceRoot,
94
- stdio: 'inherit',
95
- shell: true,
96
- });
97
- };
98
-
99
- try {
100
- run();
101
- } catch {
102
- console.log(`${COLORS.yellow}[tailjng CLI] Retrying with --legacy-peer-deps...${COLORS.reset}`);
103
- run('--legacy-peer-deps');
104
- }
105
- }
106
-
107
72
  function resolveTargetPath(workspaceRoot, appProject, relativePath, appRootRelative = false) {
108
73
  const appRoot = path.join(workspaceRoot, appProject.root || '');
109
74
 
@@ -180,12 +145,19 @@ async function runInitApp() {
180
145
  console.log(`${COLORS.yellow}Install it first: npm install tailjng${COLORS.reset}`);
181
146
  }
182
147
 
148
+ // Siempre alinear @angular/animations con la versión real de @angular/core del proyecto
149
+ const animationsAligned = alignAngularAnimationsInPackageJson(workspaceRoot);
150
+ if (animationsAligned.changed) {
151
+ console.log(`${COLORS.cyan}[tailjng CLI] Aligned @angular/animations → ${animationsAligned.version} (same as @angular/core)${COLORS.reset}`);
152
+ Object.assign(packageJson, readJson(packageJsonPath));
153
+ }
154
+
183
155
  if (installDeps) {
184
156
  if (tailwindReady) {
185
157
  console.log(`${COLORS.cyan}[tailjng CLI] Tailwind already configured — skipping Tailwind/PostCSS packages and .postcssrc.json${COLORS.reset}`);
186
158
  }
187
159
 
188
- const runtimePackages = resolveRuntimePackages(packageJson, RUNTIME_PACKAGES);
160
+ const runtimePackages = resolveRuntimePackages(packageJson, RUNTIME_PACKAGES, workspaceRoot);
189
161
  const devPackages = resolveDevPackages(packageJson, DEV_PACKAGES, tailwindReady);
190
162
  const missingRuntime = Object.fromEntries(getMissingPackages(packageJson, runtimePackages, workspaceRoot));
191
163
  const missingDev = Object.fromEntries(getMissingPackages(packageJson, devPackages, workspaceRoot));
@@ -198,6 +170,9 @@ async function runInitApp() {
198
170
  installPackages(workspaceRoot, missingDev, true);
199
171
  }
200
172
 
173
+ const { appRoot } = resolveAppPaths(workspaceRoot, selectedApp);
174
+ ensureProjectColorsConfig(workspaceRoot, appRoot, overwrite);
175
+
201
176
  const hasAppConfig = fileExists(appConfigPath);
202
177
  const safelistImport = getTailjngSafelistCssImport(workspaceRoot, primaryStylePath, selectedApp);
203
178
  const primaryStyleFullPath = resolveStyleFilePath(workspaceRoot, primaryStylePath, selectedApp);
@@ -246,6 +221,11 @@ async function runInitApp() {
246
221
  if (patched.changed) {
247
222
  console.log(`${COLORS.green}✔ Patched ${path.relative(workspaceRoot, appConfigPath)}${COLORS.reset}`);
248
223
  }
224
+
225
+ const colorsPatched = patchAppConfigColors(appConfigPath, srcRoot);
226
+ if (colorsPatched.changed) {
227
+ console.log(`${COLORS.green}✔ Linked tailjngColorsProvider from src/app/tailjng/colors/${COLORS.reset}`);
228
+ }
249
229
  }
250
230
 
251
231
  const indexPatched = patchIndexHtml(indexPath);
@@ -0,0 +1,192 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { COLORS } = require('../settings/colors');
4
+ const { RUNTIME_PACKAGES, DEV_PACKAGES, installPackages } = require('../settings/init-packages');
5
+ const { buildPathsJson, buildPathsReadme } = require('../templates/app.generator');
6
+ const {
7
+ findAngularWorkspace,
8
+ readJson,
9
+ getApplicationProjects,
10
+ resolveAppPaths,
11
+ getBuildOptions,
12
+ detectStyleLanguage,
13
+ getPrimaryStyleEntry,
14
+ writeFileSafe,
15
+ ensureTailjngStylesInAngularJson,
16
+ patchIndexHtml,
17
+ patchAppConfig,
18
+ getMissingPackages,
19
+ resolveRuntimePackages,
20
+ resolveDevPackages,
21
+ hasTailwindSetup,
22
+ getTailjngSafelistCssImport,
23
+ ensureTailjngSafelistImport,
24
+ alignAngularAnimationsInPackageJson,
25
+ resolveStyleFilePath,
26
+ fileExists,
27
+ } = require('../settings/project-utils');
28
+ const {
29
+ ensureProjectColorsConfig,
30
+ patchAppConfigColors,
31
+ COLORS_FILES,
32
+ } = require('../settings/colors-config-utils');
33
+
34
+ function resolveTargetPath(workspaceRoot, appProject, relativePath, appRootRelative = false) {
35
+ const appRoot = path.join(workspaceRoot, appProject.root || '');
36
+
37
+ if (appRootRelative) {
38
+ return path.join(appRoot, relativePath);
39
+ }
40
+
41
+ if (relativePath.startsWith('projects/')) {
42
+ return path.join(workspaceRoot, relativePath);
43
+ }
44
+
45
+ return path.join(workspaceRoot, appProject.sourceRoot, relativePath.replace(/^src\//, ''));
46
+ }
47
+
48
+ async function runSyncApp() {
49
+ const workspace = findAngularWorkspace(process.cwd());
50
+
51
+ if (!workspace) {
52
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: angular.json not found. Run this inside an Angular project.${COLORS.reset}`);
53
+ process.exit(1);
54
+ }
55
+
56
+ const { workspaceRoot, angularJsonPath } = workspace;
57
+ const angularJson = readJson(angularJsonPath);
58
+ const apps = getApplicationProjects(angularJson);
59
+
60
+ if (apps.length === 0) {
61
+ console.error(`${COLORS.red}[tailjng CLI] ERROR: No application project found in angular.json.${COLORS.reset}`);
62
+ process.exit(1);
63
+ }
64
+
65
+ const selectedApp = apps[0];
66
+ const { appRoot, srcRoot } = resolveAppPaths(workspaceRoot, selectedApp);
67
+ const buildOptions = getBuildOptions(angularJson, selectedApp.name);
68
+ const styleLanguage = detectStyleLanguage(buildOptions);
69
+ const primaryStylePath = getPrimaryStyleEntry(buildOptions);
70
+ const primaryStyleFullPath = resolveStyleFilePath(workspaceRoot, primaryStylePath, selectedApp);
71
+ const indexPath = path.join(workspaceRoot, buildOptions.index || path.join(selectedApp.sourceRoot, 'index.html'));
72
+ const appConfigPath = path.join(srcRoot, 'app', 'app.config.ts');
73
+ const packageJsonPath = path.join(workspaceRoot, 'package.json');
74
+ const packageJson = fileExists(packageJsonPath) ? readJson(packageJsonPath) : { dependencies: {}, devDependencies: {} };
75
+ const tailwindReady = hasTailwindSetup(workspaceRoot, packageJson, primaryStylePath);
76
+ const installDeps = !process.argv.includes('--no-install');
77
+ const componentsPath = 'src/app/tailjng';
78
+
79
+ console.log(`\n${COLORS.bright}${COLORS.blue}[tailjng CLI] sync:app${COLORS.reset}`);
80
+ console.log(`${COLORS.dim}Updates integration only — does not overwrite your config (environment, providers, styles).${COLORS.reset}`);
81
+ console.log(`${COLORS.dim}Workspace: ${workspaceRoot} | App: ${selectedApp.name}${COLORS.reset}\n`);
82
+
83
+ if (!packageJson.dependencies?.tailjng) {
84
+ console.log(`${COLORS.yellow}[tailjng CLI] WARNING: tailjng is not in package.json. Run: npm install tailjng@latest --legacy-peer-deps${COLORS.reset}`);
85
+ }
86
+
87
+ let changes = 0;
88
+
89
+ const animationsAligned = alignAngularAnimationsInPackageJson(workspaceRoot);
90
+ if (animationsAligned.changed) {
91
+ changes += 1;
92
+ console.log(`${COLORS.green}✔ Aligned @angular/animations → ${animationsAligned.version}${COLORS.reset}`);
93
+ Object.assign(packageJson, readJson(packageJsonPath));
94
+ }
95
+
96
+ if (installDeps) {
97
+ const runtimePackages = resolveRuntimePackages(packageJson, RUNTIME_PACKAGES, workspaceRoot);
98
+ const devPackages = resolveDevPackages(packageJson, DEV_PACKAGES, tailwindReady);
99
+ const missingRuntime = Object.fromEntries(getMissingPackages(packageJson, runtimePackages, workspaceRoot));
100
+ const missingDev = Object.fromEntries(getMissingPackages(packageJson, devPackages, workspaceRoot));
101
+
102
+ if (Object.keys(missingRuntime).length > 0) {
103
+ installPackages(workspaceRoot, missingRuntime, false);
104
+ changes += 1;
105
+ console.log(`${COLORS.green}✔ Installed missing runtime packages${COLORS.reset}`);
106
+ }
107
+
108
+ if (Object.keys(missingDev).length > 0) {
109
+ installPackages(workspaceRoot, missingDev, true);
110
+ changes += 1;
111
+ console.log(`${COLORS.green}✔ Installed missing dev packages${COLORS.reset}`);
112
+ }
113
+
114
+ if (Object.keys(missingRuntime).length === 0 && Object.keys(missingDev).length === 0) {
115
+ console.log(`${COLORS.dim}↷ Dependencies already satisfied${COLORS.reset}`);
116
+ }
117
+ } else {
118
+ console.log(`${COLORS.dim}↷ Skipped dependency install (--no-install)${COLORS.reset}`);
119
+ }
120
+
121
+ const pathsJsonPath = resolveTargetPath(workspaceRoot, selectedApp, '.tailjng/paths.json', true);
122
+ if (writeFileSafe(pathsJsonPath, buildPathsJson(componentsPath), false)) {
123
+ changes += 1;
124
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, pathsJsonPath)}${COLORS.reset}`);
125
+ }
126
+
127
+ const pathsReadmePath = resolveTargetPath(workspaceRoot, selectedApp, '.tailjng/README.md', true);
128
+ if (writeFileSafe(pathsReadmePath, buildPathsReadme(), false)) {
129
+ changes += 1;
130
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, pathsReadmePath)}${COLORS.reset}`);
131
+ }
132
+
133
+ const colorsResult = ensureProjectColorsConfig(workspaceRoot, appRoot, false);
134
+ if (colorsResult.created > 0) {
135
+ changes += colorsResult.created;
136
+ } else if (colorsResult.skipped === COLORS_FILES.length) {
137
+ console.log(`${COLORS.dim}↷ Project colors config already present${COLORS.reset}`);
138
+ }
139
+
140
+ const safelistImport = getTailjngSafelistCssImport(workspaceRoot, primaryStylePath, selectedApp);
141
+ const safelistPatched = ensureTailjngSafelistImport(primaryStyleFullPath, safelistImport);
142
+ if (safelistPatched.changed) {
143
+ changes += 1;
144
+ const label = safelistPatched.migrated
145
+ ? 'Migrated colors safelist to project folder'
146
+ : 'Added tailjng colors safelist';
147
+ console.log(`${COLORS.green}✔ ${label} → ${path.relative(workspaceRoot, primaryStyleFullPath)}${COLORS.reset}`);
148
+ } else {
149
+ console.log(`${COLORS.dim}↷ Colors safelist already in styles${COLORS.reset}`);
150
+ }
151
+
152
+ const stylesPatched = ensureTailjngStylesInAngularJson(angularJsonPath, selectedApp.name, styleLanguage);
153
+ if (stylesPatched.changed) {
154
+ changes += 1;
155
+ console.log(`${COLORS.green}✔ Added node_modules/tailjng/src/styles.css to angular.json${COLORS.reset}`);
156
+ } else {
157
+ console.log(`${COLORS.dim}↷ tailjng styles already in angular.json${COLORS.reset}`);
158
+ }
159
+
160
+ if (fileExists(appConfigPath)) {
161
+ const patched = patchAppConfig(appConfigPath);
162
+ if (patched.changed) {
163
+ changes += 1;
164
+ console.log(`${COLORS.green}✔ Patched app.config.ts (added tailjngProviders)${COLORS.reset}`);
165
+ }
166
+
167
+ const colorsPatched = patchAppConfigColors(appConfigPath, srcRoot);
168
+ if (colorsPatched.changed) {
169
+ changes += 1;
170
+ console.log(`${COLORS.green}✔ Linked tailjngColorsProvider from src/app/tailjng/colors/${COLORS.reset}`);
171
+ }
172
+ }
173
+
174
+ if (patchIndexHtml(indexPath)) {
175
+ changes += 1;
176
+ console.log(`${COLORS.green}✔ Patched index.html (body classes)${COLORS.reset}`);
177
+ }
178
+
179
+ console.log(`\n${COLORS.greenBright}${COLORS.bright}[tailjng CLI] sync:app completed.${COLORS.reset}`);
180
+ console.log(`${COLORS.dim}Changes applied: ${changes}${COLORS.reset}`);
181
+
182
+ if (changes === 0) {
183
+ console.log(`${COLORS.cyan}Project already up to date with this tailjng version.${COLORS.reset}`);
184
+ }
185
+
186
+ console.log(`\n${COLORS.dim}Tip: update UI components with ${COLORS.cyan}npx tailjng add <name>${COLORS.reset}${COLORS.dim} (asks before overwrite).${COLORS.reset}\n`);
187
+ }
188
+
189
+ runSyncApp().catch((error) => {
190
+ console.error(`${COLORS.red}[tailjng CLI] sync:app failed:${COLORS.reset}`, error);
191
+ process.exit(1);
192
+ });
package/cli/index.js CHANGED
@@ -18,6 +18,11 @@ async function main() {
18
18
  return
19
19
  }
20
20
 
21
+ if (command === "sync:app") {
22
+ require("./execute/sync-app")
23
+ return
24
+ }
25
+
21
26
  if (command === "add") {
22
27
  const componentName = args[1]
23
28
  if (!componentName) {
@@ -53,8 +58,10 @@ function showHelp() {
53
58
  console.log(`${COLORS.bright}${COLORS.blue}tailjng CLI${COLORS.reset} v${getPackageVersion(__dirname)}
54
59
 
55
60
  ${COLORS.bright}Project setup${COLORS.reset}
56
- ${COLORS.cyan}npx tailjng init:app${COLORS.reset} Configure Tailwind, providers, styles (no UI)
61
+ ${COLORS.cyan}npx tailjng init:app${COLORS.reset} First-time setup (Tailwind, providers, styles)
57
62
  ${COLORS.cyan}npx tailjng init:app --with-components${COLORS.reset} init:app + mode-toggle and alerts
63
+ ${COLORS.cyan}npx tailjng sync:app${COLORS.reset} Update integration after npm upgrade (no overwrite)
64
+ ${COLORS.cyan}npx tailjng sync:app --no-install${COLORS.reset} sync:app without npm install
58
65
 
59
66
  ${COLORS.bright}Components${COLORS.reset} ${COLORS.dim}(run from your Angular app directory)${COLORS.reset}
60
67
  ${COLORS.cyan}npx tailjng add${COLORS.reset} ${COLORS.dim}<name>${COLORS.reset} Install one component + dependencies
@@ -0,0 +1,188 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { COLORS } = require('./colors');
4
+ const { writeFileSafe } = require('./project-utils');
5
+
6
+ const COLORS_SOURCE_REL = 'src/lib/components/colors-config';
7
+ const COLORS_FILES = ['colors.config.ts', 'colors.safelist.css', 'README.md'];
8
+
9
+ function getProjectColorsDir(appRoot) {
10
+ return path.join(appRoot, 'src', 'app', 'tailjng', 'colors');
11
+ }
12
+
13
+ function resolveTailjngPackageRoot(workspaceRoot) {
14
+ const fromNodeModules = path.join(workspaceRoot, 'node_modules', 'tailjng', COLORS_SOURCE_REL);
15
+ if (fs.existsSync(fromNodeModules)) {
16
+ return path.join(workspaceRoot, 'node_modules', 'tailjng');
17
+ }
18
+
19
+ const fromMonorepo = path.join(workspaceRoot, 'projects', 'tailjng', COLORS_SOURCE_REL);
20
+ if (fs.existsSync(fromMonorepo)) {
21
+ return path.join(workspaceRoot, 'projects', 'tailjng');
22
+ }
23
+
24
+ return null;
25
+ }
26
+
27
+ function getColorsSafelistImportLine(workspaceRoot, styleEntry, appProject) {
28
+ const appRoot = path.join(workspaceRoot, appProject.root || '');
29
+ const colorsSafelist = path.join(getProjectColorsDir(appRoot), 'colors.safelist.css');
30
+
31
+ let stylePath;
32
+ if (path.isAbsolute(styleEntry)) {
33
+ stylePath = styleEntry;
34
+ } else if (styleEntry.startsWith('projects/')) {
35
+ stylePath = path.join(workspaceRoot, styleEntry);
36
+ } else {
37
+ stylePath = path.join(appRoot, styleEntry);
38
+ }
39
+
40
+ const targetSafelist = fs.existsSync(colorsSafelist)
41
+ ? colorsSafelist
42
+ : path.join(workspaceRoot, 'node_modules', 'tailjng', 'src', 'colors.safelist.css');
43
+
44
+ let rel = path.relative(path.dirname(stylePath), targetSafelist).split(path.sep).join('/');
45
+ if (!rel.startsWith('.')) rel = `./${rel}`;
46
+ return `@import "${rel}";`;
47
+ }
48
+
49
+ function ensureProjectColorsConfig(workspaceRoot, appRoot, overwrite = false) {
50
+ const packageRoot = resolveTailjngPackageRoot(workspaceRoot);
51
+ if (!packageRoot) {
52
+ console.log(`${COLORS.yellow}[tailjng CLI] WARNING: tailjng package not found — skip colors config copy${COLORS.reset}`);
53
+ return { created: 0, skipped: 0, colorsDir: getProjectColorsDir(appRoot) };
54
+ }
55
+
56
+ const sourceDir = path.join(packageRoot, COLORS_SOURCE_REL);
57
+ const colorsDir = getProjectColorsDir(appRoot);
58
+ let created = 0;
59
+ let skipped = 0;
60
+
61
+ for (const file of COLORS_FILES) {
62
+ const src = path.join(sourceDir, file);
63
+ const dest = path.join(colorsDir, file);
64
+ if (!fs.existsSync(src)) continue;
65
+
66
+ const content = fs.readFileSync(src, 'utf8');
67
+ if (writeFileSafe(dest, content, overwrite)) {
68
+ created += 1;
69
+ console.log(`${COLORS.green}✔ Created ${path.relative(workspaceRoot, dest)}${COLORS.reset}`);
70
+ } else {
71
+ skipped += 1;
72
+ console.log(`${COLORS.dim}↷ Skipped (exists) ${path.relative(workspaceRoot, dest)}${COLORS.reset}`);
73
+ }
74
+ }
75
+
76
+ return { created, skipped, colorsDir };
77
+ }
78
+
79
+ function ensureColorsSafelistImport(styleFilePath, importLine) {
80
+ if (!fs.existsSync(styleFilePath)) return { changed: false };
81
+
82
+ let content = fs.readFileSync(styleFilePath, 'utf8');
83
+ const projectImport = /tailjng\/colors\/colors\.safelist\.css/;
84
+ const npmImport = /node_modules\/tailjng\/src\/colors\.safelist\.css/;
85
+ const anySafelistImport = /@import\s+"[^"]*colors\.safelist\.css";?/;
86
+
87
+ if (projectImport.test(content)) {
88
+ return { changed: false };
89
+ }
90
+
91
+ if (npmImport.test(content) && projectImport.test(importLine)) {
92
+ content = content.replace(anySafelistImport, importLine);
93
+ fs.writeFileSync(styleFilePath, content, 'utf8');
94
+ return { changed: true, migrated: true };
95
+ }
96
+
97
+ if (content.includes('colors.safelist.css')) {
98
+ return { changed: false };
99
+ }
100
+
101
+ const tailwindMatch = content.match(/@(?:import|use)\s+"tailwindcss";?/);
102
+ if (tailwindMatch) {
103
+ const insertAt = tailwindMatch.index + tailwindMatch[0].length;
104
+ content = `${content.slice(0, insertAt)}\n${importLine}\n${content.slice(insertAt)}`;
105
+ } else {
106
+ content = `${importLine}\n${content}`;
107
+ }
108
+
109
+ fs.writeFileSync(styleFilePath, content, 'utf8');
110
+ return { changed: true };
111
+ }
112
+
113
+ function patchTailjngProvidersColors(providersPath) {
114
+ if (!fs.existsSync(providersPath)) return { changed: false };
115
+
116
+ let content = fs.readFileSync(providersPath, 'utf8');
117
+ if (content.includes('tailjngColorsProvider') || content.includes('../tailjng/colors/colors.config')) {
118
+ return { changed: false };
119
+ }
120
+
121
+ const lines = content.split('\n');
122
+ let lastImport = -1;
123
+ for (let i = 0; i < lines.length; i += 1) {
124
+ if (lines[i].startsWith('import ')) lastImport = i;
125
+ }
126
+
127
+ const importLine = "import { tailjngColorsProvider } from '../tailjng/colors/colors.config';";
128
+ if (lastImport === -1) {
129
+ content = `${importLine}\n${content}`;
130
+ } else {
131
+ lines.splice(lastImport + 1, 0, importLine);
132
+ content = lines.join('\n');
133
+ }
134
+
135
+ if (content.includes('export const tailjngProviders = [')) {
136
+ content = content.replace(
137
+ /export const tailjngProviders = \[/,
138
+ 'export const tailjngProviders = [\n tailjngColorsProvider,',
139
+ );
140
+ }
141
+
142
+ fs.writeFileSync(providersPath, content, 'utf8');
143
+ return { changed: true };
144
+ }
145
+
146
+ function patchAppConfigColors(appConfigPath, srcRoot) {
147
+ const providersPath = path.join(srcRoot, 'app', 'config', 'tailjng.providers.ts');
148
+ if (fs.existsSync(providersPath)) {
149
+ return patchTailjngProvidersColors(providersPath);
150
+ }
151
+
152
+ if (!fs.existsSync(appConfigPath)) return { changed: false };
153
+
154
+ let content = fs.readFileSync(appConfigPath, 'utf8');
155
+ if (content.includes('tailjngColorsProvider') || content.includes('./tailjng/colors/colors.config')) {
156
+ return { changed: false };
157
+ }
158
+
159
+ const lines = content.split('\n');
160
+ let lastImport = -1;
161
+ for (let i = 0; i < lines.length; i += 1) {
162
+ if (lines[i].startsWith('import ')) lastImport = i;
163
+ }
164
+
165
+ const importLine = "import { tailjngColorsProvider } from './tailjng/colors/colors.config';";
166
+ if (lastImport === -1) {
167
+ content = `${importLine}\n${content}`;
168
+ } else {
169
+ lines.splice(lastImport + 1, 0, importLine);
170
+ content = lines.join('\n');
171
+ }
172
+
173
+ if (content.includes('providers: [')) {
174
+ content = content.replace(/providers:\s*\[/, 'providers: [\n tailjngColorsProvider,');
175
+ }
176
+
177
+ fs.writeFileSync(appConfigPath, content, 'utf8');
178
+ return { changed: true };
179
+ }
180
+
181
+ module.exports = {
182
+ getProjectColorsDir,
183
+ getColorsSafelistImportLine,
184
+ ensureProjectColorsConfig,
185
+ ensureColorsSafelistImport,
186
+ patchAppConfigColors,
187
+ COLORS_FILES,
188
+ };
@@ -0,0 +1,39 @@
1
+ const { execSync } = require('child_process');
2
+ const { COLORS } = require('./colors');
3
+
4
+ const RUNTIME_PACKAGES = {
5
+ 'lucide-angular': '^0.525.0',
6
+ '@ng-icons/lucide': '>=32.0.0',
7
+ 'date-fns': '^4.1.0',
8
+ 'exceljs': '^4.4.0',
9
+ 'xlsx': '^0.18.5',
10
+ 'file-saver': '^2.0.5',
11
+ };
12
+
13
+ const DEV_PACKAGES = {
14
+ tailwindcss: '^4.0.9',
15
+ '@tailwindcss/postcss': '^4.0.9',
16
+ postcss: '^8.5.3',
17
+ autoprefixer: '^10.4.20',
18
+ };
19
+
20
+ function installPackages(workspaceRoot, packages, isDev = false) {
21
+ const entries = Object.entries(packages);
22
+ if (entries.length === 0) return;
23
+
24
+ const spec = entries.map(([name, version]) => `${name}@${version}`).join(' ');
25
+ const flag = isDev ? '--save-dev' : '--save';
26
+ console.log(`${COLORS.blue}[tailjng CLI] Installing ${isDev ? 'dev ' : ''}dependencies...${COLORS.reset}`);
27
+
28
+ execSync(`npm install ${flag} --legacy-peer-deps ${spec}`.replace(/\s+/g, ' ').trim(), {
29
+ cwd: workspaceRoot,
30
+ stdio: 'inherit',
31
+ shell: true,
32
+ });
33
+ }
34
+
35
+ module.exports = {
36
+ RUNTIME_PACKAGES,
37
+ DEV_PACKAGES,
38
+ installPackages,
39
+ };
@@ -77,29 +77,13 @@ function resolveStyleFilePath(workspaceRoot, styleEntry, appProject) {
77
77
  }
78
78
 
79
79
  function getTailjngSafelistCssImport(workspaceRoot, styleEntry, appProject) {
80
- const stylePath = resolveStyleFilePath(workspaceRoot, styleEntry, appProject);
81
- const safelistPath = path.join(workspaceRoot, 'node_modules', 'tailjng', 'src', 'colors.safelist.css');
82
- let rel = path.relative(path.dirname(stylePath), safelistPath).split(path.sep).join('/');
83
- if (!rel.startsWith('.')) rel = `./${rel}`;
84
- return `@import "${rel}";`;
80
+ const { getColorsSafelistImportLine } = require('./colors-config-utils');
81
+ return getColorsSafelistImportLine(workspaceRoot, styleEntry, appProject);
85
82
  }
86
83
 
87
84
  function ensureTailjngSafelistImport(styleFilePath, importLine) {
88
- if (!fs.existsSync(styleFilePath)) return { changed: false };
89
-
90
- let content = fs.readFileSync(styleFilePath, 'utf8');
91
- if (content.includes('colors.safelist.css')) return { changed: false };
92
-
93
- const tailwindMatch = content.match(/@(?:import|use)\s+"tailwindcss";?/);
94
- if (tailwindMatch) {
95
- const insertAt = tailwindMatch.index + tailwindMatch[0].length;
96
- content = `${content.slice(0, insertAt)}\n${importLine}\n${content.slice(insertAt)}`;
97
- } else {
98
- content = `${importLine}\n${content}`;
99
- }
100
-
101
- fs.writeFileSync(styleFilePath, content, 'utf8');
102
- return { changed: true };
85
+ const { ensureColorsSafelistImport } = require('./colors-config-utils');
86
+ return ensureColorsSafelistImport(styleFilePath, importLine);
103
87
  }
104
88
 
105
89
  function ensureTailjngStylesInAngularJson(angularJsonPath, projectName, styleLanguage) {
@@ -244,15 +228,56 @@ function getMergedDependencies(packageJson) {
244
228
  return { ...packageJson.dependencies, ...packageJson.devDependencies };
245
229
  }
246
230
 
247
- /** Alinea @angular/animations con la línea de @angular/core del proyecto (evita ERESOLVE 19.2.0 vs 19.2.25). */
248
- function resolveRuntimePackages(packageJson, basePackages) {
231
+ function getInstalledPackageVersion(workspaceRoot, name) {
232
+ const pkgPath = path.join(workspaceRoot, 'node_modules', ...name.split('/'), 'package.json');
233
+ if (!fs.existsSync(pkgPath)) return null;
234
+ try {
235
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
241
+ function resolveAngularAnimationsSpec(packageJson, workspaceRoot) {
242
+ const deps = getMergedDependencies(packageJson);
243
+ const coreInstalled = workspaceRoot ? getInstalledPackageVersion(workspaceRoot, '@angular/core') : null;
244
+ if (coreInstalled) return `^${coreInstalled}`;
245
+ if (deps['@angular/core']) return deps['@angular/core'];
246
+ return '^19.2.0';
247
+ }
248
+
249
+ /** Corrige @angular/animations para usar la misma versión que @angular/core del proyecto. */
250
+ function alignAngularAnimationsInPackageJson(workspaceRoot) {
251
+ const pkgPath = path.join(workspaceRoot, 'package.json');
252
+ if (!fs.existsSync(pkgPath)) return { changed: false };
253
+
254
+ const pkg = readJson(pkgPath);
255
+ const deps = pkg.dependencies || {};
256
+ const coreInstalled = getInstalledPackageVersion(workspaceRoot, '@angular/core');
257
+ const coreSpec = deps['@angular/core'];
258
+
259
+ // Preferir versión instalada (ej. 19.2.25); si no, la misma entrada que core en package.json
260
+ const aligned = coreInstalled ? `^${coreInstalled}` : coreSpec;
261
+ if (!aligned) return { changed: false };
262
+
263
+ if (!deps['@angular/animations']) return { changed: false };
264
+ if (deps['@angular/animations'] === aligned) return { changed: false };
265
+
266
+ deps['@angular/animations'] = aligned;
267
+ pkg.dependencies = deps;
268
+ writeJson(pkgPath, pkg);
269
+ return { changed: true, version: aligned };
270
+ }
271
+
272
+ /** Alinea @angular/animations con la versión instalada de @angular/core (evita ERESOLVE 19.2.0 vs 19.2.25). */
273
+ function resolveRuntimePackages(packageJson, basePackages, workspaceRoot = null) {
249
274
  const packages = { ...basePackages };
250
275
  const deps = getMergedDependencies(packageJson);
251
276
 
252
277
  if (deps['@angular/animations']) {
253
278
  delete packages['@angular/animations'];
254
- } else if (deps['@angular/core']) {
255
- packages['@angular/animations'] = deps['@angular/core'];
279
+ } else if (deps['@angular/core'] || (workspaceRoot && getInstalledPackageVersion(workspaceRoot, '@angular/core'))) {
280
+ packages['@angular/animations'] = resolveAngularAnimationsSpec(packageJson, workspaceRoot);
256
281
  }
257
282
 
258
283
  return packages;
@@ -327,6 +352,7 @@ module.exports = {
327
352
  getMergedDependencies,
328
353
  resolveRuntimePackages,
329
354
  resolveDevPackages,
355
+ alignAngularAnimationsInPackageJson,
330
356
  hasTailwindSetup,
331
357
  fileExists,
332
358
  };
@@ -380,4 +380,6 @@ function buildInitFiles(options) {
380
380
  module.exports = {
381
381
  buildInitFiles,
382
382
  buildAppConfigTs,
383
+ buildPathsJson,
384
+ buildPathsReadme,
383
385
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailjng",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^19.2.0",
6
6
  "@angular/core": "^19.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "components": {
3
- "colors-config": {
3
+ "colors": {
4
4
  "path": "src/lib/components/colors-config",
5
5
  "dependencies": []
6
6
  },
@@ -1,4 +1,29 @@
1
- /* AUTO-GENERATED — do not edit. Run: node scripts/generate-colors-safelist.mjs */
2
- /* Tailwind v4 safelist for JColorsService dynamic classes (npm bundle). */
1
+ /* AUTO-GENERATED — do not edit blocks 1-N. Run: node scripts/generate-colors-safelist.mjs */
2
+ /* npm fallback prefer src/app/tailjng/colors/colors.safelist.css in projects */
3
3
 
4
- @source inline("bg-accent/40 bg-background bg-background/80 bg-black/5 bg-blue-50 bg-blue-500 bg-blue-500/10 bg-cyan-500 bg-cyan-500/10 bg-dark-background bg-gray-50 bg-gray-500 bg-gray-500/10 bg-green-50 bg-green-500 bg-green-500/10 bg-none bg-orange-500 bg-orange-500/10 bg-pink-500 bg-pink-500/10 bg-primary bg-primary/10 bg-primary/80 bg-purple-50 bg-purple-500 bg-purple-500/10 bg-red-50 bg-red-500 bg-red-500/10 bg-teal-500 bg-teal-500/10 bg-transparent bg-white bg-yellow-50 bg-yellow-600 bg-yellow-600/10 border border-blue-300 border-blue-500 border-blue-500/20 border-border border-cyan-300 border-cyan-500 border-cyan-500/20 border-dark-border border-dark-border/20 border-gray-400 border-gray-500 border-gray-500/20 border-green-300 border-green-500 border-green-500/20 border-orange-300 border-orange-500 border-orange-500/20 border-pink-300 border-pink-500 border-pink-500/20 border-primary border-primary/20 border-primary/30 border-purple-300 border-purple-400 border-purple-500 border-purple-500/20 border-red-400 border-red-500 border-red-500/20 border-teal-300 border-teal-500 border-teal-500/20 border-transparent border-yellow-500 border-yellow-600 border-yellow-600/20 dark:bg-[#15181e] dark:bg-[#15241f] dark:bg-[#1a1a24] dark:bg-[#1f1c1a] dark:bg-[#21181c] dark:bg-[#241732] dark:bg-dark-accent/30 dark:bg-dark-background dark:bg-dark-background/80 dark:bg-foreground dark:bg-input dark:bg-input/15 dark:bg-input/80 dark:bg-white/10 dark:border-blue-300 dark:border-blue-600 dark:border-border dark:border-border/20 dark:border-cyan-300 dark:border-cyan-600 dark:border-dark-border dark:border-gray-400 dark:border-gray-600 dark:border-green-300 dark:border-green-600 dark:border-input dark:border-input/30 dark:border-input/40 dark:border-orange-300 dark:border-orange-600 dark:border-pink-300 dark:border-pink-600 dark:border-purple-300 dark:border-purple-400 dark:border-purple-600 dark:border-red-400 dark:border-red-600 dark:border-teal-300 dark:border-teal-600 dark:border-transparent dark:border-yellow-500 dark:border-yellow-700 dark:hover:bg-blue-500/15 dark:hover:bg-blue-600/10 dark:hover:bg-border/10 dark:hover:bg-cyan-500/15 dark:hover:bg-cyan-600/10 dark:hover:bg-dark-accent/40 dark:hover:bg-dark-accent/50 dark:hover:bg-dark-background dark:hover:bg-gray-500/15 dark:hover:bg-gray-600/10 dark:hover:bg-green-500/15 dark:hover:bg-green-600/10 dark:hover:bg-input dark:hover:bg-input/15 dark:hover:bg-input/25 dark:hover:bg-input/60 dark:hover:bg-input/80 dark:hover:bg-orange-500/15 dark:hover:bg-orange-600/10 dark:hover:bg-pink-500/15 dark:hover:bg-pink-600/10 dark:hover:bg-purple-500/15 dark:hover:bg-purple-600/10 dark:hover:bg-red-500/15 dark:hover:bg-red-600/10 dark:hover:bg-teal-500/15 dark:hover:bg-teal-600/10 dark:hover:bg-white/10 dark:hover:bg-white/15 dark:hover:bg-yellow-600/15 dark:hover:bg-yellow-700/10 dark:hover:text-white dark:text-blue-400 dark:text-border dark:text-cyan-400 dark:text-gray-400 dark:text-green-400 dark:text-input dark:text-orange-400 dark:text-pink-400 dark:text-purple-400 dark:text-red-400 dark:text-teal-400 dark:text-white dark:text-yellow-400 hover:bg-accent hover:bg-accent/60 hover:bg-accent/70 hover:bg-background hover:bg-black/10 hover:bg-black/5 hover:bg-blue-500/10 hover:bg-blue-500/20 hover:bg-blue-600 hover:bg-cyan-500/10 hover:bg-cyan-500/20 hover:bg-cyan-600 hover:bg-dark-accent/50 hover:bg-dark-border/10 hover:bg-gray-500/10 hover:bg-gray-500/20 hover:bg-gray-600 hover:bg-green-500/10 hover:bg-green-500/20 hover:bg-green-600 hover:bg-orange-500/10 hover:bg-orange-500/20 hover:bg-orange-600 hover:bg-pink-500/10 hover:bg-pink-500/20 hover:bg-pink-600 hover:bg-primary hover:bg-primary/10 hover:bg-primary/20 hover:bg-primary/80 hover:bg-purple-500/10 hover:bg-purple-500/20 hover:bg-purple-600 hover:bg-red-500/10 hover:bg-red-500/20 hover:bg-red-600 hover:bg-teal-500/10 hover:bg-teal-500/20 hover:bg-teal-600 hover:bg-yellow-600/10 hover:bg-yellow-600/20 hover:bg-yellow-700 shadow-md shadow-sm text-black text-blue-500 text-blue-600 text-cyan-500 text-cyan-600 text-dark-border text-gray-500 text-gray-600 text-green-500 text-green-600 text-orange-500 text-orange-600 text-pink-500 text-pink-600 text-primary text-purple-500 text-purple-600 text-red-500 text-red-600 text-teal-500 text-teal-600 text-white text-yellow-500 text-yellow-600 text-yellow-700");
4
+ @layer utilities {
5
+ .__tailjng_colors_safelist_1__ {
6
+ @apply bg-accent/40 bg-background bg-background/80 bg-black/5 bg-blue-50 bg-blue-500 bg-blue-500/10 bg-cyan-500 bg-cyan-500/10 bg-dark-background bg-gray-50 bg-gray-500 bg-gray-500/10 bg-green-50 bg-green-500 bg-green-500/10 bg-none bg-orange-500 bg-orange-500/10 bg-pink-500 bg-pink-500/10 bg-primary bg-primary/10 bg-primary/80 bg-purple-50 bg-purple-500 bg-purple-500/10 bg-red-50 bg-red-500 bg-red-500/10 bg-teal-500 bg-teal-500/10 bg-transparent bg-white bg-yellow-50 bg-yellow-600 bg-yellow-600/10 border border-blue-300 border-blue-500 border-blue-500/20 border-border border-cyan-300 border-cyan-500 border-cyan-500/20 border-dark-border border-dark-border/20 border-gray-400 border-gray-500 border-gray-500/20;
7
+ }
8
+
9
+ .__tailjng_colors_safelist_2__ {
10
+ @apply border-green-300 border-green-500 border-green-500/20 border-orange-300 border-orange-500 border-orange-500/20 border-pink-300 border-pink-500 border-pink-500/20 border-primary border-primary/20 border-primary/30 border-purple-300 border-purple-400 border-purple-500 border-purple-500/20 border-red-400 border-red-500 border-red-500/20 border-teal-300 border-teal-500 border-teal-500/20 border-transparent border-yellow-500 border-yellow-600 border-yellow-600/20 dark:bg-[#15181e] dark:bg-[#15241f] dark:bg-[#1a1a24] dark:bg-[#1f1c1a] dark:bg-[#21181c] dark:bg-[#241732] dark:bg-dark-accent/30 dark:bg-dark-background dark:bg-dark-background/80 dark:bg-foreground dark:bg-input dark:bg-input/15 dark:bg-input/80 dark:bg-white/10 dark:border-blue-300 dark:border-blue-600 dark:border-border dark:border-border/20 dark:border-cyan-300 dark:border-cyan-600 dark:border-dark-border dark:border-gray-400 dark:border-gray-600 dark:border-green-300;
11
+ }
12
+
13
+ .__tailjng_colors_safelist_3__ {
14
+ @apply dark:border-green-600 dark:border-input dark:border-input/30 dark:border-input/40 dark:border-orange-300 dark:border-orange-600 dark:border-pink-300 dark:border-pink-600 dark:border-purple-300 dark:border-purple-400 dark:border-purple-600 dark:border-red-400 dark:border-red-600 dark:border-teal-300 dark:border-teal-600 dark:border-transparent dark:border-yellow-500 dark:border-yellow-700 dark:hover:bg-blue-500/15 dark:hover:bg-blue-600/10 dark:hover:bg-border/10 dark:hover:bg-cyan-500/15 dark:hover:bg-cyan-600/10 dark:hover:bg-dark-accent/40 dark:hover:bg-dark-accent/50 dark:hover:bg-dark-background dark:hover:bg-gray-500/15 dark:hover:bg-gray-600/10 dark:hover:bg-green-500/15 dark:hover:bg-green-600/10 dark:hover:bg-input dark:hover:bg-input/15 dark:hover:bg-input/25 dark:hover:bg-input/60 dark:hover:bg-input/80 dark:hover:bg-orange-500/15 dark:hover:bg-orange-600/10 dark:hover:bg-pink-500/15 dark:hover:bg-pink-600/10 dark:hover:bg-purple-500/15 dark:hover:bg-purple-600/10 dark:hover:bg-red-500/15 dark:hover:bg-red-600/10 dark:hover:bg-teal-500/15 dark:hover:bg-teal-600/10 dark:hover:bg-white/10 dark:hover:bg-white/15 dark:hover:bg-yellow-600/15 dark:hover:bg-yellow-700/10 dark:hover:text-white;
15
+ }
16
+
17
+ .__tailjng_colors_safelist_4__ {
18
+ @apply dark:text-blue-400 dark:text-border dark:text-cyan-400 dark:text-gray-400 dark:text-green-400 dark:text-input dark:text-orange-400 dark:text-pink-400 dark:text-purple-400 dark:text-red-400 dark:text-teal-400 dark:text-white dark:text-yellow-400 hover:bg-accent hover:bg-accent/60 hover:bg-accent/70 hover:bg-background hover:bg-black/10 hover:bg-black/5 hover:bg-blue-500/10 hover:bg-blue-500/20 hover:bg-blue-600 hover:bg-cyan-500/10 hover:bg-cyan-500/20 hover:bg-cyan-600 hover:bg-dark-accent/50 hover:bg-dark-border/10 hover:bg-gray-500/10 hover:bg-gray-500/20 hover:bg-gray-600 hover:bg-green-500/10 hover:bg-green-500/20 hover:bg-green-600 hover:bg-orange-500/10 hover:bg-orange-500/20 hover:bg-orange-600 hover:bg-pink-500/10 hover:bg-pink-500/20 hover:bg-pink-600 hover:bg-primary hover:bg-primary/10 hover:bg-primary/20 hover:bg-primary/80 hover:bg-purple-500/10 hover:bg-purple-500/20 hover:bg-purple-600 hover:bg-red-500/10 hover:bg-red-500/20 hover:bg-red-600 hover:bg-teal-500/10;
19
+ }
20
+
21
+ .__tailjng_colors_safelist_5__ {
22
+ @apply hover:bg-teal-500/20 hover:bg-teal-600 hover:bg-yellow-600/10 hover:bg-yellow-600/20 hover:bg-yellow-700 shadow-md shadow-sm text-black text-blue-500 text-blue-600 text-cyan-500 text-cyan-600 text-dark-border text-gray-500 text-gray-600 text-green-500 text-green-600 text-orange-500 text-orange-600 text-pink-500 text-pink-600 text-primary text-purple-500 text-purple-600 text-red-500 text-red-600 text-teal-500 text-teal-600 text-white text-yellow-500 text-yellow-600 text-yellow-700;
23
+ }
24
+
25
+ /* ── Tus colores custom: descomenta y añade clases Tailwind ── */
26
+ .__tailjng_custom_colors__ {
27
+ /* @apply bg-indigo-600 text-white hover:bg-indigo-700; */
28
+ }
29
+ }
@@ -0,0 +1,38 @@
1
+ # Colores Tailjng — configuración del proyecto
2
+
3
+ Carpeta generada por `init:app`, `sync:app` o `npx tailjng add colors`.
4
+
5
+ ## Archivos
6
+
7
+ | Archivo | Para qué |
8
+ |---------|----------|
9
+ | `colors.config.ts` | Variantes (`primary`, `success_soft`, …) + las tuyas (`brand`, etc.) |
10
+ | `colors.safelist.css` | Clases Tailwind que el build debe generar (`@apply`) |
11
+
12
+ ## Añadir un color propio
13
+
14
+ **1.** En `colors.config.ts`:
15
+
16
+ ```typescript
17
+ brand: 'bg-indigo-600 text-white hover:bg-indigo-700 border border-indigo-500 shadow-md',
18
+ brand_soft: 'bg-indigo-500/10 text-indigo-600 border border-indigo-500/20 shadow-sm',
19
+ ```
20
+
21
+ **2.** En `colors.safelist.css`, dentro del bloque `@layer utilities`:
22
+
23
+ ```css
24
+ .__tailjng_custom_colors__ {
25
+ @apply bg-indigo-600 text-white hover:bg-indigo-700 border-indigo-500 bg-indigo-500/10 text-indigo-600;
26
+ }
27
+ ```
28
+
29
+ **3.** Uso en componentes:
30
+
31
+ ```html
32
+ <JButton classes="brand" text="Guardar" />
33
+ <JBadge classes="brand_soft" value="N" />
34
+ ```
35
+
36
+ ## Provider
37
+
38
+ `app.config.ts` debe incluir `tailjngColorsProvider` (lo añade `init:app` / `sync:app`).
@@ -1,19 +1,112 @@
1
1
  import { TAILJNG_COLORS_CONFIG } from 'tailjng';
2
2
 
3
3
  /**
4
- * Variantes de color personalizadas del proyecto.
5
- * Uso en componentes: classes="brand" o classes="brand_soft"
4
+ * Variantes de color edita las existentes o añade las tuyas al final.
5
+ * Uso en componentes: classes="primary" | classes="success_soft" | classes="brand"
6
6
  *
7
- * Importante: cada clase Tailwind que uses aquí debe estar también en colors.safelist.css
8
- * (@source inline) para que el build las genere.
7
+ * Si añades variantes nuevas con clases Tailwind nuevas, regístralas también
8
+ * en colors.safelist.css (bloque .__tailjng_custom_colors__).
9
9
  */
10
- export const customColorVariants: Record<string, string> = {
10
+ export const colorVariants: Record<string, string> = {
11
+
12
+ primary: 'bg-primary dark:bg-input text-white dark:text-white hover:bg-primary/80 dark:hover:bg-input/60 dark:hover:text-white shadow-md border border-dark-border dark:border-border',
13
+ primary_soft: 'bg-primary/10 dark:bg-input/15 text-primary dark:text-input hover:bg-primary/20 dark:hover:bg-input/25 border border-primary/20 dark:border-input/30 shadow-sm',
14
+ primary_outline: 'bg-transparent text-primary dark:text-input border border-primary dark:border-input hover:bg-primary/10 dark:hover:bg-input/15 shadow-sm',
15
+ primary_ghost: 'bg-transparent text-primary dark:text-input hover:bg-primary/10 dark:hover:bg-input/15 border border-transparent dark:border-transparent',
16
+ primary_light: 'bg-primary/80 dark:bg-input/80 text-white hover:bg-primary dark:hover:bg-input border border-primary/30 dark:border-input/40 shadow-md',
17
+ primary_dark: 'bg-primary text-white dark:bg-input hover:bg-primary/80 dark:hover:bg-input/80 border border-dark-border dark:border-border shadow-md',
18
+
19
+ primary_secondary: 'bg-none text-dark-border dark:text-border border border-dark-border dark:border-border hover:bg-dark-border/10 dark:hover:bg-border/10 shadow-md',
20
+
21
+ secondary: 'bg-background dark:bg-dark-background text-black border border-dark-border dark:border-border dark:text-white hover:bg-accent dark:hover:bg-dark-accent/50',
22
+ secondary_soft: 'bg-accent/40 dark:bg-dark-accent/30 text-black dark:text-white border border-border dark:border-dark-border hover:bg-accent/70 dark:hover:bg-dark-accent/50 shadow-sm',
23
+ secondary_outline: 'bg-transparent text-black dark:text-white border border-border dark:border-dark-border hover:bg-accent dark:hover:bg-dark-accent/40 shadow-sm',
24
+ secondary_ghost: 'bg-transparent text-black dark:text-white hover:bg-accent/60 dark:hover:bg-dark-accent/40 border border-transparent dark:border-transparent',
25
+ secondary_light: 'bg-background/80 dark:bg-dark-background/80 text-black dark:text-white border border-border dark:border-dark-border hover:bg-background dark:hover:bg-dark-background shadow-sm',
26
+ secondary_dark: 'bg-dark-background text-white border border-dark-border hover:bg-dark-accent/50 shadow-md',
27
+
28
+ success: 'bg-green-500 hover:bg-green-600 text-white border border-green-300 dark:border-green-300 shadow-md',
29
+ success_secondary: 'bg-none text-green-500 border border-green-500 dark:border-green-600 hover:bg-green-500/10 dark:hover:bg-green-600/10 shadow-md',
30
+ success_soft: 'bg-green-500/10 text-green-600 dark:text-green-400 border border-green-500/20 hover:bg-green-500/20 dark:hover:bg-green-500/15 shadow-sm',
31
+ success_outline: 'bg-transparent text-green-500 border border-green-500 hover:bg-green-500/10 shadow-sm',
32
+ success_ghost: 'bg-transparent text-green-500 hover:bg-green-500/10 border border-transparent dark:border-transparent',
33
+
34
+ info: 'bg-blue-500 hover:bg-blue-600 text-white border border-blue-300 dark:border-blue-300 shadow-md',
35
+ info_secondary: 'bg-none text-blue-500 border border-blue-500 dark:border-blue-600 hover:bg-blue-500/10 dark:hover:bg-blue-600/10 shadow-md',
36
+ info_soft: 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20 hover:bg-blue-500/20 dark:hover:bg-blue-500/15 shadow-sm',
37
+ info_outline: 'bg-transparent text-blue-500 border border-blue-500 hover:bg-blue-500/10 shadow-sm',
38
+ info_ghost: 'bg-transparent text-blue-500 hover:bg-blue-500/10 border border-transparent dark:border-transparent',
39
+
40
+ warning: 'bg-yellow-600 hover:bg-yellow-700 text-white border border-yellow-500 dark:border-yellow-500 shadow-md',
41
+ warning_secondary: 'bg-none text-yellow-600 border border-yellow-600 dark:border-yellow-700 hover:bg-yellow-600/10 dark:hover:bg-yellow-700/10 shadow-md',
42
+ warning_soft: 'bg-yellow-600/10 text-yellow-700 dark:text-yellow-400 border border-yellow-600/20 hover:bg-yellow-600/20 dark:hover:bg-yellow-600/15 shadow-sm',
43
+ warning_outline: 'bg-transparent text-yellow-600 border border-yellow-600 hover:bg-yellow-600/10 shadow-sm',
44
+ warning_ghost: 'bg-transparent text-yellow-600 hover:bg-yellow-600/10 border border-transparent dark:border-transparent',
45
+
46
+ question: 'bg-purple-500 hover:bg-purple-600 text-white border border-purple-400 dark:border-purple-400 shadow-md',
47
+ question_secondary: 'bg-none text-purple-500 border border-purple-500 dark:border-purple-600 hover:bg-purple-500/10 dark:hover:bg-purple-600/10 shadow-md',
48
+ question_soft: 'bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20 hover:bg-purple-500/20 dark:hover:bg-purple-500/15 shadow-sm',
49
+ question_outline: 'bg-transparent text-purple-500 border border-purple-500 hover:bg-purple-500/10 shadow-sm',
50
+ question_ghost: 'bg-transparent text-purple-500 hover:bg-purple-500/10 border border-transparent dark:border-transparent',
51
+
52
+ error: 'bg-red-500 hover:bg-red-600 text-white border border-red-400 dark:border-red-400 shadow-md',
53
+ error_secondary: 'bg-none text-red-500 border border-red-500 dark:border-red-600 hover:bg-red-500/10 dark:hover:bg-red-600/10 shadow-md',
54
+ error_soft: 'bg-red-500/10 text-red-600 dark:text-red-400 border border-red-500/20 hover:bg-red-500/20 dark:hover:bg-red-500/15 shadow-sm',
55
+ error_outline: 'bg-transparent text-red-500 border border-red-500 hover:bg-red-500/10 shadow-sm',
56
+ error_ghost: 'bg-transparent text-red-500 hover:bg-red-500/10 border border-transparent dark:border-transparent',
57
+
58
+ loading: 'bg-gray-500 hover:bg-gray-600 text-white border border-gray-400 dark:border-gray-400 shadow-md',
59
+ loading_secondary: 'bg-none text-gray-500 border border-gray-500 dark:border-gray-600 hover:bg-gray-500/10 dark:hover:bg-gray-600/10 shadow-md',
60
+ loading_soft: 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border border-gray-500/20 hover:bg-gray-500/20 dark:hover:bg-gray-500/15 shadow-sm',
61
+ loading_outline: 'bg-transparent text-gray-500 border border-gray-500 hover:bg-gray-500/10 shadow-sm',
62
+ loading_ghost: 'bg-transparent text-gray-500 hover:bg-gray-500/10 border border-transparent dark:border-transparent',
63
+
64
+ orange: 'bg-orange-500 hover:bg-orange-600 text-white border border-orange-300 dark:border-orange-300 shadow-md',
65
+ orange_secondary: 'bg-none text-orange-500 border border-orange-500 dark:border-orange-600 hover:bg-orange-500/10 dark:hover:bg-orange-600/10 shadow-md',
66
+ orange_soft: 'bg-orange-500/10 text-orange-600 dark:text-orange-400 border border-orange-500/20 hover:bg-orange-500/20 dark:hover:bg-orange-500/15 shadow-sm',
67
+ orange_outline: 'bg-transparent text-orange-500 border border-orange-500 hover:bg-orange-500/10 shadow-sm',
68
+ orange_ghost: 'bg-transparent text-orange-500 hover:bg-orange-500/10 border border-transparent dark:border-transparent',
69
+
70
+ cyan: 'bg-cyan-500 hover:bg-cyan-600 text-white border border-cyan-300 dark:border-cyan-300 shadow-md',
71
+ cyan_secondary: 'bg-none text-cyan-500 border border-cyan-500 dark:border-cyan-600 hover:bg-cyan-500/10 dark:hover:bg-cyan-600/10 shadow-md',
72
+ cyan_soft: 'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border border-cyan-500/20 hover:bg-cyan-500/20 dark:hover:bg-cyan-500/15 shadow-sm',
73
+ cyan_outline: 'bg-transparent text-cyan-500 border border-cyan-500 hover:bg-cyan-500/10 shadow-sm',
74
+ cyan_ghost: 'bg-transparent text-cyan-500 hover:bg-cyan-500/10 border border-transparent dark:border-transparent',
75
+
76
+ purple: 'bg-purple-500 hover:bg-purple-600 text-white border border-purple-300 dark:border-purple-300 shadow-md',
77
+ purple_secondary: 'bg-none text-purple-500 border border-purple-500 dark:border-purple-600 hover:bg-purple-500/10 dark:hover:bg-purple-600/10 shadow-md',
78
+ purple_soft: 'bg-purple-500/10 text-purple-600 dark:text-purple-400 border border-purple-500/20 hover:bg-purple-500/20 dark:hover:bg-purple-500/15 shadow-sm',
79
+ purple_outline: 'bg-transparent text-purple-500 border border-purple-500 hover:bg-purple-500/10 shadow-sm',
80
+ purple_ghost: 'bg-transparent text-purple-500 hover:bg-purple-500/10 border border-transparent dark:border-transparent',
81
+
82
+ teal: 'bg-teal-500 hover:bg-teal-600 text-white border border-teal-300 dark:border-teal-300 shadow-md',
83
+ teal_secondary: 'bg-none text-teal-500 border border-teal-500 dark:border-teal-600 hover:bg-teal-500/10 dark:hover:bg-teal-600/10 shadow-md',
84
+ teal_soft: 'bg-teal-500/10 text-teal-600 dark:text-teal-400 border border-teal-500/20 hover:bg-teal-500/20 dark:hover:bg-teal-500/15 shadow-sm',
85
+ teal_outline: 'bg-transparent text-teal-500 border border-teal-500 hover:bg-teal-500/10 shadow-sm',
86
+ teal_ghost: 'bg-transparent text-teal-500 hover:bg-teal-500/10 border border-transparent dark:border-transparent',
87
+
88
+ pink: 'bg-pink-500 hover:bg-pink-600 text-white border border-pink-300 dark:border-pink-300 shadow-md',
89
+ pink_secondary: 'bg-none text-pink-500 border border-pink-500 dark:border-pink-600 hover:bg-pink-500/10 dark:hover:bg-pink-600/10 shadow-md',
90
+ pink_soft: 'bg-pink-500/10 text-pink-600 dark:text-pink-400 border border-pink-500/20 hover:bg-pink-500/20 dark:hover:bg-pink-500/15 shadow-sm',
91
+ pink_outline: 'bg-transparent text-pink-500 border border-pink-500 hover:bg-pink-500/10 shadow-sm',
92
+ pink_ghost: 'bg-transparent text-pink-500 hover:bg-pink-500/10 border border-transparent dark:border-transparent',
93
+
94
+ green: 'bg-green-500 hover:bg-green-600 text-white border border-green-300 dark:border-green-300 shadow-md',
95
+ green_secondary: 'bg-none text-green-500 border border-green-500 dark:border-green-600 hover:bg-green-500/10 dark:hover:bg-green-600/10 shadow-md',
96
+ green_soft: 'bg-green-500/10 text-green-600 dark:text-green-400 border border-green-500/20 hover:bg-green-500/20 dark:hover:bg-green-500/15 shadow-sm',
97
+ green_outline: 'bg-transparent text-green-500 border border-green-500 hover:bg-green-500/10 shadow-sm',
98
+ green_ghost: 'bg-transparent text-green-500 hover:bg-green-500/10 border border-transparent dark:border-transparent',
99
+
100
+ default: 'text-black dark:text-white shadow-md border border-dark-border dark:border-border',
101
+ default_soft: 'bg-black/5 dark:bg-white/10 text-black dark:text-white border border-dark-border/20 dark:border-border/20 hover:bg-black/10 dark:hover:bg-white/15 shadow-sm',
102
+ default_outline: 'bg-transparent text-black dark:text-white border border-dark-border dark:border-border hover:bg-black/5 dark:hover:bg-white/10 shadow-sm',
103
+ default_ghost: 'bg-transparent text-black dark:text-white hover:bg-black/5 dark:hover:bg-white/10 border border-transparent dark:border-transparent',
104
+
105
+ // ── Custom — añade variantes propias debajo ──
11
106
  // brand: 'bg-indigo-600 text-white hover:bg-indigo-700 border border-indigo-500 shadow-md',
12
107
  };
13
108
 
14
109
  export const tailjngColorsProvider = {
15
110
  provide: TAILJNG_COLORS_CONFIG,
16
- useValue: {
17
- variants: customColorVariants,
18
- },
111
+ useValue: { variants: colorVariants },
19
112
  };
@@ -1,7 +1,29 @@
1
- /**
2
- * Safelist Tailwind para variantes personalizadas del proyecto.
3
- * Añade tus clases al @source inline (Tailwind v4 las incluirá en el build).
4
- *
5
- * Generado por: npx tailjng add colors-config
6
- */
7
- @source inline("");
1
+ /* AUTO-GENERATED — do not edit blocks 1-N. Run: node scripts/generate-colors-safelist.mjs */
2
+ /* Importar en styles.css DESPUÉS de @import "tailwindcss". */
3
+
4
+ @layer utilities {
5
+ .__tailjng_colors_safelist_1__ {
6
+ @apply bg-accent/40 bg-background bg-background/80 bg-black/5 bg-blue-50 bg-blue-500 bg-blue-500/10 bg-cyan-500 bg-cyan-500/10 bg-dark-background bg-gray-50 bg-gray-500 bg-gray-500/10 bg-green-50 bg-green-500 bg-green-500/10 bg-none bg-orange-500 bg-orange-500/10 bg-pink-500 bg-pink-500/10 bg-primary bg-primary/10 bg-primary/80 bg-purple-50 bg-purple-500 bg-purple-500/10 bg-red-50 bg-red-500 bg-red-500/10 bg-teal-500 bg-teal-500/10 bg-transparent bg-white bg-yellow-50 bg-yellow-600 bg-yellow-600/10 border border-blue-300 border-blue-500 border-blue-500/20 border-border border-cyan-300 border-cyan-500 border-cyan-500/20 border-dark-border border-dark-border/20 border-gray-400 border-gray-500 border-gray-500/20;
7
+ }
8
+
9
+ .__tailjng_colors_safelist_2__ {
10
+ @apply border-green-300 border-green-500 border-green-500/20 border-orange-300 border-orange-500 border-orange-500/20 border-pink-300 border-pink-500 border-pink-500/20 border-primary border-primary/20 border-primary/30 border-purple-300 border-purple-400 border-purple-500 border-purple-500/20 border-red-400 border-red-500 border-red-500/20 border-teal-300 border-teal-500 border-teal-500/20 border-transparent border-yellow-500 border-yellow-600 border-yellow-600/20 dark:bg-[#15181e] dark:bg-[#15241f] dark:bg-[#1a1a24] dark:bg-[#1f1c1a] dark:bg-[#21181c] dark:bg-[#241732] dark:bg-dark-accent/30 dark:bg-dark-background dark:bg-dark-background/80 dark:bg-foreground dark:bg-input dark:bg-input/15 dark:bg-input/80 dark:bg-white/10 dark:border-blue-300 dark:border-blue-600 dark:border-border dark:border-border/20 dark:border-cyan-300 dark:border-cyan-600 dark:border-dark-border dark:border-gray-400 dark:border-gray-600 dark:border-green-300;
11
+ }
12
+
13
+ .__tailjng_colors_safelist_3__ {
14
+ @apply dark:border-green-600 dark:border-input dark:border-input/30 dark:border-input/40 dark:border-orange-300 dark:border-orange-600 dark:border-pink-300 dark:border-pink-600 dark:border-purple-300 dark:border-purple-400 dark:border-purple-600 dark:border-red-400 dark:border-red-600 dark:border-teal-300 dark:border-teal-600 dark:border-transparent dark:border-yellow-500 dark:border-yellow-700 dark:hover:bg-blue-500/15 dark:hover:bg-blue-600/10 dark:hover:bg-border/10 dark:hover:bg-cyan-500/15 dark:hover:bg-cyan-600/10 dark:hover:bg-dark-accent/40 dark:hover:bg-dark-accent/50 dark:hover:bg-dark-background dark:hover:bg-gray-500/15 dark:hover:bg-gray-600/10 dark:hover:bg-green-500/15 dark:hover:bg-green-600/10 dark:hover:bg-input dark:hover:bg-input/15 dark:hover:bg-input/25 dark:hover:bg-input/60 dark:hover:bg-input/80 dark:hover:bg-orange-500/15 dark:hover:bg-orange-600/10 dark:hover:bg-pink-500/15 dark:hover:bg-pink-600/10 dark:hover:bg-purple-500/15 dark:hover:bg-purple-600/10 dark:hover:bg-red-500/15 dark:hover:bg-red-600/10 dark:hover:bg-teal-500/15 dark:hover:bg-teal-600/10 dark:hover:bg-white/10 dark:hover:bg-white/15 dark:hover:bg-yellow-600/15 dark:hover:bg-yellow-700/10 dark:hover:text-white;
15
+ }
16
+
17
+ .__tailjng_colors_safelist_4__ {
18
+ @apply dark:text-blue-400 dark:text-border dark:text-cyan-400 dark:text-gray-400 dark:text-green-400 dark:text-input dark:text-orange-400 dark:text-pink-400 dark:text-purple-400 dark:text-red-400 dark:text-teal-400 dark:text-white dark:text-yellow-400 hover:bg-accent hover:bg-accent/60 hover:bg-accent/70 hover:bg-background hover:bg-black/10 hover:bg-black/5 hover:bg-blue-500/10 hover:bg-blue-500/20 hover:bg-blue-600 hover:bg-cyan-500/10 hover:bg-cyan-500/20 hover:bg-cyan-600 hover:bg-dark-accent/50 hover:bg-dark-border/10 hover:bg-gray-500/10 hover:bg-gray-500/20 hover:bg-gray-600 hover:bg-green-500/10 hover:bg-green-500/20 hover:bg-green-600 hover:bg-orange-500/10 hover:bg-orange-500/20 hover:bg-orange-600 hover:bg-pink-500/10 hover:bg-pink-500/20 hover:bg-pink-600 hover:bg-primary hover:bg-primary/10 hover:bg-primary/20 hover:bg-primary/80 hover:bg-purple-500/10 hover:bg-purple-500/20 hover:bg-purple-600 hover:bg-red-500/10 hover:bg-red-500/20 hover:bg-red-600 hover:bg-teal-500/10;
19
+ }
20
+
21
+ .__tailjng_colors_safelist_5__ {
22
+ @apply hover:bg-teal-500/20 hover:bg-teal-600 hover:bg-yellow-600/10 hover:bg-yellow-600/20 hover:bg-yellow-700 shadow-md shadow-sm text-black text-blue-500 text-blue-600 text-cyan-500 text-cyan-600 text-dark-border text-gray-500 text-gray-600 text-green-500 text-green-600 text-orange-500 text-orange-600 text-pink-500 text-pink-600 text-primary text-purple-500 text-purple-600 text-red-500 text-red-600 text-teal-500 text-teal-600 text-white text-yellow-500 text-yellow-600 text-yellow-700;
23
+ }
24
+
25
+ /* ── Tus colores custom: descomenta y añade clases Tailwind ── */
26
+ .__tailjng_custom_colors__ {
27
+ /* @apply bg-indigo-600 text-white hover:bg-indigo-700; */
28
+ }
29
+ }
Binary file
@@ -1,12 +0,0 @@
1
- import { Component } from '@angular/core';
2
-
3
- /**
4
- * Configuración de colores/variantes Tailwind del proyecto (sin UI).
5
- * Los archivos útiles son colors.config.ts y colors.safelist.css en esta carpeta.
6
- */
7
- @Component({
8
- selector: 'j-colors-config',
9
- standalone: true,
10
- template: '',
11
- })
12
- export class JColorsConfigComponent {}
package/tailjng-0.1.2.tgz DELETED
Binary file