diff --git a/.npmrc.example b/.npmrc.example
index d3df147..3416dc1 100644
--- a/.npmrc.example
+++ b/.npmrc.example
@@ -7,13 +7,13 @@
# Redirige solo el scope @pi al repositorio npm (hosted) de Nexus.
# Cambia la URL por la de tu instancia y el nombre de tu repo npm-hosted.
-@pi:registry=https://nexus.tuempresa.com/repository/npm-private/
+@pi:registry=https://nexus.pireon.dev/repository/npm-private/
# Autenticación por token (recomendado, funciona igual en local y en CI).
# El token se genera en Nexus: usuario > Security > NuGet/npm token, o vía
-# `npm login --registry=https://nexus.tuempresa.com/repository/npm-private/`
+# `npm login --registry=https://nexus.pireon.dev/repository/npm-private/`
# (esto último escribe la línea de abajo automáticamente en tu ~/.npmrc).
-//nexus.tuempresa.com/repository/npm-private/:_authToken=${NEXUS_NPM_TOKEN}
+//nexus.pireon.dev/repository/npm-private/:_authToken=${NEXUS_NPM_TOKEN}
# Nexus no es el registro público de npm: sin este flag, `npm publish`
# rechaza paquetes con scope si no lleva --access, y con un registro privado
diff --git a/.stylelintrc.json b/.stylelintrc.json
new file mode 100644
index 0000000..f87cdca
--- /dev/null
+++ b/.stylelintrc.json
@@ -0,0 +1,13 @@
+{
+ "plugins": ["./tools/stylelint-plugin-pi-theme/no-raw-color-token.mjs"],
+ "customSyntax": "postcss-scss",
+ "ignoreFiles": [
+ "libs/ui/src/lib/styles/themes/**/*.scss",
+ "libs/ui/src/lib/styles/common/abstracts/**/*.scss",
+ "dist/**",
+ "node_modules/**"
+ ],
+ "rules": {
+ "pi/no-raw-color-token": true
+ }
+}
diff --git a/apps/playground/src/app/app.html b/apps/playground/src/app/app.html
index 3497039..288ee4c 100644
--- a/apps/playground/src/app/app.html
+++ b/apps/playground/src/app/app.html
@@ -16,9 +16,9 @@
{{ option.label }}
@@ -26,7 +26,7 @@
Modo oscuro
-
+
@@ -43,8 +43,8 @@
- Tema: {{ theme() }} ·
- {{ shellClassByTheme[theme()] }}
+ Tema: {{ themeService.theme() }} ·
+ {{ themeService.shellClass() }}
en el <body>. Los cuatro temas comparten exactamente los
mismos selectores: solo cambian los tokens --pi-color-* y
--pi-theme-* que definen themes/_default.scss,
diff --git a/apps/playground/src/app/app.ts b/apps/playground/src/app/app.ts
index 4b8d232..b2b9821 100644
--- a/apps/playground/src/app/app.ts
+++ b/apps/playground/src/app/app.ts
@@ -1,14 +1,12 @@
-import { DOCUMENT } from '@angular/common';
-import {
- ChangeDetectionStrategy,
- Component,
- effect,
- inject,
- signal,
-} from '@angular/core';
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
-import { PiLoaderBarComponent, PiSlideToggleComponent, ShellClass } from '@pi/ui';
+import {
+ PiLoaderBarComponent,
+ PiSlideToggleComponent,
+ PiThemeService,
+ ThemeVariant,
+} from '@pi/ui';
/**
* Shell del playground: cabecera, navegación y estado de tema. El contenido de
@@ -38,9 +36,12 @@ export class App {
/* --- Tema y modo oscuro -------------------------------------------------- */
- private readonly document = inject(DOCUMENT);
-
- protected readonly darkMode = signal(false);
+ /**
+ * Aplicar `pi-shell pi-shell__` en `` y `dark-mode` en ``
+ * (con persistencia en `localStorage`) vive en `PiThemeService`, no aquí:
+ * cualquier app que consuma `@pi/ui` necesita este mismo cableado.
+ */
+ protected readonly themeService = inject(PiThemeService);
protected readonly themeOptions: ReadonlyArray<{
value: ThemeVariant;
@@ -51,34 +52,4 @@ export class App {
{ value: 'minimal', label: 'Minimal' },
{ value: 'gradient', label: 'Gradient' },
];
-
- /**
- * Tema visual de la librería. `ShellClass` devuelve las dos clases que hay
- * que poner juntas (`pi-shell pi-shell__`); van en el `` porque
- * los overlays de Material (diálogos, snackbars) se montan fuera de
- * `` y también deben heredarlas.
- */
- protected readonly theme = signal('default');
-
- protected readonly shellClassByTheme: Record = {
- default: ShellClass.Default,
- alternative: ShellClass.Alternative,
- minimal: ShellClass.Minimal,
- gradient: ShellClass.Gradient,
- };
-
- constructor() {
- effect(() => {
- this.document.documentElement.classList.toggle(
- 'dark-mode',
- this.darkMode(),
- );
- });
-
- effect(() => {
- this.document.body.className = this.shellClassByTheme[this.theme()];
- });
- }
}
-
-type ThemeVariant = 'default' | 'alternative' | 'minimal' | 'gradient';
diff --git a/libs/ui/README.md b/libs/ui/README.md
index acdd8b3..e384b20 100644
--- a/libs/ui/README.md
+++ b/libs/ui/README.md
@@ -80,6 +80,24 @@ en custom properties (`--pi-color-*`, `--pi-theme-*`) aplicadas vía `ShellClass
tema (Default/Alternative/Minimal/Gradient) y guía para añadir tokens o temas
nuevos: [`TOKENS.md`](./TOKENS.md).
+Tres piezas pensadas para que reskinear la librería en otro proyecto sea
+barato:
+
+- **`PiThemeService`** (`util/theme`) — inyectable, con `theme`/`darkMode`
+ como señales normales (mutables con setters o con `[(value)]`/`[(checked)]`
+ directo), persistencia en `localStorage` y aplicación de clases vía
+ `effect()`. Sustituye al cableado manual que antes vivía en cada app
+ consumidora (ver el `app.ts` del playground antes/después de esta
+ extracción).
+- **`nx g @pi/ui:theme `** — genera `_.scss`, lo registra en
+ `themes/index.scss`, añade la entrada a `ShellClass` y extiende
+ `PiThemeService`; el tema queda seleccionable sin pasos manuales.
+- **`nx run ui:lint-styles`** — regla de stylelint (`pi/no-raw-color-token`)
+ que detecta un `--pi-color-*` usado fuera del fallback de un
+ `--pi-theme-*`, la misma clase de bug que dejó colores de marca fijos en
+ modo oscuro (accordion/tab/select-option, antes de tokenizarlos). Target
+ aparte por ahora — ver TOKENS.md para el porqué y el estado de adopción.
+
| Componente | Contrato | Tipo del valor |
| ---------------------------------------------------- | --------------------- | ------------------------------------- |
| text, email, password, phone, icon, search, textarea | `FormValueControl` | `string` |
diff --git a/libs/ui/TOKENS.md b/libs/ui/TOKENS.md
index 1d5e214..7026b23 100644
--- a/libs/ui/TOKENS.md
+++ b/libs/ui/TOKENS.md
@@ -20,10 +20,31 @@ componente — solo redefine tokens (ver `_alternative.scss`, `_minimal.scss`,
## Cómo se aplica un tema
+La forma recomendada es `PiThemeService` — inyectable, con persistencia en
+`localStorage` y sin tener que tocar el DOM a mano:
+
+```ts
+import { inject } from '@angular/core';
+import { PiThemeService } from '@pi/ui';
+
+const themeService = inject(PiThemeService);
+
+themeService.setTheme('alternative'); // pinta pi-shell pi-shell__alternative en
+themeService.toggleDarkMode(); // pinta/quita dark-mode en
+```
+
+`theme`/`darkMode` son señales normales: también se pueden mutar con un
+`[(value)]`/`[(checked)]` dos vías directo sobre la señal (ver el uso real en
+`apps/playground/src/app/app.ts`/`app.html`). Por debajo, cada cambio dispara
+un `effect()` que aplica la clase y persiste — cualquier camino de escritura
+queda cubierto por igual, no solo los setters.
+
+Si no quieres el servicio (p. ej. fuera de un contexto de inyección), el
+mecanismo de bajo nivel sigue disponible:
+
```ts
import { ShellClass } from '@pi/ui';
-// en el elemento raíz (normalmente o el contenedor de la app)
document.body.className = ShellClass.Alternative; // 'pi-shell pi-shell__alternative'
```
@@ -122,6 +143,8 @@ siempre el *fallback* de `var()` (el valor que tenían antes de tokenizar).
| `--pi-theme-tooltip-radius` | `var(--pi-border-radius-medium)` | `var(--pi-border-radius-xx-small)` | `_mat-tooltip.scss` |
| `--pi-theme-tooltip-shadow` | `var(--pi-box-shadow-panel-double)` | `none` | `_mat-tooltip.scss` |
| `--pi-theme-accordion-radius` | `var(--pi-border-radius-large)` | `var(--pi-border-radius-xx-small)` | `accordion.component.scss`, `_horizontal-accordion.scss`, `_vertical-accordion.scss` (×6, incluye un `calc(... - 0.0625rem)`) |
+| `--pi-theme-overlay-divider-color` | `var(--pi-color-grey-100)` | — | `drawer.component.scss`, `hub.component.scss` (borde bajo la cabecera) |
+| `--pi-theme-hub-grabber-color` | `var(--pi-color-grey-100)` | — | `hub.component.scss` (el asa superior) |
### Puente con Angular Material (`--mat-sys-*`)
@@ -236,12 +259,55 @@ compilación y no forman parte del contrato de theming en runtime.
## Cómo añadir un tema nuevo
+La forma recomendada es el generador:
+
+```bash
+nx g @pi/ui:theme mi-marca
+```
+
+Crea `libs/ui/src/lib/styles/themes/_mi-marca.scss` (en blanco, con los
+grupos de tokens más habituales comentados a modo de chuleta), lo registra en
+`themes/index.scss`, añade la entrada a `ShellClass` en `styles/configs.ts` y
+extiende `ThemeVariant`/`PiThemeService` — el tema queda seleccionable con
+`themeService.setTheme('mi-marca')` sin ningún paso manual más. Solo falta
+descomentar y ajustar los tokens que definen el aspecto, y (si tu app expone un
+selector de tema) añadirlo a esa lista.
+
+Estos son los pasos que da por ti, si prefieres hacerlo a mano o entender qué
+toca:
+
1. Crea `libs/ui/src/lib/styles/themes/_.scss` con un mixin
`ThemeTokens` que redefina solo los tokens `--pi-theme-*` (y, si
aplica, `--pi-color-*`) que cambian respecto a Default.
2. Aplícalo a `.pi-shell__`.
3. Impórtalo desde `themes/index.scss`.
4. Añade la entrada a `ShellClass` en `styles/configs.ts`.
+5. Añade `''` a `ThemeVariant` y una entrada a `shellClassByVariant`
+ en `util/theme/theme.service.ts`.
No hace falta tocar ningún componente: si consumen los tokens en vez de
valores literales, el tema nuevo se propaga solo.
+
+## Lint de tokens: evitar que un color de marca se filtre sin querer
+
+El bug que motivó tokenizar accordion/tab/select-option/drawer/hub esta misma
+sesión (un `var(--pi-color-*)` a pelo en el componente, así que ningún tema ni
+el modo oscuro puede redefinir esa superficie) tiene ahora una regla de
+stylelint que lo detecta automáticamente: `pi/no-raw-color-token`
+(`tools/stylelint-plugin-pi-theme/`).
+
+```bash
+nx run ui:lint-styles
+```
+
+Marca cualquier `--pi-color-*` que aparezca fuera del *fallback* de un
+`var(--pi-theme-*, ...)` — es decir, exactamente el patrón contrario al de
+["El patrón de fallback"](#el-patrón-de-fallback). Están exentos
+`styles/themes/**` (ahí `--pi-color-*` es legítimo: son las definiciones) y
+`styles/common/abstracts/**` (donde se declaran las primitivas de la paleta).
+
+**Estado actual: es un target aparte, no está enganchado a `nx lint` ni a CI.**
+Al escribirse, señala ~500 usos preexistentes repartidos por componentes que
+esta sesión no llegó a tokenizar — adoptarlo como gate exige ir migrándolos
+primero (mismo patrón que en "Cómo tokenizar una superficie nueva"). Úsalo para
+no introducir *más* mientras se migra el resto.
diff --git a/libs/ui/TOKENS.pdf b/libs/ui/TOKENS.pdf
index 020e553..7426581 100644
Binary files a/libs/ui/TOKENS.pdf and b/libs/ui/TOKENS.pdf differ
diff --git a/libs/ui/docs/theming.html b/libs/ui/docs/theming.html
index 4aa6558..6733f1d 100644
--- a/libs/ui/docs/theming.html
+++ b/libs/ui/docs/theming.html
@@ -454,19 +454,32 @@
--pi-theme-tooltip-radiussin declarar → var(--pi-border-radius-medium)_mat-tooltip.scss
--pi-theme-tooltip-shadowsin declarar → var(--pi-box-shadow-panel-double)_mat-tooltip.scss
--pi-theme-accordion-radiussin declarar → var(--pi-border-radius-large)accordion.component.scss, _horizontal-accordion.scss, _vertical-accordion.scss (×6, incluye un calc())
+ --pi-theme-overlay-divider-colorsin declarar → var(--pi-color-grey-100)drawer.component.scss, hub.component.scss
+ --pi-theme-hub-grabber-colorsin declarar → var(--pi-color-grey-100)hub.component.scss
- Minimal declara los seis en su mixin (_minimal.scss): aplana
- todos los radios a --pi-border-radius-xx-small y apaga las dos
- sombras (none) — mismo criterio que ya aplicaba a botón, campo
- y card, extendido al resto de superficies flotantes.
+ Minimal declara los seis primeros en su mixin (_minimal.scss):
+ aplana todos los radios a --pi-border-radius-xx-small y apaga
+ las dos sombras (none) — mismo criterio que ya aplicaba a
+ botón, campo y card, extendido al resto de superficies flotantes. Los dos
+ últimos (drawer/hub) son más recientes y todavía ningún tema los redefine.
3 Crear un tema, paso a paso
Cuatro ficheros, ninguno toca un componente. Ejemplo con un tema ficticio brutalist.
+
+ Los cuatro pasos de esta página los da un generador:
+ nx g @pi/ui:theme brutalist. Crea el fichero del paso 1 (en
+ blanco, con los grupos de tokens más habituales comentados), hace los
+ pasos 2 y 3, y además extiende PiThemeService (paso 4) para
+ que el tema quede seleccionable sin ningún paso manual más. Lo que sigue
+ es para quien prefiera hacerlo a mano o quiera entender qué toca cada
+ paso.
+
+
1
@@ -523,7 +536,13 @@
4
Aplícalo en la app consumidora
-
En el playground esto ya está resuelto con un signal + effect (ver apps/playground/src/app/app.ts); en cualquier otra app basta con:
+
Con PiThemeService (ver apps/playground/src/app/app.ts) basta con:
+
import { inject } from '@angular/core';
+import { PiThemeService } from '@pi/ui';
+
+const themeService = inject(PiThemeService);
+themeService.setTheme('brutalist');
+
Si el generador ya extendió ThemeVariant/shellClassByVariant (paso automático 4), no hace falta tocar nada más que este setTheme. Sin el servicio, el mecanismo de bajo nivel sigue funcionando igual:
import { ShellClass } from '@pi/ui';
document.body.className = ShellClass.Brutalist;
@@ -654,7 +673,11 @@ document.body.className = ShellClass.Brutalist;
@pi/ui no trae un tema oscuro propio. El playground activa
uno "best-effort" añadiendo la clase
dark-mode al
<html> (no al
<body>, que es donde
- vive
.pi-shell), y ahí reasigna solo los
+ vive
.pi-shell) — hoy vía
+
PiThemeService.setDarkMode()/
toggleDarkMode(),
+ que hace ese toggle por ti (persistido en
localStorage) en
+ vez de que cada app repita el mismo
effect() — y ahí reasigna
+ solo los
neutros (
--pi-color-white,
--pi-color-grey-*,
--pi-color-black) — las paletas de marca
(
primary,
accent…) se dejan igual:
@@ -684,12 +707,14 @@ document.body.className = ShellClass.Brutalist;
Antes de dar el tema por terminado
+ Se creó con nx g @pi/ui:theme <nombre> — hace los tres puntos siguientes por ti.
El fichero nuevo vive en themes/, exporta un mixin y una clase .pi-shell__<nombre>.
Está en themes/index.scss, al final de ambos bloques (@forward y @use).
- Tiene entrada en ShellClass (configs.ts), con un comentario de una línea que resuma el aspecto.
+ Tiene entrada en ShellClass (configs.ts) y en PiThemeService (ThemeVariant/shellClassByVariant), con un comentario de una línea que resuma el aspecto.
Solo declara los tokens que cambia — el resto hereda de _default.scss sin repetirlo.
Si toca --pi-color-primary/accent/highlighted, revisado que el puente --mat-sys-* (declarado una vez en default) sigue coherente.
Si toca overlays (modal/notificación/tooltip/accordion), revisado con las tres páginas de catálogo, no solo con un botón suelto.
+ Si al tokenizar una superficie nueva tocaste un .component.scss, nx run ui:lint-styles no marca ese fichero (detecta --pi-color-* usado fuera de un fallback --pi-theme-*).
Probado en el playground con las tres páginas de catálogo, no solo en un botón suelto.
diff --git a/libs/ui/docs/theming.pdf b/libs/ui/docs/theming.pdf
index 9b29be9..6101df5 100644
Binary files a/libs/ui/docs/theming.pdf and b/libs/ui/docs/theming.pdf differ
diff --git a/libs/ui/generators.json b/libs/ui/generators.json
new file mode 100644
index 0000000..7792934
--- /dev/null
+++ b/libs/ui/generators.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/generators.json",
+ "generators": {
+ "theme": {
+ "factory": "./src/generators/theme/generator.cts",
+ "schema": "./src/generators/theme/schema.json",
+ "description": "Crea un nuevo tema (_
.scss + ShellClass + PiThemeService) para @pi/ui."
+ }
+ }
+}
diff --git a/libs/ui/ng-package.json b/libs/ui/ng-package.json
index db0c4c0..340c3ba 100644
--- a/libs/ui/ng-package.json
+++ b/libs/ui/ng-package.json
@@ -3,5 +3,13 @@
"dest": "../../dist/libs/ui",
"lib": {
"entryFile": "src/index.ts"
- }
+ },
+ "allowedNonPeerDependencies": ["lodash", "luxon", "ngx-infinite-scroll"],
+ "assets": [
+ {
+ "glob": "**/*.scss",
+ "input": "src/lib/styles",
+ "output": "/styles"
+ }
+ ]
}
diff --git a/libs/ui/package.json b/libs/ui/package.json
index 7dad134..eb924c5 100644
--- a/libs/ui/package.json
+++ b/libs/ui/package.json
@@ -1,9 +1,29 @@
{
"name": "@pi/ui",
"version": "0.0.1",
+ "description": "Librería de componentes Angular standalone (botones, campos con Signal Forms, cards, charts, drawer/hub/modal, theming vía tokens CSS...).",
+ "license": "MIT",
+ "generators": "./generators.json",
+ "publishConfig": {
+ "registry": "https://nexus.pireon.dev/repository/npm-private/",
+ "access": "restricted"
+ },
"peerDependencies": {
+ "@angular/animations": "^22.0.0",
+ "@angular/cdk": "^22.0.0",
"@angular/common": "^22.0.0",
- "@angular/core": "^22.0.0"
+ "@angular/core": "^22.0.0",
+ "@angular/forms": "^22.0.0",
+ "@angular/material": "^22.0.0",
+ "@angular/router": "^22.0.0",
+ "@ngx-translate/core": "^18.0.0",
+ "@ngx-translate/http-loader": "^18.0.0",
+ "chart.js": "^4.0.0"
+ },
+ "dependencies": {
+ "lodash": "^4.17.0",
+ "luxon": "^3.0.0",
+ "ngx-infinite-scroll": "^22.0.0"
},
"sideEffects": false
}
diff --git a/libs/ui/project.json b/libs/ui/project.json
index 8da1cee..d181740 100644
--- a/libs/ui/project.json
+++ b/libs/ui/project.json
@@ -52,6 +52,27 @@
},
"lint": {
"executor": "@nx/eslint:lint"
+ },
+ "lint-styles": {
+ "executor": "nx:run-commands",
+ "options": {
+ "command": "stylelint \"libs/ui/src/**/*.scss\" --config .stylelintrc.json"
+ }
+ },
+ "build-generators": {
+ "executor": "nx:run-commands",
+ "dependsOn": ["package"],
+ "outputs": [
+ "{workspaceRoot}/dist/{projectRoot}/generators",
+ "{workspaceRoot}/dist/{projectRoot}/generators.json"
+ ],
+ "options": {
+ "commands": [
+ "tsc -p libs/ui/tsconfig.generators.json",
+ "node libs/ui/scripts/finalize-generators-dist.cjs"
+ ],
+ "parallel": false
+ }
}
}
}
diff --git a/libs/ui/scripts/finalize-generators-dist.cjs b/libs/ui/scripts/finalize-generators-dist.cjs
new file mode 100644
index 0000000..ef3374d
--- /dev/null
+++ b/libs/ui/scripts/finalize-generators-dist.cjs
@@ -0,0 +1,55 @@
+'use strict';
+
+/**
+ * `ng-packagr` (el `build`/`package` de `ui`) solo empaqueta lo alcanzable
+ * desde `src/index.ts` — los generadores (`src/generators/`) son Node/Nx
+ * plugin, no Angular, así que ng-packagr los ignora por completo. Este script
+ * los deja listos en `dist/libs/ui/` (ya compilados por
+ * `tsc -p tsconfig.generators.json`, que corre antes que este script) para
+ * que `generators.json` publicado apunte a ficheros que de verdad existen en
+ * el tarball — si no, `nx g @pi/ui:theme` funcionaría dentro de este
+ * monorepo pero fallaría para quien instale el paquete desde el registry.
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const libRoot = path.join(__dirname, '..');
+const distRoot = path.join(libRoot, '..', '..', 'dist', 'libs', 'ui');
+
+const sourceManifest = JSON.parse(
+ fs.readFileSync(path.join(libRoot, 'generators.json'), 'utf8'),
+);
+
+/** `./src/generators/theme/generator.cts` (fuente) -> `./generators/theme/generator.cjs` (dist). */
+function toDistPath(sourcePath) {
+ return sourcePath.replace('./src/generators/', './generators/').replace(/\.cts$/, '.cjs');
+}
+
+const distManifest = {
+ ...sourceManifest,
+ generators: Object.fromEntries(
+ Object.entries(sourceManifest.generators).map(([name, config]) => [
+ name,
+ {
+ ...config,
+ factory: toDistPath(config.factory),
+ schema: toDistPath(config.schema),
+ },
+ ]),
+ ),
+};
+
+fs.writeFileSync(
+ path.join(distRoot, 'generators.json'),
+ JSON.stringify(distManifest, null, 2) + '\n',
+);
+
+for (const { schema } of Object.values(sourceManifest.generators)) {
+ const sourceSchemaPath = path.join(libRoot, schema);
+ const destSchemaPath = path.join(distRoot, toDistPath(schema));
+ fs.mkdirSync(path.dirname(destSchemaPath), { recursive: true });
+ fs.copyFileSync(sourceSchemaPath, destSchemaPath);
+}
+
+console.log('✔ generators.json + schemas listos en dist/libs/ui/');
diff --git a/libs/ui/src/generators/theme/generator.cts b/libs/ui/src/generators/theme/generator.cts
new file mode 100644
index 0000000..c544fd8
--- /dev/null
+++ b/libs/ui/src/generators/theme/generator.cts
@@ -0,0 +1,172 @@
+import { Tree, formatFiles, logger, names } from '@nx/devkit';
+import type { ThemeGeneratorSchema } from './schema';
+
+const THEMES_DIR = 'libs/ui/src/lib/styles/themes';
+const CONFIGS_PATH = 'libs/ui/src/lib/styles/configs.ts';
+const THEME_SERVICE_PATH = 'libs/ui/src/lib/util/theme/theme.service.ts';
+
+export default async function themeGenerator(
+ tree: Tree,
+ options: ThemeGeneratorSchema,
+): Promise {
+ const { fileName: themeName, className, propertyName } = names(options.name);
+ const scssPath = `${THEMES_DIR}/_${themeName}.scss`;
+
+ if (tree.exists(scssPath)) {
+ throw new Error(
+ `Ya existe un tema en ${scssPath}. Elige otro nombre o bórralo primero.`,
+ );
+ }
+
+ tree.write(scssPath, buildThemeScss(themeName, propertyName));
+ registerInThemesIndex(tree, themeName);
+ addShellClassEntry(tree, className, themeName);
+ extendThemeService(tree, className, themeName);
+
+ await formatFiles(tree);
+
+ logger.info(`\n✔ Tema "${themeName}" creado:`);
+ logger.info(` - ${scssPath}`);
+ logger.info(` - registrado en ${THEMES_DIR}/index.scss`);
+ logger.info(` - añadido a ShellClass en ${CONFIGS_PATH}`);
+ logger.info(` - añadido a ThemeVariant/PiThemeService en ${THEME_SERVICE_PATH}`);
+ logger.info(
+ `\nEdita los tokens de ${scssPath} para definir el aspecto (ver TOKENS.md).`,
+ );
+ logger.info(
+ `Ya es seleccionable vía PiThemeService.setTheme('${themeName}'); solo falta` +
+ ` añadirlo a la UI de tu app (p. ej. \`themeOptions\` en tu shell) si quieres un botón.`,
+ );
+}
+
+function buildThemeScss(themeName: string, propertyName: string): string {
+ return `// TEMA ${themeName.toUpperCase()}
+//
+// Generado con "nx g @pi/ui:theme ${themeName}". Empieza en blanco a
+// propósito: mientras no descomentes nada, este tema es visualmente idéntico
+// al tema por defecto (todo cae al ".pi-shell" base). Descomenta y cambia solo
+// los tokens que quieras redefinir — no hace falta declarar el resto. Lista
+// completa de tokens "--pi-theme-*"/"--pi-color-*" disponibles: TOKENS.md.
+
+@mixin ${propertyName}ThemeTokens {
+ // --- Paleta de marca ----------------------------------------------------
+ // --pi-color-primary: #799ef0;
+ // --pi-color-accent: #eda82a;
+ // --pi-color-highlighted: #0d2a6b;
+
+ // --- Botones --------------------------------------------------------------
+ // --pi-theme-button-radius: 999px;
+ // --pi-theme-button-shadow: none;
+ // --pi-theme-button-fill-primary: none;
+
+ // --- Campos de formulario ---------------------------------------------------
+ // --pi-theme-field-radius: 0.875rem;
+ // --pi-theme-field-border-color-focus: var(--pi-color-primary);
+ // --pi-theme-field-surface: var(--pi-color-white);
+
+ // --- Superficies ------------------------------------------------------------
+ // --pi-theme-card-radius: var(--pi-border-radius-large);
+ // --pi-theme-card-shadow: var(--pi-box-shadow-panel);
+ // --pi-theme-panel-radius: var(--pi-border-radius-large);
+}
+
+.pi-shell__${themeName} {
+ @include ${propertyName}ThemeTokens;
+}
+`;
+}
+
+function registerInThemesIndex(tree: Tree, themeName: string): void {
+ const path = `${THEMES_DIR}/index.scss`;
+ const content = tree.read(path, 'utf-8');
+ if (!content) throw new Error(`No se encontró ${path}.`);
+
+ const withForward = insertAfterLastMatch(
+ content,
+ /@forward '[^']+';/g,
+ `@forward '${themeName}';`,
+ );
+ const withUse = insertAfterLastMatch(
+ withForward,
+ /@use '[^']+' as \*;/g,
+ `@use '${themeName}' as *;`,
+ );
+
+ tree.write(path, withUse);
+}
+
+function addShellClassEntry(
+ tree: Tree,
+ className: string,
+ themeName: string,
+): void {
+ patchObjectLiteral(
+ tree,
+ CONFIGS_PATH,
+ /export const ShellClass = \{[\s\S]*?\n\};/,
+ ` /** Generado con \`nx g @pi/ui:theme ${themeName}\`. */\n ${className}: shellTheming('${themeName}'),\n};`,
+ );
+}
+
+function extendThemeService(
+ tree: Tree,
+ className: string,
+ themeName: string,
+): void {
+ const path = THEME_SERVICE_PATH;
+ const content = tree.read(path, 'utf-8');
+ if (!content) throw new Error(`No se encontró ${path}.`);
+
+ const unionPattern =
+ /export type ThemeVariant = 'default' \| 'alternative' \| 'minimal' \| 'gradient'(?:\s*\|\s*'[^']+')*;/;
+ const updatedUnion = content.replace(unionPattern, (match) =>
+ match.replace(/;$/, ` | '${themeName}';`),
+ );
+
+ patchObjectLiteral(
+ tree,
+ path,
+ /const shellClassByVariant: Record = \{[\s\S]*?\n\};/,
+ ` '${themeName}': ShellClass.${className},\n};`,
+ updatedUnion,
+ );
+}
+
+/**
+ * Sustituye el objeto literal que casa con `blockPattern` insertando
+ * `newClosing` justo antes de su `\n};` de cierre — funciona igual si el
+ * generador ya se ha ejecutado antes (siempre inserta antes del PRIMER cierre
+ * tras la apertura, no del último del fichero).
+ */
+function patchObjectLiteral(
+ tree: Tree,
+ path: string,
+ blockPattern: RegExp,
+ newClosing: string,
+ contentOverride?: string,
+): void {
+ const content = contentOverride ?? tree.read(path, 'utf-8');
+ if (!content) throw new Error(`No se encontró ${path}.`);
+
+ const match = content.match(blockPattern);
+ if (!match) {
+ throw new Error(`No se encontró el bloque esperado en ${path}.`);
+ }
+
+ const block = match[0];
+ const updatedBlock = block.replace(/\n\};$/, `\n${newClosing}`);
+ tree.write(path, content.replace(block, updatedBlock));
+}
+
+function insertAfterLastMatch(
+ content: string,
+ pattern: RegExp,
+ newLine: string,
+): string {
+ const matches = [...content.matchAll(pattern)];
+ if (matches.length === 0) return `${content}\n${newLine}`;
+
+ const last = matches[matches.length - 1];
+ const insertPos = (last.index ?? content.length) + last[0].length;
+ return `${content.slice(0, insertPos)}\n${newLine}${content.slice(insertPos)}`;
+}
diff --git a/libs/ui/src/generators/theme/schema.d.ts b/libs/ui/src/generators/theme/schema.d.ts
new file mode 100644
index 0000000..6653e3f
--- /dev/null
+++ b/libs/ui/src/generators/theme/schema.d.ts
@@ -0,0 +1,3 @@
+export interface ThemeGeneratorSchema {
+ name: string;
+}
diff --git a/libs/ui/src/generators/theme/schema.json b/libs/ui/src/generators/theme/schema.json
new file mode 100644
index 0000000..bf5ab36
--- /dev/null
+++ b/libs/ui/src/generators/theme/schema.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json-schema.org/schema",
+ "$id": "PiUiTheme",
+ "title": "Genera un nuevo tema para @pi/ui",
+ "description": "Crea _.scss, lo registra en themes/index.scss, añade una entrada a ShellClass (styles/configs.ts) y extiende ThemeVariant/PiThemeService para que quede utilizable de inmediato.",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Nombre del tema, en kebab-case (p. ej. \"acme-corp\").",
+ "$default": { "$source": "argv", "index": 0 },
+ "x-prompt": "¿Cómo se llama el tema?"
+ }
+ },
+ "required": ["name"]
+}
diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts
index e544242..1250bc3 100644
--- a/libs/ui/src/index.ts
+++ b/libs/ui/src/index.ts
@@ -6,7 +6,6 @@ export * from './lib/util/affix';
export * from './lib/util/amplitude';
export * from './lib/util/color';
export * from './lib/util/common';
-export * from './lib/util/dato-cms';
export * from './lib/util/device-configs';
export * from './lib/util/environments';
export * from './lib/util/icon-registry';
@@ -21,6 +20,7 @@ export * from './lib/util/router-store';
export * from './lib/util/schema';
export * from './lib/util/scroll-to';
export * from './lib/util/status';
+export * from './lib/util/theme';
export * from './lib/util/translate';
export * from './lib/util/validator';
export * from './lib/util/value';
diff --git a/libs/ui/src/lib/ui/drawer/drawer/drawer.component.scss b/libs/ui/src/lib/ui/drawer/drawer/drawer.component.scss
index 84d4b2a..7452578 100644
--- a/libs/ui/src/lib/ui/drawer/drawer/drawer.component.scss
+++ b/libs/ui/src/lib/ui/drawer/drawer/drawer.component.scss
@@ -10,7 +10,7 @@
justify-content: space-between;
gap: var(--pi-spacing-medium);
padding: var(--pi-spacing-large) var(--pi-spacing-x-large);
- border-bottom: 0.0625rem solid var(--pi-color-grey-100);
+ border-bottom: 0.0625rem solid var(--pi-theme-overlay-divider-color, var(--pi-color-grey-100));
}
main {
diff --git a/libs/ui/src/lib/ui/hub/hub/hub.component.scss b/libs/ui/src/lib/ui/hub/hub/hub.component.scss
index 6bd67c1..e616447 100644
--- a/libs/ui/src/lib/ui/hub/hub/hub.component.scss
+++ b/libs/ui/src/lib/ui/hub/hub/hub.component.scss
@@ -10,7 +10,7 @@
height: 0.25rem;
margin: var(--pi-spacing-small) auto 0;
border-radius: var(--pi-border-radius-x-small);
- background-color: var(--pi-color-grey-100);
+ background-color: var(--pi-theme-hub-grabber-color, var(--pi-color-grey-100));
}
header {
@@ -19,7 +19,7 @@
justify-content: space-between;
gap: var(--pi-spacing-medium);
padding: var(--pi-spacing-medium) var(--pi-spacing-x-large) var(--pi-spacing-large);
- border-bottom: 0.0625rem solid var(--pi-color-grey-100);
+ border-bottom: 0.0625rem solid var(--pi-theme-overlay-divider-color, var(--pi-color-grey-100));
}
main {
diff --git a/libs/ui/src/lib/util/dato-cms/graphql.module.ts b/libs/ui/src/lib/util/dato-cms/graphql.module.ts
deleted file mode 100644
index 2686458..0000000
--- a/libs/ui/src/lib/util/dato-cms/graphql.module.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const datoCms = 'https://graphql.datocms.com/'; // our GraphQL API
diff --git a/libs/ui/src/lib/util/dato-cms/index.ts b/libs/ui/src/lib/util/dato-cms/index.ts
deleted file mode 100644
index ef59679..0000000
--- a/libs/ui/src/lib/util/dato-cms/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './util-dato-cms.module';
diff --git a/libs/ui/src/lib/util/dato-cms/util-dato-cms.module.ts b/libs/ui/src/lib/util/dato-cms/util-dato-cms.module.ts
deleted file mode 100644
index ac99c64..0000000
--- a/libs/ui/src/lib/util/dato-cms/util-dato-cms.module.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
-
-// Apollo
-import { InMemoryCache } from '@apollo/client/core';
-import { APOLLO_OPTIONS } from 'apollo-angular';
-import { HttpLink } from 'apollo-angular/http';
-
-// Shared
-import { environment } from '../environments';
-
-// datoCMS
-import { datoCms } from './graphql.module';
-
-/**
- * Sustituye a `PiSharedUtilDatoCmsModule`: apollo-angular ya no expone
- * `ApolloModule`, la configuración se hace con providers.
- */
-export function providePiDatoCms(): EnvironmentProviders {
- return makeEnvironmentProviders([
- {
- provide: APOLLO_OPTIONS,
- useFactory: (httpLink: HttpLink) => {
- return {
- cache: new InMemoryCache(),
- link: httpLink.create({
- uri: datoCms,
- headers: {
- Authorization: environment.DATO_KEY,
- } as any,
- }),
- };
- },
- deps: [HttpLink],
- },
- ]);
-}
diff --git a/libs/ui/src/lib/util/theme/index.ts b/libs/ui/src/lib/util/theme/index.ts
new file mode 100644
index 0000000..1b62ce4
--- /dev/null
+++ b/libs/ui/src/lib/util/theme/index.ts
@@ -0,0 +1 @@
+export * from './theme.service';
diff --git a/libs/ui/src/lib/util/theme/theme.service.spec.ts b/libs/ui/src/lib/util/theme/theme.service.spec.ts
new file mode 100644
index 0000000..7581275
--- /dev/null
+++ b/libs/ui/src/lib/util/theme/theme.service.spec.ts
@@ -0,0 +1,60 @@
+import { TestBed } from '@angular/core/testing';
+
+import { PiThemeService } from './theme.service';
+
+describe('PiThemeService', () => {
+ afterEach(() => {
+ localStorage.clear();
+ document.body.className = '';
+ document.documentElement.classList.remove('dark-mode');
+ });
+
+ it('applies `pi-shell pi-shell__default` to by default', () => {
+ TestBed.inject(PiThemeService);
+ TestBed.flushEffects();
+
+ expect(document.body.className).toBe('pi-shell pi-shell__default');
+ });
+
+ it('updates when the theme changes', () => {
+ const service = TestBed.inject(PiThemeService);
+
+ service.setTheme('alternative');
+ TestBed.flushEffects();
+
+ expect(document.body.className).toBe('pi-shell pi-shell__alternative');
+ });
+
+ it('toggles `dark-mode` on ', () => {
+ const service = TestBed.inject(PiThemeService);
+
+ service.setDarkMode(true);
+ TestBed.flushEffects();
+ expect(document.documentElement.classList.contains('dark-mode')).toBe(true);
+
+ service.toggleDarkMode();
+ TestBed.flushEffects();
+ expect(document.documentElement.classList.contains('dark-mode')).toBe(false);
+ });
+
+ it('persists the theme and dark mode across a fresh instance (reload)', () => {
+ const service = TestBed.inject(PiThemeService);
+ service.setTheme('minimal');
+ service.setDarkMode(true);
+ TestBed.flushEffects();
+
+ TestBed.resetTestingModule();
+ const reloaded = TestBed.inject(PiThemeService);
+
+ expect(reloaded.theme()).toBe('minimal');
+ expect(reloaded.darkMode()).toBe(true);
+ });
+
+ it('falls back to the default theme when nothing valid is stored', () => {
+ localStorage.setItem('pi-theme', JSON.stringify('not-a-real-theme'));
+
+ const service = TestBed.inject(PiThemeService);
+
+ expect(service.theme()).toBe('default');
+ });
+});
diff --git a/libs/ui/src/lib/util/theme/theme.service.ts b/libs/ui/src/lib/util/theme/theme.service.ts
new file mode 100644
index 0000000..7664463
--- /dev/null
+++ b/libs/ui/src/lib/util/theme/theme.service.ts
@@ -0,0 +1,98 @@
+import { DOCUMENT, isPlatformBrowser } from '@angular/common';
+import {
+ Injectable,
+ PLATFORM_ID,
+ computed,
+ effect,
+ inject,
+ signal,
+} from '@angular/core';
+
+import { ShellClass } from '../../styles';
+import { LocalStorageService } from '../local-storage';
+
+export type ThemeVariant = 'default' | 'alternative' | 'minimal' | 'gradient';
+
+const THEME_STORAGE_KEY = 'pi-theme';
+const DARK_MODE_STORAGE_KEY = 'pi-dark-mode';
+
+const shellClassByVariant: Record = {
+ default: ShellClass.Default,
+ alternative: ShellClass.Alternative,
+ minimal: ShellClass.Minimal,
+ gradient: ShellClass.Gradient,
+};
+
+/**
+ * Centraliza lo que hasta ahora cada app reimplementaba a mano (ver el
+ * `app.ts` del playground antes de esta extracción): fijar
+ * `pi-shell pi-shell__` en `` — los overlays de Material (diálogo,
+ * snackbar) cuelgan fuera de `` y también necesitan heredarlo — y
+ * `dark-mode` en ``.
+ *
+ * `theme`/`darkMode` son señales normales: se pueden mutar tanto con
+ * `setTheme()`/`toggleDarkMode()` como con un `[(checked)]`/`[(value)]` dos
+ * vías directo sobre la señal. La aplicación de clases y la persistencia en
+ * `localStorage` viven en `effect()`, no en los setters, para que cualquier
+ * camino de escritura quede cubierto por igual.
+ *
+ * El modo oscuro en sí (qué tokens remapea `.dark-mode`) lo sigue definiendo
+ * cada app consumidora — este servicio solo pone o quita la clase.
+ */
+@Injectable({ providedIn: 'root' })
+export class PiThemeService {
+ private readonly document = inject(DOCUMENT);
+ private readonly platformId = inject(PLATFORM_ID);
+ private readonly localStorage = inject(LocalStorageService);
+
+ readonly theme = signal(this.readStoredTheme());
+ readonly darkMode = signal(this.readStoredDarkMode());
+
+ readonly shellClass = computed(() => shellClassByVariant[this.theme()]);
+
+ constructor() {
+ effect(() => {
+ this.document.body.className = this.shellClass();
+ this.persist(THEME_STORAGE_KEY, this.theme());
+ });
+
+ effect(() => {
+ this.document.documentElement.classList.toggle(
+ 'dark-mode',
+ this.darkMode(),
+ );
+ this.persist(DARK_MODE_STORAGE_KEY, this.darkMode());
+ });
+ }
+
+ setTheme(theme: ThemeVariant): void {
+ this.theme.set(theme);
+ }
+
+ setDarkMode(darkMode: boolean): void {
+ this.darkMode.set(darkMode);
+ }
+
+ toggleDarkMode(): void {
+ this.darkMode.update((value) => !value);
+ }
+
+ private persist(key: string, value: unknown): void {
+ if (isPlatformBrowser(this.platformId)) {
+ this.localStorage.setItem(key, value);
+ }
+ }
+
+ private readStoredTheme(): ThemeVariant {
+ if (!isPlatformBrowser(this.platformId)) return 'default';
+ const stored = this.localStorage.getItem(THEME_STORAGE_KEY);
+ return stored && stored in shellClassByVariant ? stored : 'default';
+ }
+
+ private readStoredDarkMode(): boolean {
+ return (
+ isPlatformBrowser(this.platformId) &&
+ !!this.localStorage.getItem(DARK_MODE_STORAGE_KEY)
+ );
+ }
+}
diff --git a/libs/ui/tsconfig.generators.json b/libs/ui/tsconfig.generators.json
new file mode 100644
index 0000000..43199d6
--- /dev/null
+++ b/libs/ui/tsconfig.generators.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "target": "es2021",
+ "outDir": "../../dist/libs/ui/generators",
+ "rootDir": "src/generators",
+ "declaration": false,
+ "sourceMap": false,
+ "types": ["node"]
+ },
+ "include": ["src/generators/**/*.cts"]
+}
diff --git a/package-lock.json b/package-lock.json
index 7914312..535be16 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,13 +22,10 @@
"@angular/platform-server": "22.0.6",
"@angular/router": "22.0.6",
"@angular/ssr": "22.0.6",
- "@apollo/client": "^4.2.8",
"@ngx-translate/core": "^18.0.0",
"@ngx-translate/http-loader": "^18.0.0",
- "apollo-angular": "^14.1.0",
"chart.js": "^4.5.1",
"express": "^4.21.2",
- "graphql": "^17.0.2",
"lodash": "^4.18.1",
"luxon": "^3.7.2",
"ngx-infinite-scroll": "^22.0.0",
@@ -90,7 +87,9 @@
"ng-packagr": "~22.0.0",
"nx": "23.1.0",
"postcss": "^8.4.5",
+ "postcss-scss": "^4.0.9",
"prettier": "^3.8.1",
+ "stylelint": "^17.14.1",
"supertest": "^7.1.4",
"ts-jest": "^29.4.7",
"ts-node": "10.9.1",
@@ -2960,43 +2959,6 @@
}
}
},
- "node_modules/@apollo/client": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.2.8.tgz",
- "integrity": "sha512-Iw/e/5fpOx+/2CgtcFNLdwxtdPzwTfmD4mYRjHA1UBdkfe6WOtWLRQeRESCUJCh49blXwp9I496A0MIoLgmCsw==",
- "license": "MIT",
- "dependencies": {
- "@graphql-typed-document-node/core": "^3.1.1",
- "@wry/caches": "^1.0.0",
- "@wry/equality": "^0.5.6",
- "@wry/trie": "^0.5.0",
- "graphql-tag": "^2.12.6",
- "optimism": "^0.18.0",
- "tslib": "^2.3.0"
- },
- "peerDependencies": {
- "graphql": "^16.0.0 || ^17.0.0",
- "graphql-ws": "^5.5.5 || ^6.0.3",
- "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc",
- "react-dom": "^17.0.0 || ^18.0.0 || >=19.0.0-rc",
- "rxjs": "^7.3.0",
- "subscriptions-transport-ws": "^0.9.0 || ^0.11.0"
- },
- "peerDependenciesMeta": {
- "graphql-ws": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- },
- "subscriptions-transport-ws": {
- "optional": true
- }
- }
- },
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
@@ -4974,6 +4936,50 @@
"dev": true,
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
+ "node_modules/@cacheable/memory": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz",
+ "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/utils": "^2.5.0",
+ "@keyv/bigmap": "^1.3.1",
+ "hookified": "^1.15.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/memory/node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
+ }
+ },
+ "node_modules/@cacheable/utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz",
+ "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.5.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/utils/node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
+ }
+ },
"node_modules/@colordx/core": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@colordx/core/-/core-5.5.0.tgz",
@@ -5100,6 +5106,31 @@
"@csstools/css-tokenizer": "^3.0.4"
}
},
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz",
+ "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
@@ -5120,6 +5151,76 @@
"node": ">=18"
}
},
+ "node_modules/@csstools/media-query-list-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz",
+ "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/selector-resolve-nested": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.1.tgz",
+ "integrity": "sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "postcss-selector-parser": "^7.1.1"
+ }
+ },
+ "node_modules/@csstools/selector-specificity": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz",
+ "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "postcss-selector-parser": "^7.1.1"
+ }
+ },
"node_modules/@discoveryjs/json-ext": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz",
@@ -5875,15 +5976,6 @@
"node": "^20.17.0 || >=22.9.0"
}
},
- "node_modules/@graphql-typed-document-node/core": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
- "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
- "license": "MIT",
- "peerDependencies": {
- "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
- }
- },
"node_modules/@harperfast/extended-iterable": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz",
@@ -8309,6 +8401,30 @@
"tslib": "2"
}
},
+ "node_modules/@keyv/bigmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz",
+ "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.4.0",
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@keyv/serialize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz",
+ "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
@@ -13391,6 +13507,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
@@ -15217,54 +15346,6 @@
"@xtuc/long": "4.2.2"
}
},
- "node_modules/@wry/caches": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz",
- "integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@wry/context": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz",
- "integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@wry/equality": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz",
- "integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@wry/trie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz",
- "integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.3.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -15596,24 +15677,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/apollo-angular": {
- "version": "14.1.0",
- "resolved": "https://registry.npmjs.org/apollo-angular/-/apollo-angular-14.1.0.tgz",
- "integrity": "sha512-rE6gcY9pSsI6FuG0tNhoVGxVJ82qN6vQAtG0t2yfujWTJRW1YD6aZe85D5LyyADM1oXGYySuYQcTtWBqAAQ6XQ==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=16"
- },
- "peerDependencies": {
- "@angular/core": "^20.0.0 || ^21.0.0 || ^22.0.0",
- "@apollo/client": "^4.0.1",
- "graphql": "^16.0.0",
- "rxjs": "^7.8.0"
- }
- },
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@@ -15693,6 +15756,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -16415,6 +16488,30 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/cacheable": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz",
+ "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/memory": "^2.2.0",
+ "@cacheable/utils": "^2.5.0",
+ "hookified": "^1.15.0",
+ "keyv": "^5.6.0",
+ "qified": "^0.10.1"
+ }
+ },
+ "node_modules/cacheable/node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -16835,6 +16932,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/colord": {
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
@@ -17175,6 +17279,16 @@
"postcss": "^8.0.9"
}
},
+ "node_modules/css-functions-list": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz",
+ "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/css-loader": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
@@ -18791,6 +18905,16 @@
"fast-string-width": "^3.0.2"
}
},
+ "node_modules/fastest-levenshtein": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
+ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.9.1"
+ }
+ },
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -19592,6 +19716,54 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "global-prefix": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/global-prefix/node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/global-prefix/node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
"node_modules/globals": {
"version": "17.8.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz",
@@ -19605,6 +19777,47 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/globby": {
+ "version": "16.2.2",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.2.tgz",
+ "integrity": "sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "fast-glob": "^3.3.3",
+ "ignore": "^7.0.5",
+ "is-path-inside": "^4.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby/node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globjoin": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz",
+ "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
@@ -19630,30 +19843,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
- "node_modules/graphql": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/graphql/-/graphql-17.0.2.tgz",
- "integrity": "sha512-FRWbddMxfkjiB7z+aQDWIR+E34xo9I8c9mtK2RPv8PmMzKRvrdsreHL/Ui/TmwHJfhHChEtsFPyMHKI+xuarQQ==",
- "license": "MIT",
- "engines": {
- "node": "^22.0.0 || ^24.0.0 || ^25.0.0 || >=26.0.0"
- }
- },
- "node_modules/graphql-tag": {
- "version": "2.12.7",
- "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.7.tgz",
- "integrity": "sha512-xnE/NFzy+0eIesvAsREJZ284zTl/wYuBAvpsFSDhRGRdRHdnE90M21Q3xAWyYInb0J756c6x0pIQ62+vtvOs1Q==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
- }
- },
"node_modules/handle-thing": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
@@ -19736,6 +19925,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/hashery": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz",
+ "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
@@ -19768,6 +19970,13 @@
"node": ">=16.9.0"
}
},
+ "node_modules/hookified": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz",
+ "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/hosted-git-info": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz",
@@ -19853,6 +20062,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/html-tags": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz",
+ "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
@@ -20201,6 +20423,17 @@
"node": ">=8"
}
},
+ "node_modules/import-meta-resolve": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
+ "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -20444,6 +20677,19 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-path-inside": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
+ "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
@@ -24451,6 +24697,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lodash.uniq": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
@@ -24734,6 +24987,17 @@
"node": ">= 0.4"
}
},
+ "node_modules/mathml-tag-names": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz",
+ "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
@@ -24763,6 +25027,19 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/meow": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz",
+ "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -25148,9 +25425,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.16",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
- "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "version": "3.3.17",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
+ "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
"funding": [
{
"type": "github",
@@ -26588,18 +26865,6 @@
"opener": "bin/opener-bin.js"
}
},
- "node_modules/optimism": {
- "version": "0.18.1",
- "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz",
- "integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==",
- "license": "MIT",
- "dependencies": {
- "@wry/caches": "^1.0.0",
- "@wry/context": "^0.7.0",
- "@wry/trie": "^0.5.0",
- "tslib": "^2.3.0"
- }
- },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -27974,6 +28239,33 @@
"postcss": "^8.4.31"
}
},
+ "node_modules/postcss-scss": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.29"
+ }
+ },
"node_modules/postcss-selector-parser": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz",
@@ -28184,6 +28476,26 @@
"node": ">=16.0.0"
}
},
+ "node_modules/qified": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz",
+ "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/qified/node_modules/hookified": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz",
+ "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/qs": {
"version": "6.15.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
@@ -30168,6 +30480,293 @@
"postcss": "^8.5.13"
}
},
+ "node_modules/stylelint": {
+ "version": "17.14.1",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.1.tgz",
+ "integrity": "sha512-xVQwyiuxALUBNB2fBe0tmNemg9KqLtdj3T64mioFDar79B2cU8LIyz+3KL6LdiHs9NkeNfwxpKSaIVOY8f112g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/stylelint"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/stylelint"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^3.2.1",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.6",
+ "@csstools/css-tokenizer": "^4.0.0",
+ "@csstools/media-query-list-parser": "^5.0.0",
+ "@csstools/selector-resolve-nested": "^4.0.0",
+ "@csstools/selector-specificity": "^6.0.0",
+ "colord": "^2.9.3",
+ "cosmiconfig": "^9.0.2",
+ "css-functions-list": "^3.3.3",
+ "css-tree": "^3.2.1",
+ "debug": "^4.4.3",
+ "fast-glob": "^3.3.3",
+ "fastest-levenshtein": "^1.0.16",
+ "file-entry-cache": "^11.1.5",
+ "global-modules": "^2.0.0",
+ "globby": "^16.2.1",
+ "globjoin": "^0.1.4",
+ "html-tags": "^5.1.0",
+ "ignore": "^7.0.5",
+ "import-meta-resolve": "^4.2.0",
+ "mathml-tag-names": "^4.0.0",
+ "meow": "^14.1.0",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.5.16",
+ "postcss-safe-parser": "^7.0.1",
+ "postcss-selector-parser": "^7.1.4",
+ "postcss-value-parser": "^4.2.0",
+ "string-width": "^8.2.1",
+ "supports-hyperlinks": "^4.5.0",
+ "svg-tags": "^1.0.0",
+ "table": "^6.9.0",
+ "write-file-atomic": "^7.0.1"
+ },
+ "bin": {
+ "stylelint": "bin/stylelint.mjs"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/@csstools/css-calc": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
+ "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/stylelint/node_modules/cosmiconfig": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz",
+ "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/stylelint/node_modules/file-entry-cache": {
+ "version": "11.1.5",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz",
+ "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^6.1.23"
+ }
+ },
+ "node_modules/stylelint/node_modules/flat-cache": {
+ "version": "6.1.23",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz",
+ "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cacheable": "^2.5.0",
+ "flatted": "^3.4.2",
+ "hookified": "^1.15.0"
+ }
+ },
+ "node_modules/stylelint/node_modules/js-yaml": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+ "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/stylelint/node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/stylelint/node_modules/string-width": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
+ "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stylelint/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/stylelint/node_modules/write-file-atomic": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz",
+ "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
"node_modules/superagent": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
@@ -30240,6 +30839,49 @@
"node": ">=8"
}
},
+ "node_modules/supports-hyperlinks": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.5.0.tgz",
+ "integrity": "sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^5.0.1",
+ "supports-color": "^10.2.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1"
+ }
+ },
+ "node_modules/supports-hyperlinks/node_modules/has-flag": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz",
+ "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-hyperlinks/node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -30252,6 +30894,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svg-tags": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
+ "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
+ "dev": true
+ },
"node_modules/svgo": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz",
@@ -30354,6 +31002,56 @@
"url": "https://opencollective.com/synckit"
}
},
+ "node_modules/table": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz",
+ "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/table/node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/table/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/tapable": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
@@ -31218,6 +31916,19 @@
"node": ">=4"
}
},
+ "node_modules/unicorn-magic": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz",
+ "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
diff --git a/package.json b/package.json
index bce978b..ae41f53 100644
--- a/package.json
+++ b/package.json
@@ -29,13 +29,10 @@
"@angular/platform-server": "22.0.6",
"@angular/router": "22.0.6",
"@angular/ssr": "22.0.6",
- "@apollo/client": "^4.2.8",
"@ngx-translate/core": "^18.0.0",
"@ngx-translate/http-loader": "^18.0.0",
- "apollo-angular": "^14.1.0",
"chart.js": "^4.5.1",
"express": "^4.21.2",
- "graphql": "^17.0.2",
"lodash": "^4.18.1",
"luxon": "^3.7.2",
"ngx-infinite-scroll": "^22.0.0",
@@ -97,7 +94,9 @@
"ng-packagr": "~22.0.0",
"nx": "23.1.0",
"postcss": "^8.4.5",
+ "postcss-scss": "^4.0.9",
"prettier": "^3.8.1",
+ "stylelint": "^17.14.1",
"supertest": "^7.1.4",
"ts-jest": "^29.4.7",
"ts-node": "10.9.1",
diff --git a/tools/stylelint-plugin-pi-theme/no-raw-color-token.mjs b/tools/stylelint-plugin-pi-theme/no-raw-color-token.mjs
new file mode 100644
index 0000000..cd22846
--- /dev/null
+++ b/tools/stylelint-plugin-pi-theme/no-raw-color-token.mjs
@@ -0,0 +1,98 @@
+import stylelint from 'stylelint';
+
+const { createPlugin, utils } = stylelint;
+const { report, ruleMessages, validateOptions } = utils;
+
+const ruleName = 'pi/no-raw-color-token';
+
+const messages = ruleMessages(ruleName, {
+ rejected: (token) =>
+ `"${token}" se usa directamente. Los componentes de la librería deben ` +
+ `consumir un token --pi-theme-* con este color como fallback ` +
+ `(var(--pi-theme-, ${token})) para que un tema o el modo oscuro ` +
+ `puedan redefinir la superficie sin tocar el componente. Ver TOKENS.md.`,
+});
+
+const meta = {
+ url: 'https://github.com/pi/ui/tools/stylelint-plugin-pi-theme',
+};
+
+/**
+ * Contrato de esta librería (ver TOKENS.md): un componente NO consume
+ * `--pi-color-*` en crudo, lo hace a través de un `--pi-theme-*` con el color
+ * como fallback: `var(--pi-theme-x, var(--pi-color-y))`. Esta regla detecta
+ * el caso contrario — `--pi-color-*` referenciado fuera de cualquier
+ * fallback de `--pi-theme-*` — que es exactamente la clase de bug que dejó
+ * colores de marca fijos filtrando en modo oscuro (accordion/tab/select en
+ * esta misma librería, antes de tokenizarlos).
+ *
+ * Deliberadamente NO seguimos con `postcss-value-parser`: los valores aquí
+ * son casi siempre `var(...)` con como mucho una función envolvente
+ * (`linear-gradient(...)`, `rgba(...)`), así que un escaneo con paréntesis
+ * balanceados es suficiente y evita añadir una dependencia más solo para
+ * esta regla.
+ */
+function findMatchingParenEnd(value, openParenIndex) {
+ let depth = 1;
+ for (let i = openParenIndex + 1; i < value.length; i++) {
+ if (value[i] === '(') depth++;
+ else if (value[i] === ')') {
+ depth--;
+ if (depth === 0) return i;
+ }
+ }
+ return value.length - 1;
+}
+
+/** Rangos `[start, end)` de cada fallback de un `var(--pi-theme-*, ...)` — lo que haya dentro está exento. */
+function findProtectedRanges(value) {
+ const ranges = [];
+ const pattern = /var\(\s*--pi-theme-[\w-]+/g;
+ let match;
+ while ((match = pattern.exec(value))) {
+ const openParenIndex = match.index + match[0].indexOf('(');
+ const end = findMatchingParenEnd(value, openParenIndex);
+ ranges.push([match.index, end]);
+ }
+ return ranges;
+}
+
+function isProtected(index, ranges) {
+ return ranges.some(([start, end]) => index > start && index < end);
+}
+
+const rule = (primaryOption) => {
+ return (root, result) => {
+ const validOptions = validateOptions(result, ruleName, {
+ actual: primaryOption,
+ possible: [true, false],
+ });
+ if (!validOptions || !primaryOption) return;
+
+ root.walkDecls((decl) => {
+ const value = decl.value;
+ if (!value || !value.includes('--pi-color-')) return;
+
+ const protectedRanges = findProtectedRanges(value);
+ const colorPattern = /--pi-color-[\w-]+/g;
+ let match;
+ while ((match = colorPattern.exec(value))) {
+ if (isProtected(match.index, protectedRanges)) continue;
+
+ report({
+ message: messages.rejected(match[0]),
+ node: decl,
+ word: match[0],
+ result,
+ ruleName,
+ });
+ }
+ });
+ };
+};
+
+rule.ruleName = ruleName;
+rule.messages = messages;
+rule.meta = meta;
+
+export default createPlugin(ruleName, rule);