@telcomdev/ui 0.1.37 → 0.1.38

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { ChangeDetectionStrategy, Component, Injectable, inject, HostBinding, Input, booleanAttribute, EventEmitter, Output, ViewChild, Injector, ChangeDetectorRef, signal, input, model, output, computed, forwardRef, InjectionToken, ElementRef, HostListener, Directive, EnvironmentInjector, ContentChild, DestroyRef, effect, TemplateRef, ViewChildren, ContentChildren, ViewContainerRef, Renderer2, numberAttribute } from '@angular/core';
2
+ import { ChangeDetectionStrategy, Component, Injectable, inject, HostBinding, Input, booleanAttribute, EventEmitter, Output, ViewChild, Injector, ChangeDetectorRef, signal, input, model, output, computed, forwardRef, InjectionToken, ElementRef, HostListener, Directive, EnvironmentInjector, DestroyRef, ContentChild, effect, TemplateRef, ViewChildren, ContentChildren, ViewContainerRef, Renderer2, numberAttribute } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
5
5
  import { FORM_FIELD } from '@angular/forms/signals';
@@ -5958,6 +5958,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
5958
5958
  type: Input
5959
5959
  }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
5960
5960
 
5961
+ class TdSidebar {
5962
+ router = inject(Router);
5963
+ destroyRef = inject(DestroyRef);
5964
+ titulo = 'Gestión Corporativa';
5965
+ subtitulo = 'Central Platform';
5966
+ etiquetaNavegacion = 'Navegación';
5967
+ logoUrl = '';
5968
+ logoAlt = 'Logo';
5969
+ textoCarga = 'Cargando menú...';
5970
+ mensajeVacio = 'No hay opciones disponibles.';
5971
+ error = '';
5972
+ cargando = false;
5973
+ oscuro = false;
5974
+ modoCerrado = 'compacto';
5975
+ set abierto(value) {
5976
+ this.abiertoInterno.set(value);
5977
+ }
5978
+ get abierto() {
5979
+ return this.abiertoInterno();
5980
+ }
5981
+ set menu(value) {
5982
+ this.menuNormalizado.set(this.normalizarMenu(value ?? []));
5983
+ this.expandirRutaActual();
5984
+ }
5985
+ get menu() {
5986
+ return this.menuNormalizado();
5987
+ }
5988
+ abiertoChange = new EventEmitter();
5989
+ seleccionar = new EventEmitter();
5990
+ abiertoInterno = signal(true, /* @ts-ignore */
5991
+ ...(ngDevMode ? [{ debugName: "abiertoInterno" }] : /* istanbul ignore next */ []));
5992
+ menuNormalizado = signal([], /* @ts-ignore */
5993
+ ...(ngDevMode ? [{ debugName: "menuNormalizado" }] : /* istanbul ignore next */ []));
5994
+ expandidos = signal(new Set(), /* @ts-ignore */
5995
+ ...(ngDevMode ? [{ debugName: "expandidos" }] : /* istanbul ignore next */ []));
5996
+ rutaSeleccionada = signal('', /* @ts-ignore */
5997
+ ...(ngDevMode ? [{ debugName: "rutaSeleccionada" }] : /* istanbul ignore next */ []));
5998
+ constructor() {
5999
+ this.router.events
6000
+ .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
6001
+ .subscribe(() => this.expandirRutaActual());
6002
+ }
6003
+ alternarGrupo(clave) {
6004
+ if (!this.abiertoInterno()) {
6005
+ this.abiertoInterno.set(true);
6006
+ this.abiertoChange.emit(true);
6007
+ }
6008
+ this.expandidos.update((actuales) => {
6009
+ const siguientes = new Set(actuales);
6010
+ siguientes.has(clave) ? siguientes.delete(clave) : siguientes.add(clave);
6011
+ return siguientes;
6012
+ });
6013
+ }
6014
+ estaExpandido(clave) {
6015
+ return this.expandidos().has(clave);
6016
+ }
6017
+ seleccionarItem(item, nivel) {
6018
+ this.seleccionar.emit({ item, nivel });
6019
+ }
6020
+ esItemSeleccionado(item) {
6021
+ const ruta = this.normalizarRuta(item.ruta);
6022
+ return !!ruta && ruta === this.rutaSeleccionada();
6023
+ }
6024
+ claveItem(item, padre, indice) {
6025
+ return `${padre}/${item.nombre}-${indice}`;
6026
+ }
6027
+ inicialMarca() {
6028
+ return (this.titulo.trim().charAt(0) || 'T').toUpperCase();
6029
+ }
6030
+ normalizarMenu(items) {
6031
+ return [...items]
6032
+ .sort((a, b) => a.orden - b.orden)
6033
+ .map((item) => ({
6034
+ ...item,
6035
+ icono: item.icono?.trim() || 'folder',
6036
+ subMenus: this.normalizarMenu(item.subMenus ?? []),
6037
+ }));
6038
+ }
6039
+ expandirRutaActual() {
6040
+ const rutaActual = this.normalizarRuta(this.router.url);
6041
+ const coincidencia = this.buscarMejorCoincidenciaPorRuta(this.menuNormalizado(), rutaActual);
6042
+ this.rutaSeleccionada.set(coincidencia?.ruta ?? '');
6043
+ const clavesPadre = coincidencia?.ancestros ?? [];
6044
+ if (!clavesPadre.length)
6045
+ return;
6046
+ this.expandidos.update((actuales) => {
6047
+ const siguientes = new Set(actuales);
6048
+ clavesPadre.forEach((clave) => siguientes.add(clave));
6049
+ return siguientes;
6050
+ });
6051
+ }
6052
+ buscarMejorCoincidenciaPorRuta(items, rutaActual, padre = 'raiz', ancestros = []) {
6053
+ let mejor = null;
6054
+ for (let indice = 0; indice < items.length; indice += 1) {
6055
+ const item = items[indice];
6056
+ const clave = this.claveItem(item, padre, indice);
6057
+ const ruta = this.normalizarRuta(item.ruta);
6058
+ if (this.rutaCoincide(ruta, rutaActual)) {
6059
+ mejor = this.mejorCoincidencia(mejor, {
6060
+ ruta,
6061
+ ancestros,
6062
+ peso: ruta === rutaActual ? Number.MAX_SAFE_INTEGER : ruta.length,
6063
+ });
6064
+ }
6065
+ const resultado = this.buscarMejorCoincidenciaPorRuta(item.subMenus ?? [], rutaActual, clave, [...ancestros, clave]);
6066
+ mejor = this.mejorCoincidencia(mejor, resultado);
6067
+ }
6068
+ return mejor;
6069
+ }
6070
+ mejorCoincidencia(actual, candidata) {
6071
+ if (!candidata)
6072
+ return actual;
6073
+ if (!actual)
6074
+ return candidata;
6075
+ return candidata.peso > actual.peso ? candidata : actual;
6076
+ }
6077
+ rutaCoincide(rutaMenu, rutaActual) {
6078
+ if (!rutaMenu)
6079
+ return false;
6080
+ if (rutaMenu === '/')
6081
+ return rutaActual === '/';
6082
+ return rutaActual === rutaMenu || rutaActual.startsWith(`${rutaMenu}/`);
6083
+ }
6084
+ normalizarRuta(ruta) {
6085
+ if (!ruta)
6086
+ return '';
6087
+ const limpia = ruta.split('?')[0].split('#')[0].trim();
6088
+ if (!limpia || limpia === '/')
6089
+ return '/';
6090
+ const conSlashInicial = limpia.startsWith('/') ? limpia : `/${limpia}`;
6091
+ return conSlashInicial.endsWith('/') ? conSlashInicial.slice(0, -1) : conSlashInicial;
6092
+ }
6093
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TdSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
6094
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: TdSidebar, isStandalone: true, selector: "td-sidebar", inputs: { titulo: "titulo", subtitulo: "subtitulo", etiquetaNavegacion: "etiquetaNavegacion", logoUrl: "logoUrl", logoAlt: "logoAlt", textoCarga: "textoCarga", mensajeVacio: "mensajeVacio", error: "error", cargando: "cargando", oscuro: "oscuro", modoCerrado: "modoCerrado", abierto: "abierto", menu: "menu" }, outputs: { abiertoChange: "abiertoChange", seleccionar: "seleccionar" }, ngImport: i0, template: "<aside\n class=\"td-sidebar\"\n [class.td-sidebar--compacto]=\"!abiertoInterno() && modoCerrado === 'compacto'\"\n [class.td-sidebar--oculto]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n [class.td-sidebar--oscuro]=\"oscuro\"\n [attr.aria-hidden]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n>\n <header class=\"td-sidebar__marca\">\n <div class=\"td-sidebar__logo\">\n @if (logoUrl) {\n <img [src]=\"logoUrl\" [alt]=\"logoAlt\" />\n } @else {\n <span>{{ inicialMarca() }}</span>\n }\n </div>\n\n @if (abiertoInterno()) {\n <div class=\"td-sidebar__marca-texto\">\n <small>{{ subtitulo }}</small>\n <strong>{{ titulo }}</strong>\n </div>\n }\n\n </header>\n\n <div class=\"td-sidebar__encabezado\">\n @if (abiertoInterno()) {\n <span>{{ etiquetaNavegacion }}</span>\n } @else {\n <span aria-hidden=\"true\">\u2022\u2022\u2022</span>\n }\n </div>\n\n <nav class=\"td-sidebar__nav\" aria-label=\"Men\u00FA principal\">\n @if (cargando) {\n <p class=\"td-sidebar__estado\">{{ textoCarga }}</p>\n } @else if (error) {\n <p class=\"td-sidebar__estado td-sidebar__estado--error\">{{ error }}</p>\n } @else if (!menuNormalizado().length) {\n <p class=\"td-sidebar__estado\">{{ mensajeVacio }}</p>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{ items: menuNormalizado(), nivel: 0, padre: 'raiz' }\"\n />\n }\n </nav>\n</aside>\n\n<ng-template #listaMenu let-items=\"items\" let-nivel=\"nivel\" let-padre=\"padre\">\n <ul class=\"td-sidebar__lista\" [class.td-sidebar__lista--anidada]=\"nivel > 0\">\n @for (item of items; track item) {\n @let clave = claveItem(item, padre, $index);\n @let tieneHijos = item.subMenus.length > 0;\n\n <li class=\"td-sidebar__item\">\n @if (tieneHijos) {\n <button\n class=\"td-sidebar__enlace\"\n type=\"button\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.aria-expanded]=\"estaExpandido(clave)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"alternarGrupo(clave)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n <td-icon\n class=\"td-sidebar__flecha\"\n [class.td-sidebar__flecha--abierta]=\"estaExpandido(clave)\"\n nombre=\"chevron_right\"\n />\n }\n </button>\n } @else {\n <a\n class=\"td-sidebar__enlace\"\n [routerLink]=\"item.ruta || '/'\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"seleccionarItem(item, nivel)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n }\n </a>\n }\n\n @if (abiertoInterno() && tieneHijos && estaExpandido(clave)) {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{\n items: item.subMenus,\n nivel: nivel + 1,\n padre: clave\n }\"\n />\n }\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:block;height:100%}*{box-sizing:border-box}.td-sidebar{--td-fondo: var(--td-color-surface, #fff);--td-borde: var(--td-color-border, #e2e8f0);--td-texto: var(--td-color-text, #334155);--td-texto-suave: var(--td-color-text-muted, #64748b);--td-hover: var(--td-color-surface-soft, #f1f5f9);--td-activo: var(--td-color-primary-soft, #e8efff);--td-acento: var(--td-color-primary, #2563eb);display:flex;width:18rem;height:100%;min-height:28rem;flex-direction:column;overflow:hidden;border-right:1px solid var(--td-borde);color:var(--td-texto);background:var(--td-fondo);font-family:var(--td-font-family, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif);transition:width .2s ease,background .2s ease,border-color .2s ease}.td-sidebar--oscuro{--td-fondo: var(--td-color-surface, #09090b);--td-borde: var(--td-color-border, #27272a);--td-texto: var(--td-color-text, #d4d4d8);--td-texto-suave: var(--td-color-text-muted, #71717a);--td-hover: var(--td-color-surface-soft, #18181b);--td-activo: var(--td-color-primary-soft, #172554);--td-acento: var(--td-color-primary, #60a5fa)}.td-sidebar--compacto{width:5rem}.td-sidebar--oculto{width:0;min-width:0;border-right-width:0;visibility:hidden}.td-sidebar__marca{display:flex;min-height:5rem;align-items:center;gap:.75rem;border-bottom:1px solid var(--td-borde);padding:1rem}.td-sidebar__logo{display:grid;width:2.75rem;height:2.75rem;flex:0 0 2.75rem;place-items:center;overflow:hidden;border:1px solid var(--td-borde);border-radius:var(--td-radius-md, .75rem);color:var(--td-color-on-primary, #fff);background:linear-gradient(135deg,var(--td-color-primary, #2563eb),var(--td-color-info, #3157a4));font-size:1.15rem;font-weight:800}.td-sidebar__logo img{width:75%;height:75%;object-fit:contain}.td-sidebar__marca-texto{min-width:0;flex:1}.td-sidebar__marca-texto small,.td-sidebar__marca-texto strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__marca-texto small{color:var(--td-acento);font-size:.64rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar__marca-texto strong{margin-top:.18rem;font-size:.84rem}.td-sidebar__encabezado{min-height:3.35rem;padding:1.25rem 1rem .65rem;color:var(--td-texto-suave);font-size:.66rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar--compacto .td-sidebar__encabezado{text-align:center}.td-sidebar__nav{min-height:0;flex:1;overflow-x:hidden;overflow-y:auto;padding:0 .7rem 1rem;scrollbar-width:thin;scrollbar-color:var(--td-borde) transparent}.td-sidebar__lista{margin:0;padding:0;list-style:none}.td-sidebar__lista--anidada{position:relative;margin:.2rem 0 .35rem 1.25rem;padding-left:.7rem}.td-sidebar__lista--anidada:before{position:absolute;inset:0 auto 0 0;width:1px;background:var(--td-borde);content:\"\"}.td-sidebar__item+.td-sidebar__item{margin-top:.2rem}.td-sidebar__enlace{display:flex;width:100%;min-width:0;min-height:2.5rem;align-items:center;gap:.7rem;border:0;border-radius:var(--td-radius-sm, .55rem);padding:.55rem .7rem;color:var(--td-texto);background:transparent;font:inherit;font-size:.82rem;font-weight:600;text-align:left;text-decoration:none;cursor:pointer;transition:color .12s ease,background .12s ease}.td-sidebar__enlace:hover{background:var(--td-hover)}.td-sidebar__enlace--activo,.td-sidebar__enlace--ruta-activa{color:var(--td-acento);background:var(--td-activo)}.td-sidebar__enlace span{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__icono{width:1.15rem;height:1.15rem;flex:0 0 1.15rem}.td-sidebar__flecha{width:.9rem;height:.9rem;flex:0 0 .9rem;transition:transform .16s ease}.td-sidebar__flecha--abierta{transform:rotate(90deg)}.td-sidebar--compacto .td-sidebar__marca{justify-content:center;padding-inline:.7rem}.td-sidebar--compacto .td-sidebar__nav{padding-inline:.65rem}.td-sidebar--compacto .td-sidebar__enlace{justify-content:center;padding-inline:0}.td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2.2rem;font-size:.75rem;font-weight:500}.td-sidebar__lista--anidada .td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2rem;color:var(--td-texto-suave);font-size:.7rem}.td-sidebar__estado{margin:0;padding:.75rem;color:var(--td-texto-suave);font-size:.75rem;line-height:1.45}.td-sidebar__estado--error{color:var(--td-color-warning, #d97706)}@media(prefers-reduced-motion:reduce){.td-sidebar,.td-sidebar__flecha{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: TdIcon, selector: "td-icon", inputs: ["nombre", "titulo", "tamano"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6095
+ }
6096
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TdSidebar, decorators: [{
6097
+ type: Component,
6098
+ args: [{ selector: 'td-sidebar', imports: [NgTemplateOutlet, RouterLink, TdIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<aside\n class=\"td-sidebar\"\n [class.td-sidebar--compacto]=\"!abiertoInterno() && modoCerrado === 'compacto'\"\n [class.td-sidebar--oculto]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n [class.td-sidebar--oscuro]=\"oscuro\"\n [attr.aria-hidden]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n>\n <header class=\"td-sidebar__marca\">\n <div class=\"td-sidebar__logo\">\n @if (logoUrl) {\n <img [src]=\"logoUrl\" [alt]=\"logoAlt\" />\n } @else {\n <span>{{ inicialMarca() }}</span>\n }\n </div>\n\n @if (abiertoInterno()) {\n <div class=\"td-sidebar__marca-texto\">\n <small>{{ subtitulo }}</small>\n <strong>{{ titulo }}</strong>\n </div>\n }\n\n </header>\n\n <div class=\"td-sidebar__encabezado\">\n @if (abiertoInterno()) {\n <span>{{ etiquetaNavegacion }}</span>\n } @else {\n <span aria-hidden=\"true\">\u2022\u2022\u2022</span>\n }\n </div>\n\n <nav class=\"td-sidebar__nav\" aria-label=\"Men\u00FA principal\">\n @if (cargando) {\n <p class=\"td-sidebar__estado\">{{ textoCarga }}</p>\n } @else if (error) {\n <p class=\"td-sidebar__estado td-sidebar__estado--error\">{{ error }}</p>\n } @else if (!menuNormalizado().length) {\n <p class=\"td-sidebar__estado\">{{ mensajeVacio }}</p>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{ items: menuNormalizado(), nivel: 0, padre: 'raiz' }\"\n />\n }\n </nav>\n</aside>\n\n<ng-template #listaMenu let-items=\"items\" let-nivel=\"nivel\" let-padre=\"padre\">\n <ul class=\"td-sidebar__lista\" [class.td-sidebar__lista--anidada]=\"nivel > 0\">\n @for (item of items; track item) {\n @let clave = claveItem(item, padre, $index);\n @let tieneHijos = item.subMenus.length > 0;\n\n <li class=\"td-sidebar__item\">\n @if (tieneHijos) {\n <button\n class=\"td-sidebar__enlace\"\n type=\"button\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.aria-expanded]=\"estaExpandido(clave)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"alternarGrupo(clave)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n <td-icon\n class=\"td-sidebar__flecha\"\n [class.td-sidebar__flecha--abierta]=\"estaExpandido(clave)\"\n nombre=\"chevron_right\"\n />\n }\n </button>\n } @else {\n <a\n class=\"td-sidebar__enlace\"\n [routerLink]=\"item.ruta || '/'\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"seleccionarItem(item, nivel)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n }\n </a>\n }\n\n @if (abiertoInterno() && tieneHijos && estaExpandido(clave)) {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{\n items: item.subMenus,\n nivel: nivel + 1,\n padre: clave\n }\"\n />\n }\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:block;height:100%}*{box-sizing:border-box}.td-sidebar{--td-fondo: var(--td-color-surface, #fff);--td-borde: var(--td-color-border, #e2e8f0);--td-texto: var(--td-color-text, #334155);--td-texto-suave: var(--td-color-text-muted, #64748b);--td-hover: var(--td-color-surface-soft, #f1f5f9);--td-activo: var(--td-color-primary-soft, #e8efff);--td-acento: var(--td-color-primary, #2563eb);display:flex;width:18rem;height:100%;min-height:28rem;flex-direction:column;overflow:hidden;border-right:1px solid var(--td-borde);color:var(--td-texto);background:var(--td-fondo);font-family:var(--td-font-family, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif);transition:width .2s ease,background .2s ease,border-color .2s ease}.td-sidebar--oscuro{--td-fondo: var(--td-color-surface, #09090b);--td-borde: var(--td-color-border, #27272a);--td-texto: var(--td-color-text, #d4d4d8);--td-texto-suave: var(--td-color-text-muted, #71717a);--td-hover: var(--td-color-surface-soft, #18181b);--td-activo: var(--td-color-primary-soft, #172554);--td-acento: var(--td-color-primary, #60a5fa)}.td-sidebar--compacto{width:5rem}.td-sidebar--oculto{width:0;min-width:0;border-right-width:0;visibility:hidden}.td-sidebar__marca{display:flex;min-height:5rem;align-items:center;gap:.75rem;border-bottom:1px solid var(--td-borde);padding:1rem}.td-sidebar__logo{display:grid;width:2.75rem;height:2.75rem;flex:0 0 2.75rem;place-items:center;overflow:hidden;border:1px solid var(--td-borde);border-radius:var(--td-radius-md, .75rem);color:var(--td-color-on-primary, #fff);background:linear-gradient(135deg,var(--td-color-primary, #2563eb),var(--td-color-info, #3157a4));font-size:1.15rem;font-weight:800}.td-sidebar__logo img{width:75%;height:75%;object-fit:contain}.td-sidebar__marca-texto{min-width:0;flex:1}.td-sidebar__marca-texto small,.td-sidebar__marca-texto strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__marca-texto small{color:var(--td-acento);font-size:.64rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar__marca-texto strong{margin-top:.18rem;font-size:.84rem}.td-sidebar__encabezado{min-height:3.35rem;padding:1.25rem 1rem .65rem;color:var(--td-texto-suave);font-size:.66rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar--compacto .td-sidebar__encabezado{text-align:center}.td-sidebar__nav{min-height:0;flex:1;overflow-x:hidden;overflow-y:auto;padding:0 .7rem 1rem;scrollbar-width:thin;scrollbar-color:var(--td-borde) transparent}.td-sidebar__lista{margin:0;padding:0;list-style:none}.td-sidebar__lista--anidada{position:relative;margin:.2rem 0 .35rem 1.25rem;padding-left:.7rem}.td-sidebar__lista--anidada:before{position:absolute;inset:0 auto 0 0;width:1px;background:var(--td-borde);content:\"\"}.td-sidebar__item+.td-sidebar__item{margin-top:.2rem}.td-sidebar__enlace{display:flex;width:100%;min-width:0;min-height:2.5rem;align-items:center;gap:.7rem;border:0;border-radius:var(--td-radius-sm, .55rem);padding:.55rem .7rem;color:var(--td-texto);background:transparent;font:inherit;font-size:.82rem;font-weight:600;text-align:left;text-decoration:none;cursor:pointer;transition:color .12s ease,background .12s ease}.td-sidebar__enlace:hover{background:var(--td-hover)}.td-sidebar__enlace--activo,.td-sidebar__enlace--ruta-activa{color:var(--td-acento);background:var(--td-activo)}.td-sidebar__enlace span{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__icono{width:1.15rem;height:1.15rem;flex:0 0 1.15rem}.td-sidebar__flecha{width:.9rem;height:.9rem;flex:0 0 .9rem;transition:transform .16s ease}.td-sidebar__flecha--abierta{transform:rotate(90deg)}.td-sidebar--compacto .td-sidebar__marca{justify-content:center;padding-inline:.7rem}.td-sidebar--compacto .td-sidebar__nav{padding-inline:.65rem}.td-sidebar--compacto .td-sidebar__enlace{justify-content:center;padding-inline:0}.td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2.2rem;font-size:.75rem;font-weight:500}.td-sidebar__lista--anidada .td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2rem;color:var(--td-texto-suave);font-size:.7rem}.td-sidebar__estado{margin:0;padding:.75rem;color:var(--td-texto-suave);font-size:.75rem;line-height:1.45}.td-sidebar__estado--error{color:var(--td-color-warning, #d97706)}@media(prefers-reduced-motion:reduce){.td-sidebar,.td-sidebar__flecha{transition:none}}\n"] }]
6099
+ }], ctorParameters: () => [], propDecorators: { titulo: [{
6100
+ type: Input
6101
+ }], subtitulo: [{
6102
+ type: Input
6103
+ }], etiquetaNavegacion: [{
6104
+ type: Input
6105
+ }], logoUrl: [{
6106
+ type: Input
6107
+ }], logoAlt: [{
6108
+ type: Input
6109
+ }], textoCarga: [{
6110
+ type: Input
6111
+ }], mensajeVacio: [{
6112
+ type: Input
6113
+ }], error: [{
6114
+ type: Input
6115
+ }], cargando: [{
6116
+ type: Input
6117
+ }], oscuro: [{
6118
+ type: Input
6119
+ }], modoCerrado: [{
6120
+ type: Input
6121
+ }], abierto: [{
6122
+ type: Input
6123
+ }], menu: [{
6124
+ type: Input
6125
+ }], abiertoChange: [{
6126
+ type: Output
6127
+ }], seleccionar: [{
6128
+ type: Output
6129
+ }] } });
6130
+
5961
6131
  class TdLayoutHeaderSlot {
5962
6132
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TdLayoutHeaderSlot, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5963
6133
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.4", type: TdLayoutHeaderSlot, isStandalone: true, selector: "[tdLayoutHeader]", ngImport: i0 });
@@ -6001,6 +6171,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
6001
6171
  class TdLayout {
6002
6172
  headerSlot;
6003
6173
  sidebarSlot;
6174
+ sidebarComponent;
6004
6175
  footerSlot;
6005
6176
  headerHost;
6006
6177
  sidebarHost;
@@ -6010,8 +6181,10 @@ class TdLayout {
6010
6181
  mobile = signal(false, /* @ts-ignore */
6011
6182
  ...(ngDevMode ? [{ debugName: "mobile" }] : /* istanbul ignore next */ []));
6012
6183
  resizeObserver;
6184
+ sidebarSelectionSubscription;
6013
6185
  fillViewport = true;
6014
6186
  closeSidebarOnBackdrop = true;
6187
+ closeSidebarOnNavigation = true;
6015
6188
  mobileBreakpoint = 960;
6016
6189
  sidebarPlacement = 'auto';
6017
6190
  headerSpan = 'content';
@@ -6035,6 +6208,11 @@ class TdLayout {
6035
6208
  }
6036
6209
  ngAfterContentInit() {
6037
6210
  this.syncViewport();
6211
+ this.sidebarSelectionSubscription = this.sidebarComponent?.seleccionar.subscribe(() => {
6212
+ if (this.closeSidebarOnNavigation && this.mobile() && this.sidebarAsOverlay()) {
6213
+ this.closeSidebar();
6214
+ }
6215
+ });
6038
6216
  }
6039
6217
  ngAfterViewInit() {
6040
6218
  this.updateHeaderOffset();
@@ -6047,6 +6225,7 @@ class TdLayout {
6047
6225
  }
6048
6226
  ngOnDestroy() {
6049
6227
  this.resizeObserver?.disconnect();
6228
+ this.sidebarSelectionSubscription?.unsubscribe();
6050
6229
  }
6051
6230
  syncViewport() {
6052
6231
  if (typeof window === 'undefined') {
@@ -6119,7 +6298,7 @@ class TdLayout {
6119
6298
  this.sidebarOpenChange.emit(value);
6120
6299
  }
6121
6300
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TdLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
6122
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: TdLayout, isStandalone: true, selector: "td-layout, td-shell", inputs: { fillViewport: ["fillViewport", "fillViewport", booleanAttribute], closeSidebarOnBackdrop: ["closeSidebarOnBackdrop", "closeSidebarOnBackdrop", booleanAttribute], mobileBreakpoint: "mobileBreakpoint", sidebarPlacement: "sidebarPlacement", headerSpan: "headerSpan", footerSpan: "footerSpan", mobileSidebarStyle: "mobileSidebarStyle", sidebarOpen: "sidebarOpen" }, outputs: { sidebarOpenChange: "sidebarOpenChange" }, host: { listeners: { "window:resize": "syncViewport()", "window:scroll": "syncOverlayPosition()", "document:pointerdown": "handlePointerDownOutside($event)" } }, queries: [{ propertyName: "headerSlot", first: true, predicate: TdLayoutHeaderSlot, descendants: true }, { propertyName: "sidebarSlot", first: true, predicate: TdLayoutSidebarSlot, descendants: true }, { propertyName: "footerSlot", first: true, predicate: TdLayoutFooterSlot, descendants: true }], viewQueries: [{ propertyName: "headerHost", first: true, predicate: ["headerHost"], descendants: true, read: ElementRef }, { propertyName: "sidebarHost", first: true, predicate: ["sidebarHost"], descendants: true, read: ElementRef }], ngImport: i0, template: `
6301
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: TdLayout, isStandalone: true, selector: "td-layout, td-shell", inputs: { fillViewport: ["fillViewport", "fillViewport", booleanAttribute], closeSidebarOnBackdrop: ["closeSidebarOnBackdrop", "closeSidebarOnBackdrop", booleanAttribute], closeSidebarOnNavigation: ["closeSidebarOnNavigation", "closeSidebarOnNavigation", booleanAttribute], mobileBreakpoint: "mobileBreakpoint", sidebarPlacement: "sidebarPlacement", headerSpan: "headerSpan", footerSpan: "footerSpan", mobileSidebarStyle: "mobileSidebarStyle", sidebarOpen: "sidebarOpen" }, outputs: { sidebarOpenChange: "sidebarOpenChange" }, host: { listeners: { "window:resize": "syncViewport()", "window:scroll": "syncOverlayPosition()", "document:pointerdown": "handlePointerDownOutside($event)" } }, queries: [{ propertyName: "headerSlot", first: true, predicate: TdLayoutHeaderSlot, descendants: true }, { propertyName: "sidebarSlot", first: true, predicate: TdLayoutSidebarSlot, descendants: true }, { propertyName: "sidebarComponent", first: true, predicate: TdSidebar, descendants: true }, { propertyName: "footerSlot", first: true, predicate: TdLayoutFooterSlot, descendants: true }], viewQueries: [{ propertyName: "headerHost", first: true, predicate: ["headerHost"], descendants: true, read: ElementRef }, { propertyName: "sidebarHost", first: true, predicate: ["sidebarHost"], descendants: true, read: ElementRef }], ngImport: i0, template: `
6123
6302
  <div
6124
6303
  class="td-layout"
6125
6304
  [class.td-layout--viewport]="fillViewport"
@@ -6228,6 +6407,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
6228
6407
  }], sidebarSlot: [{
6229
6408
  type: ContentChild,
6230
6409
  args: [TdLayoutSidebarSlot]
6410
+ }], sidebarComponent: [{
6411
+ type: ContentChild,
6412
+ args: [TdSidebar]
6231
6413
  }], footerSlot: [{
6232
6414
  type: ContentChild,
6233
6415
  args: [TdLayoutFooterSlot]
@@ -6243,6 +6425,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
6243
6425
  }], closeSidebarOnBackdrop: [{
6244
6426
  type: Input,
6245
6427
  args: [{ transform: booleanAttribute }]
6428
+ }], closeSidebarOnNavigation: [{
6429
+ type: Input,
6430
+ args: [{ transform: booleanAttribute }]
6246
6431
  }], mobileBreakpoint: [{
6247
6432
  type: Input
6248
6433
  }], sidebarPlacement: [{
@@ -7845,176 +8030,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
7845
8030
  type: Output
7846
8031
  }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
7847
8032
 
7848
- class TdSidebar {
7849
- router = inject(Router);
7850
- destroyRef = inject(DestroyRef);
7851
- titulo = 'Gestión Corporativa';
7852
- subtitulo = 'Central Platform';
7853
- etiquetaNavegacion = 'Navegación';
7854
- logoUrl = '';
7855
- logoAlt = 'Logo';
7856
- textoCarga = 'Cargando menú...';
7857
- mensajeVacio = 'No hay opciones disponibles.';
7858
- error = '';
7859
- cargando = false;
7860
- oscuro = false;
7861
- modoCerrado = 'compacto';
7862
- set abierto(value) {
7863
- this.abiertoInterno.set(value);
7864
- }
7865
- get abierto() {
7866
- return this.abiertoInterno();
7867
- }
7868
- set menu(value) {
7869
- this.menuNormalizado.set(this.normalizarMenu(value ?? []));
7870
- this.expandirRutaActual();
7871
- }
7872
- get menu() {
7873
- return this.menuNormalizado();
7874
- }
7875
- abiertoChange = new EventEmitter();
7876
- seleccionar = new EventEmitter();
7877
- abiertoInterno = signal(true, /* @ts-ignore */
7878
- ...(ngDevMode ? [{ debugName: "abiertoInterno" }] : /* istanbul ignore next */ []));
7879
- menuNormalizado = signal([], /* @ts-ignore */
7880
- ...(ngDevMode ? [{ debugName: "menuNormalizado" }] : /* istanbul ignore next */ []));
7881
- expandidos = signal(new Set(), /* @ts-ignore */
7882
- ...(ngDevMode ? [{ debugName: "expandidos" }] : /* istanbul ignore next */ []));
7883
- rutaSeleccionada = signal('', /* @ts-ignore */
7884
- ...(ngDevMode ? [{ debugName: "rutaSeleccionada" }] : /* istanbul ignore next */ []));
7885
- constructor() {
7886
- this.router.events
7887
- .pipe(filter((event) => event instanceof NavigationEnd), takeUntilDestroyed(this.destroyRef))
7888
- .subscribe(() => this.expandirRutaActual());
7889
- }
7890
- alternarGrupo(clave) {
7891
- if (!this.abiertoInterno()) {
7892
- this.abiertoInterno.set(true);
7893
- this.abiertoChange.emit(true);
7894
- }
7895
- this.expandidos.update((actuales) => {
7896
- const siguientes = new Set(actuales);
7897
- siguientes.has(clave) ? siguientes.delete(clave) : siguientes.add(clave);
7898
- return siguientes;
7899
- });
7900
- }
7901
- estaExpandido(clave) {
7902
- return this.expandidos().has(clave);
7903
- }
7904
- seleccionarItem(item, nivel) {
7905
- this.seleccionar.emit({ item, nivel });
7906
- }
7907
- esItemSeleccionado(item) {
7908
- const ruta = this.normalizarRuta(item.ruta);
7909
- return !!ruta && ruta === this.rutaSeleccionada();
7910
- }
7911
- claveItem(item, padre, indice) {
7912
- return `${padre}/${item.nombre}-${indice}`;
7913
- }
7914
- inicialMarca() {
7915
- return (this.titulo.trim().charAt(0) || 'T').toUpperCase();
7916
- }
7917
- normalizarMenu(items) {
7918
- return [...items]
7919
- .sort((a, b) => a.orden - b.orden)
7920
- .map((item) => ({
7921
- ...item,
7922
- icono: item.icono?.trim() || 'folder',
7923
- subMenus: this.normalizarMenu(item.subMenus ?? []),
7924
- }));
7925
- }
7926
- expandirRutaActual() {
7927
- const rutaActual = this.normalizarRuta(this.router.url);
7928
- const coincidencia = this.buscarMejorCoincidenciaPorRuta(this.menuNormalizado(), rutaActual);
7929
- this.rutaSeleccionada.set(coincidencia?.ruta ?? '');
7930
- const clavesPadre = coincidencia?.ancestros ?? [];
7931
- if (!clavesPadre.length)
7932
- return;
7933
- this.expandidos.update((actuales) => {
7934
- const siguientes = new Set(actuales);
7935
- clavesPadre.forEach((clave) => siguientes.add(clave));
7936
- return siguientes;
7937
- });
7938
- }
7939
- buscarMejorCoincidenciaPorRuta(items, rutaActual, padre = 'raiz', ancestros = []) {
7940
- let mejor = null;
7941
- for (let indice = 0; indice < items.length; indice += 1) {
7942
- const item = items[indice];
7943
- const clave = this.claveItem(item, padre, indice);
7944
- const ruta = this.normalizarRuta(item.ruta);
7945
- if (this.rutaCoincide(ruta, rutaActual)) {
7946
- mejor = this.mejorCoincidencia(mejor, {
7947
- ruta,
7948
- ancestros,
7949
- peso: ruta === rutaActual ? Number.MAX_SAFE_INTEGER : ruta.length,
7950
- });
7951
- }
7952
- const resultado = this.buscarMejorCoincidenciaPorRuta(item.subMenus ?? [], rutaActual, clave, [...ancestros, clave]);
7953
- mejor = this.mejorCoincidencia(mejor, resultado);
7954
- }
7955
- return mejor;
7956
- }
7957
- mejorCoincidencia(actual, candidata) {
7958
- if (!candidata)
7959
- return actual;
7960
- if (!actual)
7961
- return candidata;
7962
- return candidata.peso > actual.peso ? candidata : actual;
7963
- }
7964
- rutaCoincide(rutaMenu, rutaActual) {
7965
- if (!rutaMenu)
7966
- return false;
7967
- if (rutaMenu === '/')
7968
- return rutaActual === '/';
7969
- return rutaActual === rutaMenu || rutaActual.startsWith(`${rutaMenu}/`);
7970
- }
7971
- normalizarRuta(ruta) {
7972
- if (!ruta)
7973
- return '';
7974
- const limpia = ruta.split('?')[0].split('#')[0].trim();
7975
- if (!limpia || limpia === '/')
7976
- return '/';
7977
- const conSlashInicial = limpia.startsWith('/') ? limpia : `/${limpia}`;
7978
- return conSlashInicial.endsWith('/') ? conSlashInicial.slice(0, -1) : conSlashInicial;
7979
- }
7980
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TdSidebar, deps: [], target: i0.ɵɵFactoryTarget.Component });
7981
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: TdSidebar, isStandalone: true, selector: "td-sidebar", inputs: { titulo: "titulo", subtitulo: "subtitulo", etiquetaNavegacion: "etiquetaNavegacion", logoUrl: "logoUrl", logoAlt: "logoAlt", textoCarga: "textoCarga", mensajeVacio: "mensajeVacio", error: "error", cargando: "cargando", oscuro: "oscuro", modoCerrado: "modoCerrado", abierto: "abierto", menu: "menu" }, outputs: { abiertoChange: "abiertoChange", seleccionar: "seleccionar" }, ngImport: i0, template: "<aside\n class=\"td-sidebar\"\n [class.td-sidebar--compacto]=\"!abiertoInterno() && modoCerrado === 'compacto'\"\n [class.td-sidebar--oculto]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n [class.td-sidebar--oscuro]=\"oscuro\"\n [attr.aria-hidden]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n>\n <header class=\"td-sidebar__marca\">\n <div class=\"td-sidebar__logo\">\n @if (logoUrl) {\n <img [src]=\"logoUrl\" [alt]=\"logoAlt\" />\n } @else {\n <span>{{ inicialMarca() }}</span>\n }\n </div>\n\n @if (abiertoInterno()) {\n <div class=\"td-sidebar__marca-texto\">\n <small>{{ subtitulo }}</small>\n <strong>{{ titulo }}</strong>\n </div>\n }\n\n </header>\n\n <div class=\"td-sidebar__encabezado\">\n @if (abiertoInterno()) {\n <span>{{ etiquetaNavegacion }}</span>\n } @else {\n <span aria-hidden=\"true\">\u2022\u2022\u2022</span>\n }\n </div>\n\n <nav class=\"td-sidebar__nav\" aria-label=\"Men\u00FA principal\">\n @if (cargando) {\n <p class=\"td-sidebar__estado\">{{ textoCarga }}</p>\n } @else if (error) {\n <p class=\"td-sidebar__estado td-sidebar__estado--error\">{{ error }}</p>\n } @else if (!menuNormalizado().length) {\n <p class=\"td-sidebar__estado\">{{ mensajeVacio }}</p>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{ items: menuNormalizado(), nivel: 0, padre: 'raiz' }\"\n />\n }\n </nav>\n</aside>\n\n<ng-template #listaMenu let-items=\"items\" let-nivel=\"nivel\" let-padre=\"padre\">\n <ul class=\"td-sidebar__lista\" [class.td-sidebar__lista--anidada]=\"nivel > 0\">\n @for (item of items; track item) {\n @let clave = claveItem(item, padre, $index);\n @let tieneHijos = item.subMenus.length > 0;\n\n <li class=\"td-sidebar__item\">\n @if (tieneHijos) {\n <button\n class=\"td-sidebar__enlace\"\n type=\"button\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.aria-expanded]=\"estaExpandido(clave)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"alternarGrupo(clave)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n <td-icon\n class=\"td-sidebar__flecha\"\n [class.td-sidebar__flecha--abierta]=\"estaExpandido(clave)\"\n nombre=\"chevron_right\"\n />\n }\n </button>\n } @else {\n <a\n class=\"td-sidebar__enlace\"\n [routerLink]=\"item.ruta || '/'\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"seleccionarItem(item, nivel)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n }\n </a>\n }\n\n @if (abiertoInterno() && tieneHijos && estaExpandido(clave)) {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{\n items: item.subMenus,\n nivel: nivel + 1,\n padre: clave\n }\"\n />\n }\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:block;height:100%}*{box-sizing:border-box}.td-sidebar{--td-fondo: var(--td-color-surface, #fff);--td-borde: var(--td-color-border, #e2e8f0);--td-texto: var(--td-color-text, #334155);--td-texto-suave: var(--td-color-text-muted, #64748b);--td-hover: var(--td-color-surface-soft, #f1f5f9);--td-activo: var(--td-color-primary-soft, #e8efff);--td-acento: var(--td-color-primary, #2563eb);display:flex;width:18rem;height:100%;min-height:28rem;flex-direction:column;overflow:hidden;border-right:1px solid var(--td-borde);color:var(--td-texto);background:var(--td-fondo);font-family:var(--td-font-family, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif);transition:width .2s ease,background .2s ease,border-color .2s ease}.td-sidebar--oscuro{--td-fondo: var(--td-color-surface, #09090b);--td-borde: var(--td-color-border, #27272a);--td-texto: var(--td-color-text, #d4d4d8);--td-texto-suave: var(--td-color-text-muted, #71717a);--td-hover: var(--td-color-surface-soft, #18181b);--td-activo: var(--td-color-primary-soft, #172554);--td-acento: var(--td-color-primary, #60a5fa)}.td-sidebar--compacto{width:5rem}.td-sidebar--oculto{width:0;min-width:0;border-right-width:0;visibility:hidden}.td-sidebar__marca{display:flex;min-height:5rem;align-items:center;gap:.75rem;border-bottom:1px solid var(--td-borde);padding:1rem}.td-sidebar__logo{display:grid;width:2.75rem;height:2.75rem;flex:0 0 2.75rem;place-items:center;overflow:hidden;border:1px solid var(--td-borde);border-radius:var(--td-radius-md, .75rem);color:var(--td-color-on-primary, #fff);background:linear-gradient(135deg,var(--td-color-primary, #2563eb),var(--td-color-info, #3157a4));font-size:1.15rem;font-weight:800}.td-sidebar__logo img{width:75%;height:75%;object-fit:contain}.td-sidebar__marca-texto{min-width:0;flex:1}.td-sidebar__marca-texto small,.td-sidebar__marca-texto strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__marca-texto small{color:var(--td-acento);font-size:.64rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar__marca-texto strong{margin-top:.18rem;font-size:.84rem}.td-sidebar__encabezado{min-height:3.35rem;padding:1.25rem 1rem .65rem;color:var(--td-texto-suave);font-size:.66rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar--compacto .td-sidebar__encabezado{text-align:center}.td-sidebar__nav{min-height:0;flex:1;overflow-x:hidden;overflow-y:auto;padding:0 .7rem 1rem;scrollbar-width:thin;scrollbar-color:var(--td-borde) transparent}.td-sidebar__lista{margin:0;padding:0;list-style:none}.td-sidebar__lista--anidada{position:relative;margin:.2rem 0 .35rem 1.25rem;padding-left:.7rem}.td-sidebar__lista--anidada:before{position:absolute;inset:0 auto 0 0;width:1px;background:var(--td-borde);content:\"\"}.td-sidebar__item+.td-sidebar__item{margin-top:.2rem}.td-sidebar__enlace{display:flex;width:100%;min-width:0;min-height:2.5rem;align-items:center;gap:.7rem;border:0;border-radius:var(--td-radius-sm, .55rem);padding:.55rem .7rem;color:var(--td-texto);background:transparent;font:inherit;font-size:.82rem;font-weight:600;text-align:left;text-decoration:none;cursor:pointer;transition:color .12s ease,background .12s ease}.td-sidebar__enlace:hover{background:var(--td-hover)}.td-sidebar__enlace--activo,.td-sidebar__enlace--ruta-activa{color:var(--td-acento);background:var(--td-activo)}.td-sidebar__enlace span{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__icono{width:1.15rem;height:1.15rem;flex:0 0 1.15rem}.td-sidebar__flecha{width:.9rem;height:.9rem;flex:0 0 .9rem;transition:transform .16s ease}.td-sidebar__flecha--abierta{transform:rotate(90deg)}.td-sidebar--compacto .td-sidebar__marca{justify-content:center;padding-inline:.7rem}.td-sidebar--compacto .td-sidebar__nav{padding-inline:.65rem}.td-sidebar--compacto .td-sidebar__enlace{justify-content:center;padding-inline:0}.td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2.2rem;font-size:.75rem;font-weight:500}.td-sidebar__lista--anidada .td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2rem;color:var(--td-texto-suave);font-size:.7rem}.td-sidebar__estado{margin:0;padding:.75rem;color:var(--td-texto-suave);font-size:.75rem;line-height:1.45}.td-sidebar__estado--error{color:var(--td-color-warning, #d97706)}@media(prefers-reduced-motion:reduce){.td-sidebar,.td-sidebar__flecha{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "component", type: TdIcon, selector: "td-icon", inputs: ["nombre", "titulo", "tamano"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7982
- }
7983
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TdSidebar, decorators: [{
7984
- type: Component,
7985
- args: [{ selector: 'td-sidebar', imports: [NgTemplateOutlet, RouterLink, TdIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<aside\n class=\"td-sidebar\"\n [class.td-sidebar--compacto]=\"!abiertoInterno() && modoCerrado === 'compacto'\"\n [class.td-sidebar--oculto]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n [class.td-sidebar--oscuro]=\"oscuro\"\n [attr.aria-hidden]=\"!abiertoInterno() && modoCerrado === 'oculto'\"\n>\n <header class=\"td-sidebar__marca\">\n <div class=\"td-sidebar__logo\">\n @if (logoUrl) {\n <img [src]=\"logoUrl\" [alt]=\"logoAlt\" />\n } @else {\n <span>{{ inicialMarca() }}</span>\n }\n </div>\n\n @if (abiertoInterno()) {\n <div class=\"td-sidebar__marca-texto\">\n <small>{{ subtitulo }}</small>\n <strong>{{ titulo }}</strong>\n </div>\n }\n\n </header>\n\n <div class=\"td-sidebar__encabezado\">\n @if (abiertoInterno()) {\n <span>{{ etiquetaNavegacion }}</span>\n } @else {\n <span aria-hidden=\"true\">\u2022\u2022\u2022</span>\n }\n </div>\n\n <nav class=\"td-sidebar__nav\" aria-label=\"Men\u00FA principal\">\n @if (cargando) {\n <p class=\"td-sidebar__estado\">{{ textoCarga }}</p>\n } @else if (error) {\n <p class=\"td-sidebar__estado td-sidebar__estado--error\">{{ error }}</p>\n } @else if (!menuNormalizado().length) {\n <p class=\"td-sidebar__estado\">{{ mensajeVacio }}</p>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{ items: menuNormalizado(), nivel: 0, padre: 'raiz' }\"\n />\n }\n </nav>\n</aside>\n\n<ng-template #listaMenu let-items=\"items\" let-nivel=\"nivel\" let-padre=\"padre\">\n <ul class=\"td-sidebar__lista\" [class.td-sidebar__lista--anidada]=\"nivel > 0\">\n @for (item of items; track item) {\n @let clave = claveItem(item, padre, $index);\n @let tieneHijos = item.subMenus.length > 0;\n\n <li class=\"td-sidebar__item\">\n @if (tieneHijos) {\n <button\n class=\"td-sidebar__enlace\"\n type=\"button\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.aria-expanded]=\"estaExpandido(clave)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"alternarGrupo(clave)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n <td-icon\n class=\"td-sidebar__flecha\"\n [class.td-sidebar__flecha--abierta]=\"estaExpandido(clave)\"\n nombre=\"chevron_right\"\n />\n }\n </button>\n } @else {\n <a\n class=\"td-sidebar__enlace\"\n [routerLink]=\"item.ruta || '/'\"\n [class.td-sidebar__enlace--ruta-activa]=\"esItemSeleccionado(item)\"\n [attr.title]=\"!abiertoInterno() ? item.nombre : null\"\n (click)=\"seleccionarItem(item, nivel)\"\n >\n <td-icon class=\"td-sidebar__icono\" [nombre]=\"item.icono || 'folder'\" />\n @if (abiertoInterno()) {\n <span>{{ item.nombre }}</span>\n }\n </a>\n }\n\n @if (abiertoInterno() && tieneHijos && estaExpandido(clave)) {\n <ng-container\n [ngTemplateOutlet]=\"listaMenu\"\n [ngTemplateOutletContext]=\"{\n items: item.subMenus,\n nivel: nivel + 1,\n padre: clave\n }\"\n />\n }\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:block;height:100%}*{box-sizing:border-box}.td-sidebar{--td-fondo: var(--td-color-surface, #fff);--td-borde: var(--td-color-border, #e2e8f0);--td-texto: var(--td-color-text, #334155);--td-texto-suave: var(--td-color-text-muted, #64748b);--td-hover: var(--td-color-surface-soft, #f1f5f9);--td-activo: var(--td-color-primary-soft, #e8efff);--td-acento: var(--td-color-primary, #2563eb);display:flex;width:18rem;height:100%;min-height:28rem;flex-direction:column;overflow:hidden;border-right:1px solid var(--td-borde);color:var(--td-texto);background:var(--td-fondo);font-family:var(--td-font-family, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif);transition:width .2s ease,background .2s ease,border-color .2s ease}.td-sidebar--oscuro{--td-fondo: var(--td-color-surface, #09090b);--td-borde: var(--td-color-border, #27272a);--td-texto: var(--td-color-text, #d4d4d8);--td-texto-suave: var(--td-color-text-muted, #71717a);--td-hover: var(--td-color-surface-soft, #18181b);--td-activo: var(--td-color-primary-soft, #172554);--td-acento: var(--td-color-primary, #60a5fa)}.td-sidebar--compacto{width:5rem}.td-sidebar--oculto{width:0;min-width:0;border-right-width:0;visibility:hidden}.td-sidebar__marca{display:flex;min-height:5rem;align-items:center;gap:.75rem;border-bottom:1px solid var(--td-borde);padding:1rem}.td-sidebar__logo{display:grid;width:2.75rem;height:2.75rem;flex:0 0 2.75rem;place-items:center;overflow:hidden;border:1px solid var(--td-borde);border-radius:var(--td-radius-md, .75rem);color:var(--td-color-on-primary, #fff);background:linear-gradient(135deg,var(--td-color-primary, #2563eb),var(--td-color-info, #3157a4));font-size:1.15rem;font-weight:800}.td-sidebar__logo img{width:75%;height:75%;object-fit:contain}.td-sidebar__marca-texto{min-width:0;flex:1}.td-sidebar__marca-texto small,.td-sidebar__marca-texto strong{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__marca-texto small{color:var(--td-acento);font-size:.64rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar__marca-texto strong{margin-top:.18rem;font-size:.84rem}.td-sidebar__encabezado{min-height:3.35rem;padding:1.25rem 1rem .65rem;color:var(--td-texto-suave);font-size:.66rem;font-weight:750;letter-spacing:.12em;text-transform:uppercase}.td-sidebar--compacto .td-sidebar__encabezado{text-align:center}.td-sidebar__nav{min-height:0;flex:1;overflow-x:hidden;overflow-y:auto;padding:0 .7rem 1rem;scrollbar-width:thin;scrollbar-color:var(--td-borde) transparent}.td-sidebar__lista{margin:0;padding:0;list-style:none}.td-sidebar__lista--anidada{position:relative;margin:.2rem 0 .35rem 1.25rem;padding-left:.7rem}.td-sidebar__lista--anidada:before{position:absolute;inset:0 auto 0 0;width:1px;background:var(--td-borde);content:\"\"}.td-sidebar__item+.td-sidebar__item{margin-top:.2rem}.td-sidebar__enlace{display:flex;width:100%;min-width:0;min-height:2.5rem;align-items:center;gap:.7rem;border:0;border-radius:var(--td-radius-sm, .55rem);padding:.55rem .7rem;color:var(--td-texto);background:transparent;font:inherit;font-size:.82rem;font-weight:600;text-align:left;text-decoration:none;cursor:pointer;transition:color .12s ease,background .12s ease}.td-sidebar__enlace:hover{background:var(--td-hover)}.td-sidebar__enlace--activo,.td-sidebar__enlace--ruta-activa{color:var(--td-acento);background:var(--td-activo)}.td-sidebar__enlace span{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.td-sidebar__icono{width:1.15rem;height:1.15rem;flex:0 0 1.15rem}.td-sidebar__flecha{width:.9rem;height:.9rem;flex:0 0 .9rem;transition:transform .16s ease}.td-sidebar__flecha--abierta{transform:rotate(90deg)}.td-sidebar--compacto .td-sidebar__marca{justify-content:center;padding-inline:.7rem}.td-sidebar--compacto .td-sidebar__nav{padding-inline:.65rem}.td-sidebar--compacto .td-sidebar__enlace{justify-content:center;padding-inline:0}.td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2.2rem;font-size:.75rem;font-weight:500}.td-sidebar__lista--anidada .td-sidebar__lista--anidada .td-sidebar__enlace{min-height:2rem;color:var(--td-texto-suave);font-size:.7rem}.td-sidebar__estado{margin:0;padding:.75rem;color:var(--td-texto-suave);font-size:.75rem;line-height:1.45}.td-sidebar__estado--error{color:var(--td-color-warning, #d97706)}@media(prefers-reduced-motion:reduce){.td-sidebar,.td-sidebar__flecha{transition:none}}\n"] }]
7986
- }], ctorParameters: () => [], propDecorators: { titulo: [{
7987
- type: Input
7988
- }], subtitulo: [{
7989
- type: Input
7990
- }], etiquetaNavegacion: [{
7991
- type: Input
7992
- }], logoUrl: [{
7993
- type: Input
7994
- }], logoAlt: [{
7995
- type: Input
7996
- }], textoCarga: [{
7997
- type: Input
7998
- }], mensajeVacio: [{
7999
- type: Input
8000
- }], error: [{
8001
- type: Input
8002
- }], cargando: [{
8003
- type: Input
8004
- }], oscuro: [{
8005
- type: Input
8006
- }], modoCerrado: [{
8007
- type: Input
8008
- }], abierto: [{
8009
- type: Input
8010
- }], menu: [{
8011
- type: Input
8012
- }], abiertoChange: [{
8013
- type: Output
8014
- }], seleccionar: [{
8015
- type: Output
8016
- }] } });
8017
-
8018
8033
  let tabSequence = 0;
8019
8034
  class TdTab {
8020
8035
  generatedId = `td-tab-${++tabSequence}`;