@neural-ui/core 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -88
- package/accordion/package.json +4 -0
- package/alert/package.json +4 -0
- package/autocomplete/package.json +4 -0
- package/avatar/package.json +4 -0
- package/badge/package.json +4 -0
- package/block-ui/package.json +4 -0
- package/breadcrumb/package.json +4 -0
- package/button/package.json +4 -0
- package/card/package.json +4 -0
- package/chart/package.json +4 -0
- package/checkbox/package.json +4 -0
- package/chip/package.json +4 -0
- package/code-block/package.json +4 -0
- package/color-picker/package.json +4 -0
- package/command-palette/package.json +4 -0
- package/confirm-dialog/package.json +4 -0
- package/context-menu/package.json +4 -0
- package/dashboard-grid/package.json +4 -0
- package/date-input/package.json +4 -0
- package/divider/package.json +4 -0
- package/empty-state/package.json +4 -0
- package/fesm2022/neural-ui-core-accordion.mjs +162 -0
- package/fesm2022/neural-ui-core-accordion.mjs.map +1 -0
- package/fesm2022/neural-ui-core-alert.mjs +116 -0
- package/fesm2022/neural-ui-core-alert.mjs.map +1 -0
- package/fesm2022/neural-ui-core-autocomplete.mjs +332 -0
- package/fesm2022/neural-ui-core-autocomplete.mjs.map +1 -0
- package/fesm2022/neural-ui-core-avatar.mjs +109 -0
- package/fesm2022/neural-ui-core-avatar.mjs.map +1 -0
- package/fesm2022/neural-ui-core-badge.mjs +54 -0
- package/fesm2022/neural-ui-core-badge.mjs.map +1 -0
- package/fesm2022/neural-ui-core-block-ui.mjs +95 -0
- package/fesm2022/neural-ui-core-block-ui.mjs.map +1 -0
- package/fesm2022/neural-ui-core-breadcrumb.mjs +84 -0
- package/fesm2022/neural-ui-core-breadcrumb.mjs.map +1 -0
- package/fesm2022/neural-ui-core-button.mjs +125 -0
- package/fesm2022/neural-ui-core-button.mjs.map +1 -0
- package/fesm2022/neural-ui-core-card.mjs +69 -0
- package/fesm2022/neural-ui-core-card.mjs.map +1 -0
- package/fesm2022/neural-ui-core-chart.mjs +287 -0
- package/fesm2022/neural-ui-core-chart.mjs.map +1 -0
- package/fesm2022/neural-ui-core-checkbox.mjs +138 -0
- package/fesm2022/neural-ui-core-checkbox.mjs.map +1 -0
- package/fesm2022/neural-ui-core-chip.mjs +130 -0
- package/fesm2022/neural-ui-core-chip.mjs.map +1 -0
- package/fesm2022/neural-ui-core-code-block.mjs +250 -0
- package/fesm2022/neural-ui-core-code-block.mjs.map +1 -0
- package/fesm2022/neural-ui-core-color-picker.mjs +435 -0
- package/fesm2022/neural-ui-core-color-picker.mjs.map +1 -0
- package/fesm2022/neural-ui-core-command-palette.mjs +235 -0
- package/fesm2022/neural-ui-core-command-palette.mjs.map +1 -0
- package/fesm2022/neural-ui-core-confirm-dialog.mjs +118 -0
- package/fesm2022/neural-ui-core-confirm-dialog.mjs.map +1 -0
- package/fesm2022/neural-ui-core-context-menu.mjs +158 -0
- package/fesm2022/neural-ui-core-context-menu.mjs.map +1 -0
- package/fesm2022/neural-ui-core-dashboard-grid.mjs +144 -0
- package/fesm2022/neural-ui-core-dashboard-grid.mjs.map +1 -0
- package/fesm2022/neural-ui-core-date-input.mjs +1332 -0
- package/fesm2022/neural-ui-core-date-input.mjs.map +1 -0
- package/fesm2022/neural-ui-core-divider.mjs +54 -0
- package/fesm2022/neural-ui-core-divider.mjs.map +1 -0
- package/fesm2022/neural-ui-core-empty-state.mjs +84 -0
- package/fesm2022/neural-ui-core-empty-state.mjs.map +1 -0
- package/fesm2022/neural-ui-core-filter-bar.mjs +118 -0
- package/fesm2022/neural-ui-core-filter-bar.mjs.map +1 -0
- package/fesm2022/neural-ui-core-icon.mjs +50 -0
- package/fesm2022/neural-ui-core-icon.mjs.map +1 -0
- package/fesm2022/neural-ui-core-image-viewer.mjs +309 -0
- package/fesm2022/neural-ui-core-image-viewer.mjs.map +1 -0
- package/fesm2022/neural-ui-core-input-otp.mjs +192 -0
- package/fesm2022/neural-ui-core-input-otp.mjs.map +1 -0
- package/fesm2022/neural-ui-core-input.mjs +320 -0
- package/fesm2022/neural-ui-core-input.mjs.map +1 -0
- package/fesm2022/neural-ui-core-knob.mjs +323 -0
- package/fesm2022/neural-ui-core-knob.mjs.map +1 -0
- package/fesm2022/neural-ui-core-meter-group.mjs +122 -0
- package/fesm2022/neural-ui-core-meter-group.mjs.map +1 -0
- package/fesm2022/neural-ui-core-modal.mjs +156 -0
- package/fesm2022/neural-ui-core-modal.mjs.map +1 -0
- package/fesm2022/neural-ui-core-multiselect.mjs +748 -0
- package/fesm2022/neural-ui-core-multiselect.mjs.map +1 -0
- package/fesm2022/neural-ui-core-nav.mjs +952 -0
- package/fesm2022/neural-ui-core-nav.mjs.map +1 -0
- package/fesm2022/neural-ui-core-notification-center.mjs +264 -0
- package/fesm2022/neural-ui-core-notification-center.mjs.map +1 -0
- package/fesm2022/neural-ui-core-number-input.mjs +331 -0
- package/fesm2022/neural-ui-core-number-input.mjs.map +1 -0
- package/fesm2022/neural-ui-core-pagination.mjs +198 -0
- package/fesm2022/neural-ui-core-pagination.mjs.map +1 -0
- package/fesm2022/neural-ui-core-popover.mjs +207 -0
- package/fesm2022/neural-ui-core-popover.mjs.map +1 -0
- package/fesm2022/neural-ui-core-progress-bar.mjs +105 -0
- package/fesm2022/neural-ui-core-progress-bar.mjs.map +1 -0
- package/fesm2022/neural-ui-core-radio.mjs +171 -0
- package/fesm2022/neural-ui-core-radio.mjs.map +1 -0
- package/fesm2022/neural-ui-core-rating.mjs +151 -0
- package/fesm2022/neural-ui-core-rating.mjs.map +1 -0
- package/fesm2022/neural-ui-core-select.mjs +638 -0
- package/fesm2022/neural-ui-core-select.mjs.map +1 -0
- package/fesm2022/neural-ui-core-sidebar.mjs +214 -0
- package/fesm2022/neural-ui-core-sidebar.mjs.map +1 -0
- package/fesm2022/neural-ui-core-skeleton.mjs +40 -0
- package/fesm2022/neural-ui-core-skeleton.mjs.map +1 -0
- package/fesm2022/neural-ui-core-slider.mjs +146 -0
- package/fesm2022/neural-ui-core-slider.mjs.map +1 -0
- package/fesm2022/neural-ui-core-spinner.mjs +113 -0
- package/fesm2022/neural-ui-core-spinner.mjs.map +1 -0
- package/fesm2022/neural-ui-core-split-button.mjs +252 -0
- package/fesm2022/neural-ui-core-split-button.mjs.map +1 -0
- package/fesm2022/neural-ui-core-splitter.mjs +174 -0
- package/fesm2022/neural-ui-core-splitter.mjs.map +1 -0
- package/fesm2022/neural-ui-core-stats-card.mjs +163 -0
- package/fesm2022/neural-ui-core-stats-card.mjs.map +1 -0
- package/fesm2022/neural-ui-core-stepper.mjs +204 -0
- package/fesm2022/neural-ui-core-stepper.mjs.map +1 -0
- package/fesm2022/neural-ui-core-switch.mjs +111 -0
- package/fesm2022/neural-ui-core-switch.mjs.map +1 -0
- package/fesm2022/neural-ui-core-table.mjs +1860 -0
- package/fesm2022/neural-ui-core-table.mjs.map +1 -0
- package/fesm2022/neural-ui-core-tabs.mjs +246 -0
- package/fesm2022/neural-ui-core-tabs.mjs.map +1 -0
- package/fesm2022/neural-ui-core-textarea.mjs +188 -0
- package/fesm2022/neural-ui-core-textarea.mjs.map +1 -0
- package/fesm2022/neural-ui-core-timeline.mjs +117 -0
- package/fesm2022/neural-ui-core-timeline.mjs.map +1 -0
- package/fesm2022/neural-ui-core-toast.mjs +171 -0
- package/fesm2022/neural-ui-core-toast.mjs.map +1 -0
- package/fesm2022/neural-ui-core-toggle-button-group.mjs +162 -0
- package/fesm2022/neural-ui-core-toggle-button-group.mjs.map +1 -0
- package/fesm2022/neural-ui-core-toolbar.mjs +67 -0
- package/fesm2022/neural-ui-core-toolbar.mjs.map +1 -0
- package/fesm2022/neural-ui-core-tooltip.mjs +151 -0
- package/fesm2022/neural-ui-core-tooltip.mjs.map +1 -0
- package/fesm2022/neural-ui-core-url-state.mjs +96 -0
- package/fesm2022/neural-ui-core-url-state.mjs.map +1 -0
- package/fesm2022/neural-ui-core-virtual-list.mjs +126 -0
- package/fesm2022/neural-ui-core-virtual-list.mjs.map +1 -0
- package/fesm2022/neural-ui-core.mjs +11 -8544
- package/fesm2022/neural-ui-core.mjs.map +1 -1
- package/filter-bar/package.json +4 -0
- package/icon/package.json +4 -0
- package/image-viewer/package.json +4 -0
- package/input/package.json +4 -0
- package/input-otp/package.json +4 -0
- package/knob/package.json +4 -0
- package/meter-group/package.json +4 -0
- package/modal/package.json +4 -0
- package/multiselect/package.json +4 -0
- package/nav/package.json +4 -0
- package/notification-center/package.json +4 -0
- package/number-input/package.json +4 -0
- package/package.json +252 -5
- package/pagination/package.json +4 -0
- package/popover/package.json +4 -0
- package/progress-bar/package.json +4 -0
- package/radio/package.json +4 -0
- package/rating/package.json +4 -0
- package/select/package.json +4 -0
- package/sidebar/package.json +4 -0
- package/skeleton/package.json +4 -0
- package/slider/package.json +4 -0
- package/spinner/package.json +4 -0
- package/split-button/package.json +4 -0
- package/splitter/package.json +4 -0
- package/stats-card/package.json +4 -0
- package/stepper/package.json +4 -0
- package/styles/_tokens.scss +202 -0
- package/styles.scss +1 -0
- package/switch/package.json +4 -0
- package/table/package.json +4 -0
- package/tabs/package.json +4 -0
- package/textarea/package.json +4 -0
- package/timeline/package.json +4 -0
- package/toast/package.json +4 -0
- package/toggle-button-group/package.json +4 -0
- package/toolbar/package.json +4 -0
- package/tooltip/package.json +4 -0
- package/types/neural-ui-core-accordion.d.ts +55 -0
- package/types/neural-ui-core-alert.d.ts +47 -0
- package/types/neural-ui-core-autocomplete.d.ts +69 -0
- package/types/neural-ui-core-avatar.d.ts +39 -0
- package/types/neural-ui-core-badge.d.ts +36 -0
- package/types/neural-ui-core-block-ui.d.ts +46 -0
- package/types/neural-ui-core-breadcrumb.d.ts +38 -0
- package/types/neural-ui-core-button.d.ts +55 -0
- package/types/neural-ui-core-card.d.ts +37 -0
- package/types/neural-ui-core-chart.d.ts +236 -0
- package/types/neural-ui-core-checkbox.d.ts +33 -0
- package/types/neural-ui-core-chip.d.ts +53 -0
- package/types/neural-ui-core-code-block.d.ts +55 -0
- package/types/neural-ui-core-color-picker.d.ts +55 -0
- package/types/neural-ui-core-command-palette.d.ts +56 -0
- package/types/neural-ui-core-confirm-dialog.d.ts +50 -0
- package/types/neural-ui-core-context-menu.d.ts +66 -0
- package/types/neural-ui-core-dashboard-grid.d.ts +41 -0
- package/types/neural-ui-core-date-input.d.ts +178 -0
- package/types/neural-ui-core-divider.d.ts +20 -0
- package/types/neural-ui-core-empty-state.d.ts +32 -0
- package/types/neural-ui-core-filter-bar.d.ts +49 -0
- package/types/neural-ui-core-icon.d.ts +33 -0
- package/types/neural-ui-core-image-viewer.d.ts +67 -0
- package/types/neural-ui-core-input-otp.d.ts +49 -0
- package/types/neural-ui-core-input.d.ts +86 -0
- package/types/neural-ui-core-knob.d.ts +68 -0
- package/types/neural-ui-core-meter-group.d.ts +52 -0
- package/types/neural-ui-core-modal.d.ts +54 -0
- package/types/neural-ui-core-multiselect.d.ts +129 -0
- package/types/neural-ui-core-nav.d.ts +69 -0
- package/types/neural-ui-core-notification-center.d.ts +60 -0
- package/types/neural-ui-core-number-input.d.ts +63 -0
- package/types/neural-ui-core-pagination.d.ts +30 -0
- package/types/neural-ui-core-popover.d.ts +73 -0
- package/types/neural-ui-core-progress-bar.d.ts +35 -0
- package/types/neural-ui-core-radio.d.ts +51 -0
- package/types/neural-ui-core-rating.d.ts +34 -0
- package/types/neural-ui-core-select.d.ts +161 -0
- package/types/neural-ui-core-sidebar.d.ts +57 -0
- package/types/neural-ui-core-skeleton.d.ts +22 -0
- package/types/neural-ui-core-slider.d.ts +42 -0
- package/types/neural-ui-core-spinner.d.ts +38 -0
- package/types/neural-ui-core-split-button.d.ts +65 -0
- package/types/neural-ui-core-splitter.d.ts +28 -0
- package/types/neural-ui-core-stats-card.d.ts +39 -0
- package/types/neural-ui-core-stepper.d.ts +51 -0
- package/types/neural-ui-core-switch.d.ts +34 -0
- package/types/neural-ui-core-table.d.ts +282 -0
- package/types/neural-ui-core-tabs.d.ts +76 -0
- package/types/neural-ui-core-textarea.d.ts +52 -0
- package/types/neural-ui-core-timeline.d.ts +33 -0
- package/types/neural-ui-core-toast.d.ts +70 -0
- package/types/neural-ui-core-toggle-button-group.d.ts +63 -0
- package/types/neural-ui-core-toolbar.d.ts +36 -0
- package/types/neural-ui-core-tooltip.d.ts +48 -0
- package/types/neural-ui-core-url-state.d.ts +58 -0
- package/types/neural-ui-core-virtual-list.d.ts +60 -0
- package/types/neural-ui-core.d.ts +3 -2105
- package/url-state/package.json +4 -0
- package/virtual-list/package.json +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neural-ui-core-nav.mjs","sources":["../../../../projects/ui-core/nav/neu-nav.component.ts","../../../../projects/ui-core/nav/neural-ui-core-nav.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ViewEncapsulation,\n afterNextRender,\n effect,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { RouterLink, RouterLinkActive, Router, NavigationEnd } from '@angular/router';\nimport { filter } from 'rxjs';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { NeuIconComponent } from '@neural-ui/core/icon';\nimport { NeuTooltipDirective } from '@neural-ui/core/tooltip';\n\n// ---- Tipos públicos / Public types ----\n\n/**\n * Ítem de navegación con soporte para 3 niveles de profundidad.\n *\n * Destino del enlace — usa UNO de los dos:\n * - `route` → navegación interna Angular (RouterLink)\n * - `href` → URL externa, se abre en nueva pestaña con rel=\"noopener noreferrer\"\n *\n * Los ítems con `children` actúan como grupo acordeón (sin destino propio).\n * Máximo 3 niveles: raíz → hijos → nietos.\n * Los nietos no pueden tener `children`.\n */\nexport interface NeuNavItem {\n id: string;\n label: string;\n icon: string;\n /** Ruta Angular interna (RouterLink). Excluye `href`. / Internal Angular route (RouterLink). Excludes `href`. */\n route?: string;\n /** URL externa. Se abre en nueva pestaña. Excluye `route`. / External URL. Opens in a new tab. Excludes `route`. */\n href?: string;\n /** Ítems hijo (nivel 2). Cada hijo puede tener sus propios `children` (nivel 3). / Child items (level 2). Each child can have its own `children` (level 3). */\n children?: NeuNavItem[];\n badge?: string;\n badgeVariant?: 'default' | 'success' | 'warning' | 'danger' | 'info';\n disabled?: boolean;\n}\n\n@Component({\n selector: 'neu-nav',\n imports: [RouterLink, RouterLinkActive, NeuIconComponent, NeuTooltipDirective],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"neu-nav-wrapper\" [class.neu-nav-wrapper--collapsed]=\"isCollapsed()\">\n <nav\n class=\"neu-nav\"\n [class.neu-nav--collapsed]=\"isCollapsed()\"\n [attr.aria-label]=\"ariaLabel()\"\n >\n <!-- Brand slot -->\n <div class=\"neu-nav__brand\">\n <!-- Icono visible solo en modo colapsado -->\n <div class=\"neu-nav__brand-icon\">\n <ng-content select=\"[neu-nav-brand-icon]\" />\n </div>\n <!-- Contenido completo visible en modo expandido -->\n <div class=\"neu-nav__brand-content\">\n <ng-content select=\"[neu-nav-brand]\" />\n </div>\n </div>\n\n <!-- Items (nivel 1) -->\n <div class=\"neu-nav__items\" role=\"list\">\n @for (item of items(); track item.id) {\n @if (item.children?.length) {\n <!-- NIVEL 1 — Grupo acordeón -->\n <div\n class=\"neu-nav__group\"\n [class.neu-nav__group--open]=\"isGroupOpen(item.id)\"\n role=\"listitem\"\n (mouseenter)=\"onGroupMouseEnter(item, $event)\"\n (mouseleave)=\"onGroupMouseLeave()\"\n >\n <button\n class=\"neu-nav__item neu-nav__item--parent\"\n type=\"button\"\n [class.neu-nav__item--active]=\"isGroupActive(item)\"\n [class.neu-nav__item--disabled]=\"item.disabled\"\n [attr.aria-expanded]=\"isGroupOpen(item.id)\"\n [attr.aria-haspopup]=\"true\"\n [attr.disabled]=\"item.disabled ? '' : null\"\n [neuTooltip]=\"isCollapsed() ? item.label : ''\"\n neuTooltipPosition=\"right\"\n (click)=\"!isCollapsed() && toggleGroup(item.id)\"\n >\n <neu-icon\n [name]=\"item.icon\"\n size=\"18px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ item.label }}</span>\n @if (item.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n item.badgeVariant ?? 'default'\n }}\"\n >{{ item.badge }}</span\n >\n }\n <neu-icon\n name=\"lucideChevronRight\"\n size=\"14px\"\n class=\"neu-nav__group-chevron\"\n aria-hidden=\"true\"\n />\n </button>\n\n <!-- Submenú nivel 2 -->\n @if (!isCollapsed() && isGroupOpen(item.id)) {\n <div class=\"neu-nav__submenu\" role=\"list\">\n @for (child of item.children; track child.id) {\n @if (child.children?.length) {\n <!-- NIVEL 2 — Subgrupo acordeón -->\n <div\n class=\"neu-nav__group neu-nav__group--nested\"\n [class.neu-nav__group--open]=\"isGroupOpen(child.id)\"\n role=\"listitem\"\n >\n <button\n class=\"neu-nav__item neu-nav__item--child neu-nav__item--parent\"\n type=\"button\"\n [class.neu-nav__item--active]=\"isGroupActive(child)\"\n [class.neu-nav__item--disabled]=\"child.disabled\"\n [attr.aria-expanded]=\"isGroupOpen(child.id)\"\n [attr.aria-haspopup]=\"true\"\n [attr.disabled]=\"child.disabled ? '' : null\"\n (click)=\"toggleGroup(child.id)\"\n >\n <neu-icon\n [name]=\"child.icon\"\n size=\"15px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ child.label }}</span>\n @if (child.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n child.badgeVariant ?? 'default'\n }}\"\n >{{ child.badge }}</span\n >\n }\n <neu-icon\n name=\"lucideChevronRight\"\n size=\"13px\"\n class=\"neu-nav__group-chevron\"\n aria-hidden=\"true\"\n />\n </button>\n\n <!-- Submenú nivel 3 -->\n @if (isGroupOpen(child.id)) {\n <div class=\"neu-nav__submenu neu-nav__submenu--nested\" role=\"list\">\n @for (grand of child.children; track grand.id) {\n @if (grand.href) {\n <!-- NIVEL 3 — Enlace externo -->\n <a\n class=\"neu-nav__item neu-nav__item--grandchild\"\n [class.neu-nav__item--disabled]=\"grand.disabled\"\n [href]=\"grand.disabled ? null : grand.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [attr.aria-disabled]=\"grand.disabled ? 'true' : null\"\n role=\"listitem\"\n >\n <neu-icon\n [name]=\"grand.icon\"\n size=\"14px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ grand.label }}</span>\n <neu-icon\n name=\"lucideExternalLink\"\n size=\"11px\"\n class=\"neu-nav__external-icon\"\n aria-hidden=\"true\"\n />\n </a>\n } @else {\n <!-- NIVEL 3 — Enlace interno -->\n <a\n class=\"neu-nav__item neu-nav__item--grandchild\"\n [class.neu-nav__item--disabled]=\"grand.disabled\"\n [routerLink]=\"grand.disabled ? null : (grand.route ?? null)\"\n routerLinkActive=\"neu-nav__item--active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n [attr.aria-current]=\"\n isCurrentRoute(grand.route ?? '') ? 'page' : null\n \"\n role=\"listitem\"\n >\n <neu-icon\n [name]=\"grand.icon\"\n size=\"14px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ grand.label }}</span>\n @if (grand.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n grand.badgeVariant ?? 'default'\n }}\"\n >{{ grand.badge }}</span\n >\n }\n </a>\n }\n }\n </div>\n }\n </div>\n } @else if (child.href) {\n <!-- NIVEL 2 — Enlace externo -->\n <a\n class=\"neu-nav__item neu-nav__item--child\"\n [class.neu-nav__item--disabled]=\"child.disabled\"\n [href]=\"child.disabled ? null : child.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [attr.aria-disabled]=\"child.disabled ? 'true' : null\"\n role=\"listitem\"\n >\n <neu-icon\n [name]=\"child.icon\"\n size=\"15px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ child.label }}</span>\n @if (child.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n child.badgeVariant ?? 'default'\n }}\"\n >{{ child.badge }}</span\n >\n }\n <neu-icon\n name=\"lucideExternalLink\"\n size=\"12px\"\n class=\"neu-nav__external-icon\"\n aria-hidden=\"true\"\n />\n </a>\n } @else {\n <!-- NIVEL 2 — Enlace interno -->\n <a\n class=\"neu-nav__item neu-nav__item--child\"\n [class.neu-nav__item--disabled]=\"child.disabled\"\n [routerLink]=\"child.disabled ? null : (child.route ?? null)\"\n routerLinkActive=\"neu-nav__item--active\"\n [routerLinkActiveOptions]=\"{ exact: true }\"\n [attr.aria-current]=\"isCurrentRoute(child.route ?? '') ? 'page' : null\"\n role=\"listitem\"\n >\n <neu-icon\n [name]=\"child.icon\"\n size=\"15px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ child.label }}</span>\n @if (child.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n child.badgeVariant ?? 'default'\n }}\"\n >{{ child.badge }}</span\n >\n }\n </a>\n }\n }\n </div>\n }\n </div>\n } @else if (item.href) {\n <!-- NIVEL 1 — Enlace externo -->\n <a\n class=\"neu-nav__item\"\n [class.neu-nav__item--disabled]=\"item.disabled\"\n [href]=\"item.disabled ? null : item.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [neuTooltip]=\"isCollapsed() ? item.label : ''\"\n neuTooltipPosition=\"right\"\n role=\"listitem\"\n >\n <neu-icon\n [name]=\"item.icon\"\n size=\"18px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ item.label }}</span>\n @if (item.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n item.badgeVariant ?? 'default'\n }}\"\n >{{ item.badge }}</span\n >\n }\n <neu-icon\n name=\"lucideExternalLink\"\n size=\"13px\"\n class=\"neu-nav__external-icon\"\n aria-hidden=\"true\"\n />\n </a>\n } @else {\n <!-- NIVEL 1 — Enlace interno -->\n <a\n class=\"neu-nav__item\"\n [class.neu-nav__item--disabled]=\"item.disabled\"\n [routerLink]=\"item.disabled ? null : (item.route ?? null)\"\n routerLinkActive=\"neu-nav__item--active\"\n [routerLinkActiveOptions]=\"{ exact: item.route === '/' }\"\n [attr.aria-current]=\"isCurrentRoute(item.route ?? '') ? 'page' : null\"\n [neuTooltip]=\"isCollapsed() ? item.label : ''\"\n neuTooltipPosition=\"right\"\n role=\"listitem\"\n >\n <neu-icon\n [name]=\"item.icon\"\n size=\"18px\"\n class=\"neu-nav__item-icon\"\n aria-hidden=\"true\"\n />\n <span class=\"neu-nav__item-label\">{{ item.label }}</span>\n @if (item.badge) {\n <span\n class=\"neu-nav__item-badge neu-nav__item-badge--{{\n item.badgeVariant ?? 'default'\n }}\"\n >{{ item.badge }}</span\n >\n }\n </a>\n }\n }\n </div>\n\n <!-- Footer slot -->\n <div class=\"neu-nav__footer\">\n <ng-content select=\"[neu-nav-footer]\" />\n </div>\n </nav>\n\n <!-- Flyout panel para grupos en modo colapsado -->\n @if (flyoutState(); as flyout) {\n <div\n class=\"neu-nav__flyout\"\n [style.top.px]=\"flyout.top\"\n [style.left.px]=\"flyout.left\"\n role=\"menu\"\n (mouseenter)=\"onFlyoutMouseEnter()\"\n (mouseleave)=\"onFlyoutMouseLeave()\"\n >\n <div class=\"neu-nav__flyout-title\">{{ flyout.item.label }}</div>\n @for (child of flyout.item.children ?? []; track child.id) {\n @if (child.children?.length) {\n <div class=\"neu-nav__flyout-group\">\n <span class=\"neu-nav__flyout-group-label\">{{ child.label }}</span>\n @for (grand of child.children; track grand.id) {\n @if (grand.href) {\n <a\n class=\"neu-nav__flyout-item\"\n role=\"menuitem\"\n [href]=\"grand.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"flyoutState.set(null)\"\n >\n <neu-icon [name]=\"grand.icon\" size=\"13px\" aria-hidden=\"true\" />\n <span>{{ grand.label }}</span>\n <neu-icon\n name=\"lucideExternalLink\"\n size=\"10px\"\n class=\"neu-nav__external-icon\"\n />\n </a>\n } @else {\n <a\n class=\"neu-nav__flyout-item\"\n role=\"menuitem\"\n [routerLink]=\"grand.route\"\n routerLinkActive=\"neu-nav__flyout-item--active\"\n (click)=\"flyoutState.set(null)\"\n >\n <neu-icon [name]=\"grand.icon\" size=\"13px\" aria-hidden=\"true\" />\n <span>{{ grand.label }}</span>\n </a>\n }\n }\n </div>\n } @else if (child.href) {\n <a\n class=\"neu-nav__flyout-item\"\n role=\"menuitem\"\n [href]=\"child.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"flyoutState.set(null)\"\n >\n <neu-icon [name]=\"child.icon\" size=\"13px\" aria-hidden=\"true\" />\n <span>{{ child.label }}</span>\n <neu-icon name=\"lucideExternalLink\" size=\"10px\" class=\"neu-nav__external-icon\" />\n </a>\n } @else {\n <a\n class=\"neu-nav__flyout-item\"\n role=\"menuitem\"\n [routerLink]=\"child.route\"\n routerLinkActive=\"neu-nav__flyout-item--active\"\n (click)=\"flyoutState.set(null)\"\n >\n <neu-icon [name]=\"child.icon\" size=\"13px\" aria-hidden=\"true\" />\n <span>{{ child.label }}</span>\n </a>\n }\n }\n </div>\n }\n\n <!-- Pestaña collapse/expand — fuera del nav para no ser recortada -->\n @if (collapsible()) {\n <button\n class=\"neu-nav__toggle-tab\"\n type=\"button\"\n [attr.aria-label]=\"isCollapsed() ? expandLabel() : collapseLabel()\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n (click)=\"toggleCollapse()\"\n >\n <neu-icon\n [name]=\"isCollapsed() ? 'lucideChevronRight' : 'lucideChevronLeft'\"\n size=\"12px\"\n aria-hidden=\"true\"\n />\n </button>\n }\n </div>\n `,\n styleUrl: './neu-nav.component.scss',\n})\nexport class NeuNavComponent {\n private readonly router = inject(Router);\n\n // ---- Señal reactiva de ruta activa / Reactive active route signal ----\n private readonly currentUrl = toSignal(\n this.router.events.pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd)),\n { initialValue: null },\n );\n\n // ---- Inputs ----\n\n /** Lista de ítems de navegación / Navigation item list */\n items = input<NeuNavItem[]>([]);\n\n /** Estado inicial colapsado / Initial collapsed state */\n collapsed = input<boolean>(false);\n\n /** Muestra el botón de colapsar/expandir / Shows the collapse/expand button */\n collapsible = input<boolean>(true);\n\n /** Etiqueta accesible del <nav> / Accessible label for the <nav> */\n ariaLabel = input<string>('Navegación principal');\n\n /** Aria-label del botón cuando el nav está colapsado / Aria-label for the button when the nav is collapsed */\n expandLabel = input<string>('Expandir menú');\n\n /** Aria-label del botón cuando el nav está expandido / Aria-label for the button when the nav is expanded */\n collapseLabel = input<string>('Colapsar menú');\n\n /** Emite cuando cambia el estado colapsado / Emits when the collapsed state changes */\n collapsedChange = output<boolean>();\n\n // ---- Estado interno / Internal state ----\n\n // Sigue el input `collapsed` del padre (permite el configurador)\n // pero puede ser sobreescrito localmente con toggleCollapse()\n readonly isCollapsed = signal(this.collapsed());\n private readonly openGroups = signal<Set<string>>(new Set());\n\n // ---- Flyout para modo colapsado / Flyout for collapsed mode ----\n readonly flyoutState = signal<{ item: NeuNavItem; top: number; left: number } | null>(null);\n private _flyoutTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor() {\n // Sincroniza isCollapsed cuando el input cambia desde el padre\n effect(() => this.isCollapsed.set(this.collapsed()), { allowSignalWrites: true });\n // Abrimos el grupo activo DESPUÉS de que el padre haya pasado los inputs\n afterNextRender(() => this._openActiveGroup());\n // Limpiamos el timer del flyout al destruir el componente\n inject(DestroyRef).onDestroy(() => {\n if (this._flyoutTimer) clearTimeout(this._flyoutTimer);\n });\n }\n\n // ---- Helpers de estado / State helpers ----\n\n toggleCollapse(): void {\n this.isCollapsed.update((v) => {\n const next = !v;\n this.collapsedChange.emit(next);\n return next;\n });\n }\n\n toggleGroup(id: string): void {\n this.openGroups.update((s) => {\n const next = new Set(s);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n }\n\n isGroupOpen(id: string): boolean {\n return this.openGroups().has(id);\n }\n\n isCurrentRoute(route: string): boolean {\n if (!route) return false;\n // Consume la señal para que OnPush re-evalúe tras navegación\n this.currentUrl();\n return this.router.url === route || this.router.url.startsWith(route + '?');\n }\n\n isGroupActive(item: NeuNavItem): boolean {\n if (!item.children?.length) return false;\n return item.children.some(\n (c) =>\n this.isCurrentRoute(c.route ?? '') ||\n (c.children?.some((g) => this.isCurrentRoute(g.route ?? '')) ?? false),\n );\n }\n\n // ---- Flyout handlers ----\n\n onGroupMouseEnter(item: NeuNavItem, event: MouseEvent): void {\n if (!this.isCollapsed() || !item.children?.length) return;\n if (this._flyoutTimer) {\n clearTimeout(this._flyoutTimer);\n this._flyoutTimer = null;\n }\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();\n this.flyoutState.set({ item, top: rect.top, left: rect.right });\n }\n\n onGroupMouseLeave(): void {\n if (!this.isCollapsed()) return;\n this._flyoutTimer = setTimeout(() => this.flyoutState.set(null), 150);\n }\n\n onFlyoutMouseEnter(): void {\n if (this._flyoutTimer) {\n clearTimeout(this._flyoutTimer);\n this._flyoutTimer = null;\n }\n }\n\n onFlyoutMouseLeave(): void {\n this._flyoutTimer = setTimeout(() => this.flyoutState.set(null), 150);\n }\n\n private _openActiveGroup(): void {\n const toOpen = new Set<string>();\n for (const item of this.items()) {\n if (!item.children) continue;\n for (const child of item.children) {\n if (child.route && this.router.url.startsWith(child.route)) {\n toOpen.add(item.id);\n }\n if (child.children) {\n for (const grand of child.children) {\n if (grand.route && this.router.url.startsWith(grand.route)) {\n toOpen.add(item.id);\n toOpen.add(child.id);\n }\n }\n }\n }\n }\n if (toOpen.size > 0) {\n this.openGroups.set(toOpen);\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":";;;;;;;;MA2ca,eAAe,CAAA;AACT,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;;AAGvB,IAAA,UAAU,GAAG,QAAQ,CACpC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAyB,CAAC,YAAY,aAAa,CAAC,CAAC,EACtF,EAAE,YAAY,EAAE,IAAI,EAAE,CACvB;;;AAKD,IAAA,KAAK,GAAG,KAAK,CAAe,EAAE,4EAAC;;AAG/B,IAAA,SAAS,GAAG,KAAK,CAAU,KAAK,gFAAC;;AAGjC,IAAA,WAAW,GAAG,KAAK,CAAU,IAAI,kFAAC;;AAGlC,IAAA,SAAS,GAAG,KAAK,CAAS,sBAAsB,gFAAC;;AAGjD,IAAA,WAAW,GAAG,KAAK,CAAS,eAAe,kFAAC;;AAG5C,IAAA,aAAa,GAAG,KAAK,CAAS,eAAe,oFAAC;;IAG9C,eAAe,GAAG,MAAM,EAAW;;;;IAM1B,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,kFAAC;AAC9B,IAAA,UAAU,GAAG,MAAM,CAAc,IAAI,GAAG,EAAE,iFAAC;;AAGnD,IAAA,WAAW,GAAG,MAAM,CAAyD,IAAI,kFAAC;IACnF,YAAY,GAAyC,IAAI;AAEjE,IAAA,WAAA,GAAA;;QAEE,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;;QAEjF,eAAe,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;;AAE9C,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAK;YAChC,IAAI,IAAI,CAAC,YAAY;AAAE,gBAAA,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC;AACxD,QAAA,CAAC,CAAC;IACJ;;IAIA,cAAc,GAAA;QACZ,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,KAAI;AAC5B,YAAA,MAAM,IAAI,GAAG,CAAC,CAAC;AACf,YAAA,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;AAC/B,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,WAAW,CAAC,EAAU,EAAA;QACpB,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,KAAI;AAC3B,YAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;AACvB,YAAA,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAAE,gBAAA,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;;AAC5B,gBAAA,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACjB,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,WAAW,CAAC,EAAU,EAAA;QACpB,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAClC;AAEA,IAAA,cAAc,CAAC,KAAa,EAAA;AAC1B,QAAA,IAAI,CAAC,KAAK;AAAE,YAAA,OAAO,KAAK;;QAExB,IAAI,CAAC,UAAU,EAAE;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,GAAG,CAAC;IAC7E;AAEA,IAAA,aAAa,CAAC,IAAgB,EAAA;AAC5B,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM;AAAE,YAAA,OAAO,KAAK;QACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CACvB,CAAC,CAAC,KACA,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;aACjC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,CACzE;IACH;;IAIA,iBAAiB,CAAC,IAAgB,EAAE,KAAiB,EAAA;QACnD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM;YAAE;AACnD,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACrB,YAAA,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC;AAC/B,YAAA,IAAI,CAAC,YAAY,GAAG,IAAI;QAC1B;QACA,MAAM,IAAI,GAAI,KAAK,CAAC,aAA6B,CAAC,qBAAqB,EAAE;QACzE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACjE;IAEA,iBAAiB,GAAA;AACf,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE;AACzB,QAAA,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IACvE;IAEA,kBAAkB,GAAA;AAChB,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE;AACrB,YAAA,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC;AAC/B,YAAA,IAAI,CAAC,YAAY,GAAG,IAAI;QAC1B;IACF;IAEA,kBAAkB,GAAA;AAChB,QAAA,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IACvE;IAEQ,gBAAgB,GAAA;AACtB,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU;QAChC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE;YAC/B,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE;AACpB,YAAA,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE;AACjC,gBAAA,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;AAC1D,oBAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrB;AACA,gBAAA,IAAI,KAAK,CAAC,QAAQ,EAAE;AAClB,oBAAA,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,QAAQ,EAAE;AAClC,wBAAA,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;AAC1D,4BAAA,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AACnB,4BAAA,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;wBACtB;oBACF;gBACF;YACF;QACF;AACA,QAAA,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE;AACnB,YAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;QAC7B;IACF;uGA9IW,eAAe,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAf,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,eAAe,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,eAAA,EAAA,iBAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAxZhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqZT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,0gPAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAxZS,UAAU,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,aAAA,EAAA,UAAA,EAAA,qBAAA,EAAA,OAAA,EAAA,MAAA,EAAA,YAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,YAAA,EAAA,YAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,uBAAA,EAAA,kBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,gBAAA,CAAA,EAAA,QAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,gBAAgB,8FAAE,mBAAmB,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,YAAA,EAAA,oBAAA,EAAA,oBAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FA2ZlE,eAAe,EAAA,UAAA,EAAA,CAAA;kBA7Z3B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,SAAS,WACV,CAAC,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,mBAAmB,CAAC,EAAA,aAAA,EAC/D,iBAAiB,CAAC,IAAI,mBACpB,uBAAuB,CAAC,MAAM,EAAA,QAAA,EACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqZT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,0gPAAA,CAAA,EAAA;;;ACxcH;;AAEG;;;;"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, computed, Injectable, inject, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ICONS = {
|
|
5
|
+
info: 'ℹ️',
|
|
6
|
+
success: '✅',
|
|
7
|
+
warning: '⚠️',
|
|
8
|
+
error: '❌',
|
|
9
|
+
};
|
|
10
|
+
let _idSeq = 0;
|
|
11
|
+
/**
|
|
12
|
+
* NeuralUI NotificationService
|
|
13
|
+
*
|
|
14
|
+
* Servicio inyectable que gestiona la cola de notificaciones.
|
|
15
|
+
*
|
|
16
|
+
* Uso:
|
|
17
|
+
* inject(NeuNotificationService).push({ type: 'success', message: '¡Guardado!' });
|
|
18
|
+
*/
|
|
19
|
+
class NeuNotificationService {
|
|
20
|
+
notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : /* istanbul ignore next */ []));
|
|
21
|
+
unreadCount = computed(() => this.notifications().filter((n) => !n.read).length, ...(ngDevMode ? [{ debugName: "unreadCount" }] : /* istanbul ignore next */ []));
|
|
22
|
+
/** Agrega una notificación / Adds a notification */
|
|
23
|
+
push(opts) {
|
|
24
|
+
const id = `neu-notif-${++_idSeq}`;
|
|
25
|
+
const n = {
|
|
26
|
+
id,
|
|
27
|
+
type: opts.type ?? 'info',
|
|
28
|
+
title: opts.title,
|
|
29
|
+
message: opts.message,
|
|
30
|
+
icon: opts.icon ?? DEFAULT_ICONS[opts.type ?? 'info'],
|
|
31
|
+
duration: opts.duration ?? 5000,
|
|
32
|
+
timestamp: new Date(),
|
|
33
|
+
read: false,
|
|
34
|
+
};
|
|
35
|
+
this.notifications.update((list) => [n, ...list]);
|
|
36
|
+
if (n.duration && n.duration > 0) {
|
|
37
|
+
setTimeout(() => this.remove(id), n.duration);
|
|
38
|
+
}
|
|
39
|
+
return id;
|
|
40
|
+
}
|
|
41
|
+
/** Elimina una notificación / Removes a notification */
|
|
42
|
+
remove(id) {
|
|
43
|
+
this.notifications.update((list) => list.filter((n) => n.id !== id));
|
|
44
|
+
}
|
|
45
|
+
/** Marca todas como leídas / Marks all as read */
|
|
46
|
+
markAllRead() {
|
|
47
|
+
this.notifications.update((list) => list.map((n) => ({ ...n, read: true })));
|
|
48
|
+
}
|
|
49
|
+
/** Elimina todas / Clears all */
|
|
50
|
+
clearAll() {
|
|
51
|
+
this.notifications.set([]);
|
|
52
|
+
}
|
|
53
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuNotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
54
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuNotificationService, providedIn: 'root' });
|
|
55
|
+
}
|
|
56
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuNotificationService, decorators: [{
|
|
57
|
+
type: Injectable,
|
|
58
|
+
args: [{ providedIn: 'root' }]
|
|
59
|
+
}] });
|
|
60
|
+
let _panelSeq = 0;
|
|
61
|
+
/**
|
|
62
|
+
* NeuralUI NotificationCenter Component
|
|
63
|
+
*
|
|
64
|
+
* Icono de campana con badge de no leídos y panel de notificaciones
|
|
65
|
+
* deslizante. Consume NeuNotificationService.
|
|
66
|
+
*
|
|
67
|
+
* Uso:
|
|
68
|
+
* <neu-notification-center />
|
|
69
|
+
*/
|
|
70
|
+
class NeuNotificationCenterComponent {
|
|
71
|
+
_svc = inject(NeuNotificationService);
|
|
72
|
+
_isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []));
|
|
73
|
+
_panelId = `neu-nc-panel-${++_panelSeq}`;
|
|
74
|
+
_toggle() {
|
|
75
|
+
const opening = !this._isOpen();
|
|
76
|
+
this._isOpen.set(opening);
|
|
77
|
+
if (opening) {
|
|
78
|
+
// Mark all as read when panel opens
|
|
79
|
+
setTimeout(() => this._svc.markAllRead(), 500);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_relativeTime(date) {
|
|
83
|
+
const diff = Date.now() - date.getTime();
|
|
84
|
+
const mins = Math.floor(diff / 60000);
|
|
85
|
+
if (mins < 1)
|
|
86
|
+
return 'Ahora';
|
|
87
|
+
if (mins < 60)
|
|
88
|
+
return `Hace ${mins} min`;
|
|
89
|
+
const hours = Math.floor(mins / 60);
|
|
90
|
+
if (hours < 24)
|
|
91
|
+
return `Hace ${hours}h`;
|
|
92
|
+
return `Hace ${Math.floor(hours / 24)}d`;
|
|
93
|
+
}
|
|
94
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuNotificationCenterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
95
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: NeuNotificationCenterComponent, isStandalone: true, selector: "neu-notification-center", host: { properties: { "attr.aria-label": "\"Centro de notificaciones\"" }, classAttribute: "neu-nc" }, ngImport: i0, template: `
|
|
96
|
+
<!-- Bell button -->
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
class="neu-nc__bell"
|
|
100
|
+
[attr.aria-expanded]="_isOpen()"
|
|
101
|
+
[attr.aria-controls]="_panelId"
|
|
102
|
+
[attr.aria-label]="'Notificaciones. ' + _svc.unreadCount() + ' sin leer'"
|
|
103
|
+
(click)="_toggle()"
|
|
104
|
+
>
|
|
105
|
+
<span class="neu-nc__bell-icon" aria-hidden="true">🔔</span>
|
|
106
|
+
@if (_svc.unreadCount() > 0) {
|
|
107
|
+
<span class="neu-nc__badge" aria-hidden="true">{{
|
|
108
|
+
_svc.unreadCount() > 99 ? '99+' : _svc.unreadCount()
|
|
109
|
+
}}</span>
|
|
110
|
+
}
|
|
111
|
+
</button>
|
|
112
|
+
|
|
113
|
+
<!-- Panel -->
|
|
114
|
+
@if (_isOpen()) {
|
|
115
|
+
<div
|
|
116
|
+
class="neu-nc__panel"
|
|
117
|
+
[id]="_panelId"
|
|
118
|
+
role="dialog"
|
|
119
|
+
aria-modal="false"
|
|
120
|
+
aria-label="Notificaciones"
|
|
121
|
+
>
|
|
122
|
+
<div class="neu-nc__panel-header">
|
|
123
|
+
<span class="neu-nc__panel-title">Notificaciones</span>
|
|
124
|
+
<div class="neu-nc__panel-actions">
|
|
125
|
+
@if (_svc.unreadCount() > 0) {
|
|
126
|
+
<button type="button" class="neu-nc__action-btn" (click)="_svc.markAllRead()">
|
|
127
|
+
Leer todo
|
|
128
|
+
</button>
|
|
129
|
+
}
|
|
130
|
+
@if (_svc.notifications().length > 0) {
|
|
131
|
+
<button type="button" class="neu-nc__action-btn" (click)="_svc.clearAll()">
|
|
132
|
+
Limpiar
|
|
133
|
+
</button>
|
|
134
|
+
}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="neu-nc__list" role="list">
|
|
139
|
+
@if (!_svc.notifications().length) {
|
|
140
|
+
<div class="neu-nc__empty" role="status">No hay notificaciones</div>
|
|
141
|
+
}
|
|
142
|
+
@for (n of _svc.notifications(); track n.id) {
|
|
143
|
+
<div
|
|
144
|
+
class="neu-nc__item"
|
|
145
|
+
role="listitem"
|
|
146
|
+
[class.neu-nc__item--unread]="!n.read"
|
|
147
|
+
[class]="'neu-nc__item--' + n.type"
|
|
148
|
+
>
|
|
149
|
+
<span class="neu-nc__item-icon" aria-hidden="true">{{ n.icon }}</span>
|
|
150
|
+
<div class="neu-nc__item-body">
|
|
151
|
+
@if (n.title) {
|
|
152
|
+
<p class="neu-nc__item-title">{{ n.title }}</p>
|
|
153
|
+
}
|
|
154
|
+
<p class="neu-nc__item-msg">{{ n.message }}</p>
|
|
155
|
+
<time class="neu-nc__item-time" [dateTime]="n.timestamp.toISOString()">
|
|
156
|
+
{{ _relativeTime(n.timestamp) }}
|
|
157
|
+
</time>
|
|
158
|
+
</div>
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
class="neu-nc__item-close"
|
|
162
|
+
[attr.aria-label]="'Cerrar notificación'"
|
|
163
|
+
(click)="_svc.remove(n.id)"
|
|
164
|
+
>
|
|
165
|
+
×
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
}
|
|
172
|
+
`, isInline: true, styles: ["@charset \"UTF-8\";.neu-nc{position:relative;display:inline-block}.neu-nc__bell{all:unset;position:relative;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:var(--neu-radius-lg, 12px);cursor:pointer;transition:background .12s}.neu-nc__bell:hover{background:var(--neu-surface-2, #f3f4f6)}.neu-nc__bell:focus-visible{outline:2px solid var(--neu-focus-ring, #0ea5e9);outline-offset:2px}.neu-nc__bell-icon{font-size:1.2rem}.neu-nc__badge{position:absolute;top:4px;right:4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--neu-error);color:var(--neu-text-inverse);font-size:.625rem;font-weight:700;display:flex;align-items:center;justify-content:center;line-height:1}.neu-nc__panel{position:absolute;top:calc(100% + 8px);right:0;width:320px;max-height:420px;display:flex;flex-direction:column;background:var(--neu-surface-1, #ffffff);border:1px solid var(--neu-border-color, #e5e7eb);border-radius:var(--neu-radius-xl, 16px);box-shadow:0 12px 28px -6px #00000024;z-index:1001;overflow:hidden;animation:neu-nc-in .1s ease}@keyframes neu-nc-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.neu-nc__panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--neu-border-color, #e5e7eb);flex-shrink:0}.neu-nc__panel-title{font-size:.9375rem;font-weight:600;color:var(--neu-text-primary, #111)}.neu-nc__panel-actions{display:flex;gap:8px}.neu-nc__action-btn{all:unset;font-size:.75rem;color:var(--neu-color-primary, #0ea5e9);cursor:pointer;padding:2px 6px;border-radius:var(--neu-radius-sm, 4px)}.neu-nc__action-btn:hover{background:var(--neu-color-primary-alpha, rgba(14, 165, 233, .1))}.neu-nc__list{overflow-y:auto;flex:1;padding:6px}.neu-nc__empty{padding:24px;text-align:center;font-size:.875rem;color:var(--neu-text-secondary, #6b7280)}.neu-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 10px 10px 12px;border-radius:var(--neu-radius-lg, 12px);position:relative;transition:background .1s}.neu-nc__item:hover{background:var(--neu-surface-2, #f3f4f6)}.neu-nc__item--unread:before{content:\"\";position:absolute;left:4px;top:50%;transform:translateY(-50%);width:6px;height:6px;border-radius:50%;background:var(--neu-color-primary, #0ea5e9)}.neu-nc__item-icon{font-size:1.1rem;flex-shrink:0;padding-top:2px}.neu-nc__item-body{flex:1;min-width:0}.neu-nc__item-title{margin:0 0 2px;font-size:.8125rem;font-weight:600;color:var(--neu-text-primary, #111)}.neu-nc__item-msg{margin:0 0 4px;font-size:.8125rem;color:var(--neu-text-secondary, #4b5563);line-height:1.4}.neu-nc__item-time{font-size:.6875rem;color:var(--neu-text-secondary, #9ca3af)}.neu-nc__item-close{all:unset;opacity:0;cursor:pointer;padding:2px 6px;border-radius:4px;font-size:1rem;color:var(--neu-text-secondary, #9ca3af);flex-shrink:0;transition:opacity .1s}.neu-nc__item:hover .neu-nc__item-close{opacity:1}.neu-nc__item-close:hover{color:var(--neu-text-primary, #111)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
173
|
+
}
|
|
174
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: NeuNotificationCenterComponent, decorators: [{
|
|
175
|
+
type: Component,
|
|
176
|
+
args: [{ selector: 'neu-notification-center', imports: [], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
177
|
+
class: 'neu-nc',
|
|
178
|
+
'[attr.aria-label]': '"Centro de notificaciones"',
|
|
179
|
+
}, template: `
|
|
180
|
+
<!-- Bell button -->
|
|
181
|
+
<button
|
|
182
|
+
type="button"
|
|
183
|
+
class="neu-nc__bell"
|
|
184
|
+
[attr.aria-expanded]="_isOpen()"
|
|
185
|
+
[attr.aria-controls]="_panelId"
|
|
186
|
+
[attr.aria-label]="'Notificaciones. ' + _svc.unreadCount() + ' sin leer'"
|
|
187
|
+
(click)="_toggle()"
|
|
188
|
+
>
|
|
189
|
+
<span class="neu-nc__bell-icon" aria-hidden="true">🔔</span>
|
|
190
|
+
@if (_svc.unreadCount() > 0) {
|
|
191
|
+
<span class="neu-nc__badge" aria-hidden="true">{{
|
|
192
|
+
_svc.unreadCount() > 99 ? '99+' : _svc.unreadCount()
|
|
193
|
+
}}</span>
|
|
194
|
+
}
|
|
195
|
+
</button>
|
|
196
|
+
|
|
197
|
+
<!-- Panel -->
|
|
198
|
+
@if (_isOpen()) {
|
|
199
|
+
<div
|
|
200
|
+
class="neu-nc__panel"
|
|
201
|
+
[id]="_panelId"
|
|
202
|
+
role="dialog"
|
|
203
|
+
aria-modal="false"
|
|
204
|
+
aria-label="Notificaciones"
|
|
205
|
+
>
|
|
206
|
+
<div class="neu-nc__panel-header">
|
|
207
|
+
<span class="neu-nc__panel-title">Notificaciones</span>
|
|
208
|
+
<div class="neu-nc__panel-actions">
|
|
209
|
+
@if (_svc.unreadCount() > 0) {
|
|
210
|
+
<button type="button" class="neu-nc__action-btn" (click)="_svc.markAllRead()">
|
|
211
|
+
Leer todo
|
|
212
|
+
</button>
|
|
213
|
+
}
|
|
214
|
+
@if (_svc.notifications().length > 0) {
|
|
215
|
+
<button type="button" class="neu-nc__action-btn" (click)="_svc.clearAll()">
|
|
216
|
+
Limpiar
|
|
217
|
+
</button>
|
|
218
|
+
}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div class="neu-nc__list" role="list">
|
|
223
|
+
@if (!_svc.notifications().length) {
|
|
224
|
+
<div class="neu-nc__empty" role="status">No hay notificaciones</div>
|
|
225
|
+
}
|
|
226
|
+
@for (n of _svc.notifications(); track n.id) {
|
|
227
|
+
<div
|
|
228
|
+
class="neu-nc__item"
|
|
229
|
+
role="listitem"
|
|
230
|
+
[class.neu-nc__item--unread]="!n.read"
|
|
231
|
+
[class]="'neu-nc__item--' + n.type"
|
|
232
|
+
>
|
|
233
|
+
<span class="neu-nc__item-icon" aria-hidden="true">{{ n.icon }}</span>
|
|
234
|
+
<div class="neu-nc__item-body">
|
|
235
|
+
@if (n.title) {
|
|
236
|
+
<p class="neu-nc__item-title">{{ n.title }}</p>
|
|
237
|
+
}
|
|
238
|
+
<p class="neu-nc__item-msg">{{ n.message }}</p>
|
|
239
|
+
<time class="neu-nc__item-time" [dateTime]="n.timestamp.toISOString()">
|
|
240
|
+
{{ _relativeTime(n.timestamp) }}
|
|
241
|
+
</time>
|
|
242
|
+
</div>
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
class="neu-nc__item-close"
|
|
246
|
+
[attr.aria-label]="'Cerrar notificación'"
|
|
247
|
+
(click)="_svc.remove(n.id)"
|
|
248
|
+
>
|
|
249
|
+
×
|
|
250
|
+
</button>
|
|
251
|
+
</div>
|
|
252
|
+
}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
}
|
|
256
|
+
`, styles: ["@charset \"UTF-8\";.neu-nc{position:relative;display:inline-block}.neu-nc__bell{all:unset;position:relative;display:flex;align-items:center;justify-content:center;width:40px;height:40px;border-radius:var(--neu-radius-lg, 12px);cursor:pointer;transition:background .12s}.neu-nc__bell:hover{background:var(--neu-surface-2, #f3f4f6)}.neu-nc__bell:focus-visible{outline:2px solid var(--neu-focus-ring, #0ea5e9);outline-offset:2px}.neu-nc__bell-icon{font-size:1.2rem}.neu-nc__badge{position:absolute;top:4px;right:4px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--neu-error);color:var(--neu-text-inverse);font-size:.625rem;font-weight:700;display:flex;align-items:center;justify-content:center;line-height:1}.neu-nc__panel{position:absolute;top:calc(100% + 8px);right:0;width:320px;max-height:420px;display:flex;flex-direction:column;background:var(--neu-surface-1, #ffffff);border:1px solid var(--neu-border-color, #e5e7eb);border-radius:var(--neu-radius-xl, 16px);box-shadow:0 12px 28px -6px #00000024;z-index:1001;overflow:hidden;animation:neu-nc-in .1s ease}@keyframes neu-nc-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}.neu-nc__panel-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--neu-border-color, #e5e7eb);flex-shrink:0}.neu-nc__panel-title{font-size:.9375rem;font-weight:600;color:var(--neu-text-primary, #111)}.neu-nc__panel-actions{display:flex;gap:8px}.neu-nc__action-btn{all:unset;font-size:.75rem;color:var(--neu-color-primary, #0ea5e9);cursor:pointer;padding:2px 6px;border-radius:var(--neu-radius-sm, 4px)}.neu-nc__action-btn:hover{background:var(--neu-color-primary-alpha, rgba(14, 165, 233, .1))}.neu-nc__list{overflow-y:auto;flex:1;padding:6px}.neu-nc__empty{padding:24px;text-align:center;font-size:.875rem;color:var(--neu-text-secondary, #6b7280)}.neu-nc__item{display:flex;align-items:flex-start;gap:10px;padding:10px 10px 10px 12px;border-radius:var(--neu-radius-lg, 12px);position:relative;transition:background .1s}.neu-nc__item:hover{background:var(--neu-surface-2, #f3f4f6)}.neu-nc__item--unread:before{content:\"\";position:absolute;left:4px;top:50%;transform:translateY(-50%);width:6px;height:6px;border-radius:50%;background:var(--neu-color-primary, #0ea5e9)}.neu-nc__item-icon{font-size:1.1rem;flex-shrink:0;padding-top:2px}.neu-nc__item-body{flex:1;min-width:0}.neu-nc__item-title{margin:0 0 2px;font-size:.8125rem;font-weight:600;color:var(--neu-text-primary, #111)}.neu-nc__item-msg{margin:0 0 4px;font-size:.8125rem;color:var(--neu-text-secondary, #4b5563);line-height:1.4}.neu-nc__item-time{font-size:.6875rem;color:var(--neu-text-secondary, #9ca3af)}.neu-nc__item-close{all:unset;opacity:0;cursor:pointer;padding:2px 6px;border-radius:4px;font-size:1rem;color:var(--neu-text-secondary, #9ca3af);flex-shrink:0;transition:opacity .1s}.neu-nc__item:hover .neu-nc__item-close{opacity:1}.neu-nc__item-close:hover{color:var(--neu-text-primary, #111)}\n"] }]
|
|
257
|
+
}] });
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generated bundle index. Do not edit.
|
|
261
|
+
*/
|
|
262
|
+
|
|
263
|
+
export { NeuNotificationCenterComponent, NeuNotificationService };
|
|
264
|
+
//# sourceMappingURL=neural-ui-core-notification-center.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neural-ui-core-notification-center.mjs","sources":["../../../../projects/ui-core/notification-center/neu-notification-center.component.ts","../../../../projects/ui-core/notification-center/neural-ui-core-notification-center.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n Injectable,\n OnDestroy,\n ViewEncapsulation,\n computed,\n inject,\n signal,\n} from '@angular/core';\n\nexport type NeuNotificationType = 'info' | 'success' | 'warning' | 'error';\n\nexport interface NeuNotification {\n id: string;\n type: NeuNotificationType;\n title?: string;\n message: string;\n /** Auto-dismiss duration in ms (0 = persistent) */\n duration?: number;\n /** Icon text / emoji */\n icon?: string;\n timestamp: Date;\n read: boolean;\n}\n\nexport interface NeuNotificationOptions extends Omit<\n NeuNotification,\n 'id' | 'timestamp' | 'read'\n> {}\n\nconst DEFAULT_ICONS: Record<NeuNotificationType, string> = {\n info: 'ℹ️',\n success: '✅',\n warning: '⚠️',\n error: '❌',\n};\n\nlet _idSeq = 0;\n\n/**\n * NeuralUI NotificationService\n *\n * Servicio inyectable que gestiona la cola de notificaciones.\n *\n * Uso:\n * inject(NeuNotificationService).push({ type: 'success', message: '¡Guardado!' });\n */\n@Injectable({ providedIn: 'root' })\nexport class NeuNotificationService {\n readonly notifications = signal<NeuNotification[]>([]);\n\n readonly unreadCount = computed(() => this.notifications().filter((n) => !n.read).length);\n\n /** Agrega una notificación / Adds a notification */\n push(opts: Partial<NeuNotificationOptions> & Pick<NeuNotificationOptions, 'message'>): string {\n const id = `neu-notif-${++_idSeq}`;\n const n: NeuNotification = {\n id,\n type: opts.type ?? 'info',\n title: opts.title,\n message: opts.message,\n icon: opts.icon ?? DEFAULT_ICONS[opts.type ?? 'info'],\n duration: opts.duration ?? 5000,\n timestamp: new Date(),\n read: false,\n };\n this.notifications.update((list) => [n, ...list]);\n\n if (n.duration && n.duration > 0) {\n setTimeout(() => this.remove(id), n.duration);\n }\n return id;\n }\n\n /** Elimina una notificación / Removes a notification */\n remove(id: string): void {\n this.notifications.update((list) => list.filter((n) => n.id !== id));\n }\n\n /** Marca todas como leídas / Marks all as read */\n markAllRead(): void {\n this.notifications.update((list) => list.map((n) => ({ ...n, read: true })));\n }\n\n /** Elimina todas / Clears all */\n clearAll(): void {\n this.notifications.set([]);\n }\n}\n\nlet _panelSeq = 0;\n\n/**\n * NeuralUI NotificationCenter Component\n *\n * Icono de campana con badge de no leídos y panel de notificaciones\n * deslizante. Consume NeuNotificationService.\n *\n * Uso:\n * <neu-notification-center />\n */\n@Component({\n selector: 'neu-notification-center',\n imports: [],\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'neu-nc',\n '[attr.aria-label]': '\"Centro de notificaciones\"',\n },\n template: `\n <!-- Bell button -->\n <button\n type=\"button\"\n class=\"neu-nc__bell\"\n [attr.aria-expanded]=\"_isOpen()\"\n [attr.aria-controls]=\"_panelId\"\n [attr.aria-label]=\"'Notificaciones. ' + _svc.unreadCount() + ' sin leer'\"\n (click)=\"_toggle()\"\n >\n <span class=\"neu-nc__bell-icon\" aria-hidden=\"true\">🔔</span>\n @if (_svc.unreadCount() > 0) {\n <span class=\"neu-nc__badge\" aria-hidden=\"true\">{{\n _svc.unreadCount() > 99 ? '99+' : _svc.unreadCount()\n }}</span>\n }\n </button>\n\n <!-- Panel -->\n @if (_isOpen()) {\n <div\n class=\"neu-nc__panel\"\n [id]=\"_panelId\"\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label=\"Notificaciones\"\n >\n <div class=\"neu-nc__panel-header\">\n <span class=\"neu-nc__panel-title\">Notificaciones</span>\n <div class=\"neu-nc__panel-actions\">\n @if (_svc.unreadCount() > 0) {\n <button type=\"button\" class=\"neu-nc__action-btn\" (click)=\"_svc.markAllRead()\">\n Leer todo\n </button>\n }\n @if (_svc.notifications().length > 0) {\n <button type=\"button\" class=\"neu-nc__action-btn\" (click)=\"_svc.clearAll()\">\n Limpiar\n </button>\n }\n </div>\n </div>\n\n <div class=\"neu-nc__list\" role=\"list\">\n @if (!_svc.notifications().length) {\n <div class=\"neu-nc__empty\" role=\"status\">No hay notificaciones</div>\n }\n @for (n of _svc.notifications(); track n.id) {\n <div\n class=\"neu-nc__item\"\n role=\"listitem\"\n [class.neu-nc__item--unread]=\"!n.read\"\n [class]=\"'neu-nc__item--' + n.type\"\n >\n <span class=\"neu-nc__item-icon\" aria-hidden=\"true\">{{ n.icon }}</span>\n <div class=\"neu-nc__item-body\">\n @if (n.title) {\n <p class=\"neu-nc__item-title\">{{ n.title }}</p>\n }\n <p class=\"neu-nc__item-msg\">{{ n.message }}</p>\n <time class=\"neu-nc__item-time\" [dateTime]=\"n.timestamp.toISOString()\">\n {{ _relativeTime(n.timestamp) }}\n </time>\n </div>\n <button\n type=\"button\"\n class=\"neu-nc__item-close\"\n [attr.aria-label]=\"'Cerrar notificación'\"\n (click)=\"_svc.remove(n.id)\"\n >\n ×\n </button>\n </div>\n }\n </div>\n </div>\n }\n `,\n styleUrl: './neu-notification-center.component.scss',\n})\nexport class NeuNotificationCenterComponent {\n readonly _svc = inject(NeuNotificationService);\n readonly _isOpen = signal(false);\n readonly _panelId = `neu-nc-panel-${++_panelSeq}`;\n\n _toggle(): void {\n const opening = !this._isOpen();\n this._isOpen.set(opening);\n if (opening) {\n // Mark all as read when panel opens\n setTimeout(() => this._svc.markAllRead(), 500);\n }\n }\n\n _relativeTime(date: Date): string {\n const diff = Date.now() - date.getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return 'Ahora';\n if (mins < 60) return `Hace ${mins} min`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `Hace ${hours}h`;\n return `Hace ${Math.floor(hours / 24)}d`;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":";;;AA+BA,MAAM,aAAa,GAAwC;AACzD,IAAA,IAAI,EAAE,IAAI;AACV,IAAA,OAAO,EAAE,GAAG;AACZ,IAAA,OAAO,EAAE,IAAI;AACb,IAAA,KAAK,EAAE,GAAG;CACX;AAED,IAAI,MAAM,GAAG,CAAC;AAEd;;;;;;;AAOG;MAEU,sBAAsB,CAAA;AACxB,IAAA,aAAa,GAAG,MAAM,CAAoB,EAAE,oFAAC;IAE7C,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,aAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;;AAGzF,IAAA,IAAI,CAAC,IAA+E,EAAA;AAClF,QAAA,MAAM,EAAE,GAAG,CAAA,UAAA,EAAa,EAAE,MAAM,EAAE;AAClC,QAAA,MAAM,CAAC,GAAoB;YACzB,EAAE;AACF,YAAA,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,MAAM;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;AACrB,YAAA,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC;AACrD,YAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI;YAC/B,SAAS,EAAE,IAAI,IAAI,EAAE;AACrB,YAAA,IAAI,EAAE,KAAK;SACZ;AACD,QAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QAEjD,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,EAAE;AAChC,YAAA,UAAU,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC;QAC/C;AACA,QAAA,OAAO,EAAE;IACX;;AAGA,IAAA,MAAM,CAAC,EAAU,EAAA;QACf,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE;;IAGA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9E;;IAGA,QAAQ,GAAA;AACN,QAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5B;uGAvCW,sBAAsB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAtB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,sBAAsB,cADT,MAAM,EAAA,CAAA;;2FACnB,sBAAsB,EAAA,UAAA,EAAA,CAAA;kBADlC,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;AA2ClC,IAAI,SAAS,GAAG,CAAC;AAEjB;;;;;;;;AAQG;MA0FU,8BAA8B,CAAA;AAChC,IAAA,IAAI,GAAG,MAAM,CAAC,sBAAsB,CAAC;AACrC,IAAA,OAAO,GAAG,MAAM,CAAC,KAAK,8EAAC;AACvB,IAAA,QAAQ,GAAG,CAAA,aAAA,EAAgB,EAAE,SAAS,EAAE;IAEjD,OAAO,GAAA;AACL,QAAA,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE;AAC/B,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;QACzB,IAAI,OAAO,EAAE;;AAEX,YAAA,UAAU,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC;QAChD;IACF;AAEA,IAAA,aAAa,CAAC,IAAU,EAAA;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;QACrC,IAAI,IAAI,GAAG,CAAC;AAAE,YAAA,OAAO,OAAO;QAC5B,IAAI,IAAI,GAAG,EAAE;YAAE,OAAO,CAAA,KAAA,EAAQ,IAAI,CAAA,IAAA,CAAM;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC;QACnC,IAAI,KAAK,GAAG,EAAE;YAAE,OAAO,CAAA,KAAA,EAAQ,KAAK,CAAA,CAAA,CAAG;QACvC,OAAO,CAAA,KAAA,EAAQ,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAA,CAAA,CAAG;IAC1C;uGAtBW,8BAA8B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA9B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,8BAA8B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,yBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,8BAAA,EAAA,EAAA,cAAA,EAAA,QAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAhF/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ET,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,08FAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAGU,8BAA8B,EAAA,UAAA,EAAA,CAAA;kBAzF1C,SAAS;+BACE,yBAAyB,EAAA,OAAA,EAC1B,EAAE,EAAA,aAAA,EACI,iBAAiB,CAAC,IAAI,EAAA,eAAA,EACpB,uBAAuB,CAAC,MAAM,EAAA,IAAA,EACzC;AACJ,wBAAA,KAAK,EAAE,QAAQ;AACf,wBAAA,mBAAmB,EAAE,4BAA4B;qBAClD,EAAA,QAAA,EACS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ET,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,08FAAA,CAAA,EAAA;;;AC5LH;;AAEG;;;;"}
|