@kaufmann-lab/ui 0.1.9 → 0.1.11

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.
@@ -14,11 +14,11 @@ import { ControlValueAccessor } from '@angular/forms';
14
14
  * reciba un `IconName` (`Button`, `MetricCard`, etc.) — no debería requerir
15
15
  * importar el componente `Icon` completo solo para tipar una prop.
16
16
  */
17
- type IconName = 'activity' | 'alert-circle' | 'alert-octagon' | 'alert-triangle' | 'award' | 'bar-chart' | 'bell' | 'bus' | 'calculator' | 'calendar' | 'car' | 'cart' | 'check' | 'check-circle' | 'check-square' | 'chevron-down' | 'chevron-left' | 'chevron-left-bar' | 'chevron-right' | 'clipboard' | 'clock' | 'credit-card' | 'download' | 'edit' | 'external-link' | 'eye' | 'eye-off' | 'file' | 'filter' | 'gauge' | 'gift' | 'grid' | 'history' | 'inbox' | 'layers' | 'list' | 'map' | 'map-pin' | 'menu' | 'mic' | 'mic-off' | 'more-vertical' | 'pause' | 'phone' | 'play' | 'plus' | 'search' | 'settings' | 'shield-check' | 'sort' | 'star' | 'ticket' | 'tool' | 'trash' | 'truck' | 'upload-cloud' | 'user' | 'user-search' | 'video' | 'x';
17
+ type IconName = 'activity' | 'alert-circle' | 'alert-octagon' | 'alert-triangle' | 'award' | 'bar-chart' | 'bell' | 'bus' | 'calculator' | 'calendar' | 'car' | 'cart' | 'check' | 'check-circle' | 'check-square' | 'chevron-down' | 'chevron-left' | 'chevron-left-bar' | 'chevron-right' | 'clipboard' | 'clock' | 'credit-card' | 'download' | 'edit' | 'external-link' | 'eye' | 'eye-off' | 'file' | 'filter' | 'gauge' | 'gift' | 'grid' | 'history' | 'home' | 'inbox' | 'layers' | 'list' | 'log-out' | 'map' | 'map-pin' | 'menu' | 'mic' | 'mic-off' | 'more-vertical' | 'pause' | 'phone' | 'play' | 'plus' | 'search' | 'settings' | 'shield-check' | 'sort' | 'star' | 'ticket' | 'tool' | 'trash' | 'truck' | 'upload-cloud' | 'user' | 'user-search' | 'video' | 'x';
18
18
 
19
19
  type IconSize = 'sm' | 'md' | 'lg';
20
20
  /**
21
- * Ícono de trazo del design system — 60 glyphs (59 de la página 5 + `eye-off`), grilla 24×24,
21
+ * Ícono de trazo del design system — 61 glyphs (59 de la página 5 + `eye-off` y `log-out`), grilla 24×24,
22
22
  * grosor de trazo 1.9px. Referencia un sprite SVG embebido (ver
23
23
  * `icon-sprite.ts`) vía `<use>`, así que no hay peso de red adicional por
24
24
  * cada instancia: el sprite se inyecta al DOM una única vez, la primera
@@ -157,6 +157,71 @@ declare class Alert {
157
157
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Alert, "kf-alert", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["*"], true, never>;
158
158
  }
159
159
 
160
+ /**
161
+ * Layout a pantalla completa para cualquier pantalla de autenticación
162
+ * (login, recuperar contraseña, confirmación de envío) — split marca a la
163
+ * izquierda / contenido libre a la derecha en desktop, formulario a
164
+ * pantalla completa en mobile (el panel de marca completo se oculta, no se
165
+ * apila arriba: en una pantalla angosta el usuario vino a loguearse, no a
166
+ * leer la tagline). Un logo compacto (o wordmark de respaldo) sí se
167
+ * mantiene visible arriba del formulario en mobile — el usuario nunca
168
+ * pierde de vista qué portal está usando, mismo criterio que ya sigue el
169
+ * resto del ecosistema (ver la topbar mobile de `css/otd.css`, que muestra
170
+ * el logo aunque el sidebar completo se oculte).
171
+ *
172
+ * Este componente resuelve el "cascarón" — nunca el formulario en sí. Cada
173
+ * portal arma su propio `<form>` con los componentes de la librería
174
+ * (`kf-input`, `kf-checkbox`, `kf-button`, `kf-alert`) y lo proyecta en el
175
+ * slot por defecto. Es la misma separación de responsabilidad que ya usa
176
+ * `kf-card`: un componente da la estructura visual, el consumidor decide
177
+ * qué va adentro.
178
+ *
179
+ * Antes de este componente, cada portal (LATAM primero, ahora OTD) hubiera
180
+ * tenido que reimplementar este mismo split desde cero — la librería
181
+ * había pospuesto esta abstracción a propósito hasta tener un segundo
182
+ * consumidor real que confirmara qué parte es realmente compartida (el
183
+ * cascarón) y qué parte es de cada portal (el formulario). OTD es ese
184
+ * segundo consumidor.
185
+ *
186
+ * Ningún input tiene valor por defecto propio de ningún portal — `brand`
187
+ * es obligatorio (`input.required`) y el resto de props de marca son
188
+ * opcionales sin fallback hardcodeado, precisamente para que cada
189
+ * consumidor (LATAM, OTD, Star, o el ERP de Asistencia en Ruta con sus
190
+ * propios temas) traiga su propio logo/tagline sin que la librería asuma
191
+ * cuál.
192
+ *
193
+ * <kf-auth-layout
194
+ * brand="Mi Portal"
195
+ * brandLogoSrc="images/mi-portal-logo-white.png"
196
+ * brandLogoMobileSrc="images/mi-portal-logo-black.png"
197
+ * brandTagline="Frase corta de marca, propia de cada portal."
198
+ * >
199
+ * <form (ngSubmit)="onSubmit()">
200
+ * <kf-input label="Correo" type="email" />
201
+ * <kf-input label="Contraseña" type="password" toggleable />
202
+ * <kf-button type="submit">Ingresar</kf-button>
203
+ * </form>
204
+ * </kf-auth-layout>
205
+ */
206
+ declare class AuthLayout {
207
+ /** Nombre de marca/portal, usado como wordmark de respaldo si no hay ningún logo, y como `alt` de ambos logos. */
208
+ readonly brand: _angular_core.InputSignal<string>;
209
+ /** Logo en blanco para el panel oscuro de desktop — si no se pasa, se muestra `brand()` como wordmark tipográfico. */
210
+ readonly brandLogoSrc: _angular_core.InputSignal<string | undefined>;
211
+ /** Frase corta bajo el logo — opcional, cada portal decide su propio texto de marca. */
212
+ readonly brandTagline: _angular_core.InputSignal<string | undefined>;
213
+ /**
214
+ * Logo compacto para mobile, sobre el fondo claro del panel de contenido
215
+ * — normalmente la variante oscura/negra del mismo logo, ya que
216
+ * `brandLogoSrc` suele ser la variante blanca pensada para el gradiente
217
+ * oscuro. Si no se pasa, reutiliza `brandLogoSrc`; si tampoco hay logo en
218
+ * absoluto, cae al wordmark tipográfico igual que en desktop.
219
+ */
220
+ readonly brandLogoMobileSrc: _angular_core.InputSignal<string | undefined>;
221
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<AuthLayout, never>;
222
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AuthLayout, "kf-auth-layout", never, { "brand": { "alias": "brand"; "required": true; "isSignal": true; }; "brandLogoSrc": { "alias": "brandLogoSrc"; "required": false; "isSignal": true; }; "brandTagline": { "alias": "brandTagline"; "required": false; "isSignal": true; }; "brandLogoMobileSrc": { "alias": "brandLogoMobileSrc"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
223
+ }
224
+
160
225
  /**
161
226
  * Avatar de usuario: muestra una foto (`src`) o, si no hay foto, las
162
227
  * iniciales (`initials`) sobre un fondo con gradiente de marca. `round`
@@ -621,84 +686,209 @@ declare class Divider {
621
686
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Divider, "kf-divider", never, { "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
622
687
  }
623
688
 
689
+ type DrawerSide = 'left' | 'right';
690
+ /**
691
+ * Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
692
+ * ni qué es sesión, solo da tres lugares para proyectar contenido
693
+ * (`[kf-drawer-header]`, default, `[kf-drawer-footer]`) y resuelve lo que
694
+ * cualquier drawer necesita resolver igual sin importar qué haya adentro:
695
+ * overlay con blur que cierra al click, `Escape`, atrapado de foco real y
696
+ * devolución de foco al cerrar.
697
+ *
698
+ * Extraído de `PortalHeader`, que hasta acá reimplementaba su propio drawer
699
+ * embebido (navegación hardcodeada + slot único al final). Cualquier
700
+ * consumidor que necesite un panel lateral — no solo `PortalHeader` — puede
701
+ * usar este componente directamente.
702
+ *
703
+ * `open` es two-way, mismo patrón que `Dropdown`/`Modal`: el consumidor
704
+ * decide qué dispara la apertura (un botón de hamburguesa, por ejemplo) y
705
+ * qué hace con el cierre.
706
+ *
707
+ * Foco: al abrir, se crea un `FocusTrap` del CDK sobre el panel (la misma
708
+ * pieza que usa Angular Material para `mat-sidenav`/`mat-dialog`) — Tab y
709
+ * Shift+Tab ciclan solo entre los elementos focuseables del drawer, sin
710
+ * escapar al contenido de fondo. Al cerrar, el foco vuelve al elemento que
711
+ * tenía el foco antes de abrir (guardado en `document.activeElement`), no
712
+ * se pierde en el `<body>`.
713
+ *
714
+ * `[kf-drawer-header]` tiene fondo oscuro propio (`--kf-color-tertiary-deep`),
715
+ * distinto del body (`--kf-color-white`) — pensado para contener
716
+ * `kf-drawer-session`, que asume ese fondo y no lo trae él mismo (ver
717
+ * `DrawerSession.ts`). Si el slot queda vacío, el contenedor colapsa
718
+ * (`display: none`) y no deja una franja oscura sin contenido.
719
+ *
720
+ * <kf-drawer [(open)]="menuOpen" ariaLabel="Menú de navegación">
721
+ * <div kf-drawer-header>
722
+ * <kf-drawer-session name="Tomás Soto" email="tomas@kaufmann.com" (logoutClick)="onLogout()" />
723
+ * </div>
724
+ * <a href="/">Home</a>
725
+ * <a href="/vehiculos">Vehículos</a>
726
+ * </kf-drawer>
727
+ */
728
+ declare class Drawer {
729
+ /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
730
+ readonly open: _angular_core.ModelSignal<boolean>;
731
+ /** Borde de anclaje del panel. `left` (default) es la convención dominante
732
+ * (Material/iOS/Android): el botón que abre el drawer y el propio panel
733
+ * quedan del mismo lado. */
734
+ readonly side: _angular_core.InputSignal<DrawerSide>;
735
+ /** `aria-label` del `<nav>` interno — describe el propósito del panel para
736
+ * lectores de pantalla (ej. "Menú de navegación"). */
737
+ readonly ariaLabel: _angular_core.InputSignal<string>;
738
+ private readonly panelRef;
739
+ private readonly focusTrapFactory;
740
+ private readonly document;
741
+ private focusTrap;
742
+ private lastFocusedElement;
743
+ constructor();
744
+ /** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
745
+ protected onEscape(): void;
746
+ private getOrCreateFocusTrap;
747
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Drawer, never>;
748
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Drawer, "kf-drawer", never, { "open": { "alias": "open"; "required": false; "isSignal": true; }; "side": { "alias": "side"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["[kf-drawer-header]", "*", "[kf-drawer-footer]"], true, never>;
749
+ }
750
+
751
+ /**
752
+ * Botón de cerrar sesión, pensado para proyectarse dentro de
753
+ * `[kf-drawer-footer]` de `kf-drawer`, o junto a `kf-drawer-session` dentro
754
+ * de `[kf-sidebar-footer]` de `kf-sidebar-shell` — separado de
755
+ * `kf-drawer-session` (que solo muestra identidad: brand, avatar, nombre,
756
+ * email), no anidado dentro de ella.
757
+ *
758
+ * Reproduce el mismo botón que antes vivía embebido en `DrawerSession`:
759
+ * ícono `log-out` + label, color `--kf-color-error` con hover en
760
+ * `--kf-color-error-soft` y foco visible con `--kf-shadow-focus`. Se separó
761
+ * en un componente propio porque identidad y logout son responsabilidades
762
+ * distintas — `kf-drawer-session` no debería saber que existe un botón de
763
+ * cerrar sesión, y el footer del drawer ya tiene su propio padding/borde
764
+ * superior (ver `Drawer.ts`) que reemplaza el borde que antes traía el botón
765
+ * mismo.
766
+ *
767
+ * No sabe nada de autenticación real — solo emite `logoutClick` cuando el
768
+ * consumidor debe cerrar la sesión real.
769
+ *
770
+ * `collapsed` oculta el label y centra el ícono, exponiéndolo vía `title`
771
+ * — mismo criterio que `SidebarNavItem.collapsed` y
772
+ * `DrawerSession.collapsed`, para cuando este botón vive dentro de un
773
+ * `kf-sidebar-shell` colapsado.
774
+ *
775
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
776
+ */
777
+ declare class DrawerLogout {
778
+ /** Label del botón, en español neutro por defecto. Input opcional por si
779
+ * algún consumidor futuro necesita otro texto. */
780
+ readonly label: _angular_core.InputSignal<string>;
781
+ /** Oculta el label y centra el ícono — ver comentario de la clase. */
782
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
783
+ readonly logoutClick: _angular_core.OutputEmitterRef<void>;
784
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerLogout, never>;
785
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DrawerLogout, "kf-drawer-logout", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; }, { "logoutClick": "logoutClick"; }, never, never, true, never>;
786
+ }
787
+
788
+ /**
789
+ * Bloque de identidad para `[kf-drawer-header]` de `kf-drawer` o
790
+ * `[kf-sidebar-footer]` de `kf-sidebar-shell`: avatar + nombre + email,
791
+ * brand opcional arriba. Solo identidad — el logout vive aparte, en
792
+ * `kf-drawer-logout`.
793
+ *
794
+ * `collapsed` oculta brand/nombre/email y deja solo el avatar (para un
795
+ * `kf-sidebar-shell` colapsado). `interactive` convierte la tarjeta en un
796
+ * botón real que emite `cardClick`, para cuando la identidad es el
797
+ * disparador de un menú de acciones (ej. envuelta en `kf-dropdown`).
798
+ *
799
+ * <kf-dropdown [(open)]="menuOpen">
800
+ * <kf-drawer-session interactive name="Juan Pérez" email="juan@empresa.com" (cardClick)="menuOpen.set(!menuOpen())" />
801
+ * <kf-dropdown-panel variant="dark">
802
+ * <kf-dropdown-item icon="log-out" tone="error" (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
803
+ * </kf-dropdown-panel>
804
+ * </kf-dropdown>
805
+ */
806
+ declare class DrawerSession {
807
+ readonly name: _angular_core.InputSignal<string>;
808
+ readonly email: _angular_core.InputSignal<string>;
809
+ readonly initials: _angular_core.InputSignal<string>;
810
+ readonly avatarUrl: _angular_core.InputSignal<string>;
811
+ /** Label superior opcional, ej. "Portal Kaufmann LATAM". */
812
+ readonly brand: _angular_core.InputSignal<string>;
813
+ /** Oculta brand/nombre/email y centra el avatar — ver comentario de la clase. */
814
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
815
+ /** Convierte la tarjeta en un botón real con ícono de kebab — ver comentario de la clase. */
816
+ readonly interactive: _angular_core.InputSignalWithTransform<boolean, unknown>;
817
+ /** Se emite al clickear la tarjeta, solo cuando `interactive` es `true`. */
818
+ readonly cardClick: _angular_core.OutputEmitterRef<void>;
819
+ /** Si no se pasa `initials`, se derivan de `name`: primera letra de cada
820
+ * una de las primeras 2 palabras, en mayúscula. */
821
+ protected readonly displayInitials: _angular_core.Signal<string>;
822
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<DrawerSession, never>;
823
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DrawerSession, "kf-drawer-session", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "email": { "alias": "email"; "required": false; "isSignal": true; }; "initials": { "alias": "initials"; "required": false; "isSignal": true; }; "avatarUrl": { "alias": "avatarUrl"; "required": false; "isSignal": true; }; "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; }, { "cardClick": "cardClick"; }, never, never, true, never>;
824
+ }
825
+
624
826
  type DropdownItemTone = 'neutral' | 'error';
625
827
  /**
626
828
  * Ítem de acción dentro de un `kf-dropdown-panel`. Es un `<button>` nativo
627
- * por dentro (no un `<a>`): las acciones de un menú de este tipo disparan
628
- * comportamiento en la pantalla actual (cerrar sesión, ejecutar un
629
- * comando), no navegan a otra URL.
829
+ * (no un `<a>`): dispara comportamiento en la pantalla actual, no navega.
630
830
  *
631
- * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
831
+ * `icon` es opcional — sin él, el ítem es solo texto.
832
+ *
833
+ * <kf-dropdown-item icon="log-out" (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
632
834
  */
633
835
  declare class DropdownItem {
634
836
  readonly tone: _angular_core.InputSignal<DropdownItemTone>;
837
+ /** Ícono opcional al inicio de la fila. */
838
+ readonly icon: _angular_core.InputSignal<IconName | undefined>;
635
839
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownItem, never>;
636
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownItem, "kf-dropdown-item", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
840
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownItem, "kf-dropdown-item", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
637
841
  }
842
+ type DropdownPanelVariant = 'solid' | 'dark';
638
843
  /**
639
844
  * Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
640
- * (`<ng-template>` por dentro, capturada vía `TemplateRef`) — `Dropdown` la
641
- * conecta al overlay real cuando `open` pasa a `true`, no la renderiza en su
642
- * propio lugar del DOM. Esto es lo que permite que el panel aparezca pegado
643
- * al host de `Dropdown` sin importar dónde esté ese host en la página.
644
- *
645
- * <kf-dropdown [(open)]="menuOpen">
646
- * <kf-dropdown-panel>
647
- * <kf-dropdown-item (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
648
- * </kf-dropdown-panel>
649
- * </kf-dropdown>
845
+ * (`<ng-template>`, capturada vía `TemplateRef`) — `Dropdown` la conecta al
846
+ * overlay real cuando `open` pasa a `true`, no la renderiza en su propio
847
+ * lugar del DOM. Esto permite que el panel aparezca pegado al host de
848
+ * `Dropdown` sin importar dónde esté ese host en la página.
849
+ *
850
+ * `variant`: `'solid'` (default, fondo blanco) o `'dark'` (fondo sólido
851
+ * oscuro, mismo tono que `[kf-drawer-header]`) para cuando el trigger vive
852
+ * sobre una superficie oscura de marca.
853
+ *
854
+ * `dark` es sólida a propósito, no semitransparente: el CDK Overlay
855
+ * reposiciona el panel según dónde entra en el viewport y puede terminar
856
+ * cayendo sobre fondo blanco en vez de sobre el rail oscuro — un color
857
+ * sólido se ve intencional sin importar dónde lo posicione el CDK.
650
858
  */
651
859
  declare class DropdownPanel {
860
+ readonly variant: _angular_core.InputSignal<DropdownPanelVariant>;
652
861
  readonly templateRef: _angular_core.Signal<TemplateRef<any>>;
653
862
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<DropdownPanel, never>;
654
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, {}, {}, never, ["*"], true, never>;
863
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<DropdownPanel, "kf-dropdown-panel", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
655
864
  }
656
865
  /**
657
866
  * Menú de acciones flotante, conectado a un elemento disparador vía
658
867
  * `@angular/cdk/overlay` — la misma pieza que usa Angular Material para
659
- * `mat-menu`/`mat-select`. Resuelve el patrón que antes se reimplementaba a
660
- * mano en cada consumidor (ver el session-menu de `web-portal-latam-frontend`,
661
- * ~80 líneas de HTML/TS/SCSS para toggle + click-afuera + Escape + animación
662
- * de entrada, acopladas a una clase interna no pública de `PortalHeader`).
663
- *
664
- * Se apoya en el CDK en vez de un `position: absolute` casero (como sí hace
665
- * `SelectMenu`, que puede permitírselo porque su trigger vive siempre dentro
666
- * del propio componente) porque acá el disparador real puede vivir en OTRO
667
- * componente por completo — ej. el avatar dentro de `kf-portal-header` — y
668
- * el panel tiene que aparecer pegado a ESE elemento sin importar dónde el
669
- * consumidor ubique al `Dropdown` en su HTML. El Overlay CDK calcula esa
670
- * posición (con fallback si no entra en pantalla) en vez de que cada
671
- * consumidor lo recalcule a mano.
672
- *
673
- * `open` es two-way, igual que `Alert`/`Modal`: el consumidor decide qué
674
- * dispara la apertura (puede ser un `(click)` en un botón propio, o un
675
- * evento de otro componente como `PortalHeader.sessionClick`) — `Dropdown`
676
- * en sí no renderiza ningún trigger, su propio host es el punto de anclaje
677
- * visual del panel.
868
+ * `mat-menu`/`mat-select`.
678
869
  *
679
- * <kf-dropdown [(open)]="menuOpen">
680
- * <kf-dropdown-panel>
681
- * <div class="mi-header">Sesión iniciada como {{ email() }}</div>
682
- * <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
683
- * </kf-dropdown-panel>
684
- * </kf-dropdown>
870
+ * Se apoya en el CDK en vez de un `position: absolute` casero (a diferencia
871
+ * de `SelectMenu`, que puede permitírselo porque su trigger vive siempre
872
+ * dentro del propio componente) porque acá el disparador real puede vivir
873
+ * en OTRO componente por completo — ej. el avatar dentro de
874
+ * `kf-portal-header` — y el panel tiene que aparecer pegado a ESE elemento
875
+ * sin importar dónde el consumidor ubique al `Dropdown` en su HTML.
876
+ *
877
+ * `open` es two-way: el consumidor decide qué dispara la apertura —
878
+ * `Dropdown` en sí no renderiza ningún trigger, su propio host es el punto
879
+ * de anclaje visual del panel.
880
+ *
881
+ * <kf-dropdown [(open)]="menuOpen"><kf-dropdown-panel>...</kf-dropdown-panel></kf-dropdown>
685
882
  */
686
883
  declare class Dropdown {
687
884
  /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
688
885
  readonly open: _angular_core.ModelSignal<boolean>;
689
886
  /**
690
887
  * Elemento al que se conecta el overlay, si es distinto del propio host
691
- * de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es visualmente
692
- * el disparador (caso normal: el consumidor arma su propio botón como
693
- * contenido, y `Dropdown` envuelve ese contenido). Cuando el disparador
694
- * real vive dentro de OTRO componente que no se puede envolver sin
695
- * ensanchar el área de click a algo más grande que el botón (ej. el
696
- * avatar de sesión dentro de `PortalHeader`, que convive con logo y
697
- * navegación en el mismo host), `anchor` permite apuntar el overlay a la
698
- * referencia real de ese componente en vez de al host vacío de
699
- * `Dropdown`.
700
- *
701
- * <kf-dropdown [(open)]="menuOpen" [anchor]="headerRef()">
888
+ * de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es el
889
+ * disparador. Útil cuando el disparador real vive dentro de OTRO
890
+ * componente que no se puede envolver sin ensanchar el área de click
891
+ * (ej. el avatar de sesión dentro de `PortalHeader`).
702
892
  */
703
893
  readonly anchor: _angular_core.InputSignal<ElementRef<HTMLElement> | undefined>;
704
894
  private readonly panel;
@@ -707,18 +897,41 @@ declare class Dropdown {
707
897
  private readonly viewContainerRef;
708
898
  private readonly destroyRef;
709
899
  private overlayRef;
900
+ /**
901
+ * Suscripciones a `outsidePointerEvents()`/`keydownEvents()` del overlay
902
+ * ACTUALMENTE adjunto. `getOrCreateOverlayRef()` reutiliza el mismo
903
+ * `overlayRef` entre aperturas (no lo recrea cada vez) — sin esto,
904
+ * `attach()` se resuscribía a esos observables en cada apertura sin
905
+ * cancelar la suscripción de la vez anterior, acumulando N suscripciones
906
+ * activas sobre el mismo overlay persistente. Eso causaba el bug real de
907
+ * "clicks seguidos": al abrir de nuevo, el propio pointerdown que originó
908
+ * la apertura llegaba a más de una suscripción acumulada, disparando
909
+ * `open.set(false)` de forma errática en el mismo tick (patrón observado:
910
+ * abre cada 2 clicks, cierra cada 1). Se cancelan explícitamente en
911
+ * `detach()`, antes de la siguiente `attach()`.
912
+ */
913
+ private overlaySubscription;
710
914
  constructor();
711
915
  /**
712
916
  * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo
713
- * ve teclas presionadas con el foco DENTRO del pane del overlay. Si el
714
- * usuario abre el menú y presiona Escape con el foco en otro lado de la
715
- * página (caso común — el foco puede seguir en el botón disparador, que
716
- * vive fuera del overlay), `keydownEvents()` nunca dispara. Este listener
717
- * a nivel de documento cubre ese caso; el de `keydownEvents()` sigue
718
- * activo para cuando el foco sí está dentro del panel.
917
+ * ve teclas con el foco DENTRO del panel. Si el foco queda en el botón
918
+ * disparador (caso común), `keydownEvents()` nunca dispara — este
919
+ * listener a nivel de documento cubre ese caso.
719
920
  */
720
921
  onDocumentEscape(): void;
721
922
  private attach;
923
+ /**
924
+ * El trigger real (el `anchor`, o el propio host si no se pasó ninguno —
925
+ * ver comentario de la clase) vive FUERA del overlay a propósito: el
926
+ * overlay recién se crea/adjunta cuando `open` pasa a `true`, así que el
927
+ * CDK no tiene forma de saber que ese elemento externo "es parte" de su
928
+ * disparador. Sin este filtro, el mismo `pointerdown` que abre el menú
929
+ * (el trigger no es hijo del overlay) también llega a
930
+ * `outsidePointerEvents()`, cerrando el panel en el mismo gesto que lo
931
+ * abrió — bug real reproducido con Playwright: "abre y cierra" en
932
+ * clicks consecutivos rápidos sobre la tarjeta de sesión del sidebar.
933
+ */
934
+ private isEventOnTrigger;
722
935
  private detach;
723
936
  private getOrCreateOverlayRef;
724
937
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Dropdown, never>;
@@ -890,11 +1103,11 @@ interface FolderTabOption {
890
1103
  * atributo porque el panel no necesita comportamiento propio, solo marcar
891
1104
  * su presencia).
892
1105
  *
893
- * <div kf-folder-tabs-panel>...</div>
1106
+ * <div kfFolderTabsPanel>...</div>
894
1107
  */
895
1108
  declare class FolderTabsPanel {
896
1109
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabsPanel, never>;
897
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FolderTabsPanel, "[kf-folder-tabs-panel]", never, {}, {}, never, never, true, never>;
1110
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FolderTabsPanel, "[kfFolderTabsPanel]", never, {}, {}, never, never, true, never>;
898
1111
  }
899
1112
  /**
900
1113
  * Tabs con forma de "carpeta": el borde superior queda redondeado y el borde
@@ -936,7 +1149,7 @@ declare class FolderTabsPanel {
936
1149
  * evita que cada consumidor repita esas reglas a mano.
937
1150
  *
938
1151
  * <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
939
- * <div kf-folder-tabs-panel>contenido de la pestaña activa</div>
1152
+ * <div kfFolderTabsPanel>contenido de la pestaña activa</div>
940
1153
  * </kf-folder-tabs>
941
1154
  *
942
1155
  * <!-- o, sin contenido, como antes: -->
@@ -948,7 +1161,7 @@ declare class FolderTabs {
948
1161
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
949
1162
  /**
950
1163
  * Detecta si el consumidor proyectó contenido marcado con
951
- * `kf-folder-tabs-panel` — sin `.required`, así que es `undefined`
1164
+ * `kfFolderTabsPanel` — sin `.required`, así que es `undefined`
952
1165
  * cuando no hay nada proyectado (mismo patrón que usa `kf-dropdown` con
953
1166
  * `contentChild(DropdownPanel)` para saber si hay un panel que
954
1167
  * attachear). `hasPanel()` decide si el wrapper del panel participa del
@@ -960,7 +1173,39 @@ declare class FolderTabs {
960
1173
  /** Navegación estándar de ARIA tabs: flechas mueven el foco y activan la pestaña. */
961
1174
  onKeydown(event: KeyboardEvent, currentValue: string): void;
962
1175
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FolderTabs, never>;
963
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FolderTabs, "kf-folder-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, ["projectedPanel"], ["[kf-folder-tabs-panel]"], true, never>;
1176
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FolderTabs, "kf-folder-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, ["projectedPanel"], ["[kfFolderTabsPanel]"], true, never>;
1177
+ }
1178
+
1179
+ /** Un link del pie — legales, contacto, soporte. */
1180
+ interface FooterLink {
1181
+ label: string;
1182
+ href?: string;
1183
+ }
1184
+ /**
1185
+ * Pie de página general del portal (página 38): logo + copyright a la
1186
+ * izquierda, links legales/soporte empujados a la derecha. Logo y copyright
1187
+ * son inputs (cada portal del ecosistema — LATAM, OTD, Star — trae su
1188
+ * propia marca y texto legal); `links` es la lista de accesos, típicamente
1189
+ * placeholders hasta que existan destinos reales.
1190
+ *
1191
+ * <kf-footer
1192
+ * logoSrc="images/logo-kaufmann-dark.png"
1193
+ * logoAlt="Grupo Kaufmann"
1194
+ * copyright="© 2026 Kaufmann LATAM · Todos los derechos reservados"
1195
+ * [links]="[
1196
+ * { label: 'Políticas de privacidad' },
1197
+ * { label: 'Contacto' },
1198
+ * { label: 'Sugerencias y reclamos' },
1199
+ * ]"
1200
+ * />
1201
+ */
1202
+ declare class Footer {
1203
+ readonly logoSrc: _angular_core.InputSignal<string | undefined>;
1204
+ readonly logoAlt: _angular_core.InputSignal<string>;
1205
+ readonly copyright: _angular_core.InputSignal<string>;
1206
+ readonly links: _angular_core.InputSignal<FooterLink[]>;
1207
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Footer, never>;
1208
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Footer, "kf-footer", never, { "logoSrc": { "alias": "logoSrc"; "required": false; "isSignal": true; }; "logoAlt": { "alias": "logoAlt"; "required": false; "isSignal": true; }; "copyright": { "alias": "copyright"; "required": true; "isSignal": true; }; "links": { "alias": "links"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
964
1209
  }
965
1210
 
966
1211
  /**
@@ -1165,18 +1410,25 @@ interface MultiSelectOption {
1165
1410
  label: string;
1166
1411
  }
1167
1412
  /**
1168
- * Dropdown de selección múltiple con checkboxes internos, siguiendo el mismo
1169
- * patrón de panel flotante que `SelectMenu` (panel con `position: absolute`
1170
- * sobre el host, sin Angular CDK Overlay, que no es dependencia del proyecto).
1171
- * Se abre y cierra por click en el disparador, y se cierra al hacer click
1172
- * afuera o al presionar Escape.
1173
- *
1174
- * A diferencia de `SelectMenu`, el disparador no muestra un único valor sino
1175
- * los nombres seleccionados junto a un badge circular con el conteo (página
1176
- * 12: "Sucursal Norte, Lima Centro" + badge "2"), y cada opción del panel se
1177
- * marca/desmarca con un checkbox sin cerrar el panel. Incluye una opción
1178
- * "Todas" al inicio de la lista, con estado `indeterminate` cuando hay
1179
- * selección parcial — el mismo patrón de "seleccionar todos" de `Checkbox`.
1413
+ * Dropdown de selección múltiple con checkboxes internos. El panel flotante
1414
+ * se conecta al trigger vía `@angular/cdk/overlay` — el mismo mecanismo que
1415
+ * `Select` (ver `../select/Select.ts`) y `Dropdown` (`../dropdown/Dropdown.ts`).
1416
+ *
1417
+ * Hasta la `0.1.9` este panel vivía con `position: absolute` manual sobre el
1418
+ * propio host, sin depender del CDK Overlay. Se migró para unificar el
1419
+ * mecanismo de posicionamiento/cierre con el resto de los dropdowns flotantes
1420
+ * de la librería (4 posiciones de fallback, `withViewportMargin`, `withPush`,
1421
+ * cierre por `outsidePointerEvents()`/`keydownEvents()` en vez de un
1422
+ * `@HostListener('document:click')` manual).
1423
+ *
1424
+ * A diferencia de `Select`, el disparador no muestra un único valor sino los
1425
+ * nombres seleccionados junto a un badge circular con el conteo, y cada
1426
+ * opción del panel se marca/desmarca con un checkbox sin cerrar el panel.
1427
+ * Incluye una opción "Todas" al inicio de la lista, con estado
1428
+ * `indeterminate` cuando hay selección parcial — el mismo patrón de
1429
+ * "seleccionar todos" de `Checkbox`. "Todas" nunca se filtra por el buscador:
1430
+ * queda siempre visible y sigue actuando sobre el universo COMPLETO de
1431
+ * `options()`, no sobre el subconjunto filtrado (ver JSDoc de `toggleAll()`).
1180
1432
  *
1181
1433
  * <kf-multi-select
1182
1434
  * label="Sucursales"
@@ -1195,14 +1447,27 @@ declare class MultiSelect {
1195
1447
  readonly values: _angular_core.InputSignal<string[]>;
1196
1448
  readonly placeholder: _angular_core.InputSignal<string>;
1197
1449
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1450
+ /**
1451
+ * Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
1452
+ * se activa solo cuando `options().length` supera `searchThreshold()`. Si
1453
+ * el consumidor define `true`/`false` explícitamente, ese valor manda por
1454
+ * sobre el umbral automático. Mismo criterio que `Select`.
1455
+ */
1456
+ readonly searchable: _angular_core.InputSignal<boolean | undefined>;
1457
+ readonly searchThreshold: _angular_core.InputSignal<number>;
1198
1458
  readonly valuesChange: _angular_core.OutputEmitterRef<string[]>;
1199
1459
  private readonly instanceId;
1200
1460
  readonly triggerId: string;
1201
1461
  readonly listboxId: string;
1202
1462
  readonly isOpen: _angular_core.WritableSignal<boolean>;
1463
+ readonly searchQuery: _angular_core.WritableSignal<string>;
1464
+ readonly activeIndex: _angular_core.WritableSignal<number>;
1203
1465
  readonly selectedCount: _angular_core.Signal<number>;
1204
1466
  readonly allSelected: _angular_core.Signal<boolean>;
1205
1467
  readonly isPartiallySelected: _angular_core.Signal<boolean>;
1468
+ readonly showSearch: _angular_core.Signal<boolean>;
1469
+ /** Opciones visibles tras aplicar el filtro de búsqueda ("Todas" nunca se filtra). */
1470
+ readonly filteredOptions: _angular_core.Signal<MultiSelectOption[]>;
1206
1471
  /**
1207
1472
  * Texto del disparador: placeholder si no hay selección, o los nombres
1208
1473
  * seleccionados unidos por coma (el conteo va aparte, en el badge). Con
@@ -1210,18 +1475,88 @@ declare class MultiSelect {
1210
1475
  * corta el texto — el badge de conteo siempre queda visible al lado.
1211
1476
  */
1212
1477
  readonly summaryText: _angular_core.Signal<string>;
1213
- private readonly host;
1478
+ private readonly triggerRef;
1479
+ private readonly panelTemplate;
1480
+ private readonly searchInputRef;
1481
+ private readonly overlayService;
1482
+ private readonly viewContainerRef;
1483
+ private readonly destroyRef;
1484
+ private overlayRef;
1485
+ constructor();
1214
1486
  isSelected(value: string): boolean;
1215
1487
  toggle(): void;
1216
1488
  toggleOption(value: string): void;
1217
- /** Alterna entre "todas seleccionadas" y "ninguna seleccionada". */
1489
+ /**
1490
+ * Alterna entre "todas seleccionadas" y "ninguna seleccionada". Actúa
1491
+ * siempre sobre el universo COMPLETO de `options()`, no sobre
1492
+ * `filteredOptions()` — con un buscador activo, "Todas" no se filtra ni
1493
+ * cambia de significado: seguir marcando/desmarcando únicamente lo visible
1494
+ * sería sorprendente para quien ya tenía otras opciones (no visibles en el
1495
+ * filtro actual) seleccionadas de antes.
1496
+ */
1218
1497
  toggleAll(): void;
1219
- onDocumentClick(event: MouseEvent): void;
1498
+ onSearchInput(event: Event): void;
1499
+ /** Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp simple, sin wraparound. */
1500
+ moveActive(delta: number): void;
1501
+ toggleActive(): void;
1220
1502
  onEscape(): void;
1503
+ private attach;
1504
+ private detach;
1505
+ private getOrCreateOverlayRef;
1221
1506
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MultiSelect, never>;
1222
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MultiSelect, "kf-multi-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "valuesChange": "valuesChange"; }, never, never, true, never>;
1507
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MultiSelect, "kf-multi-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "values": { "alias": "values"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; }, { "valuesChange": "valuesChange"; }, never, never, true, never>;
1223
1508
  }
1224
1509
 
1510
+ type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
1511
+ type TextVariant = 'title-xxl' | 'title-xl' | 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption';
1512
+ type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
1513
+ /**
1514
+ * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
1515
+ * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
1516
+ * ve). Antes de este componente, cada pantalla que consume la librería
1517
+ * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
1518
+ * (título de página, subtítulo, texto secundario) — auditoría sobre
1519
+ * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
1520
+ * home sin ningún paso de escala en común. `variant` resuelve eso contra
1521
+ * la escala de `tokens/base.css` (`--kf-font-size-*`).
1522
+ *
1523
+ * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
1524
+ * escape hatch de color libre. Si un caso necesita un color que `tone` no
1525
+ * cubre, es una discusión de design system, no una prop nueva acá.
1526
+ *
1527
+ * `title-xl`/`title-xxl` son un tier aparte de `title-lg|md|sm`: no vienen
1528
+ * de la auditoría de contenido de página (esa cerró en 2.125rem), son para
1529
+ * títulos protagonistas de pantalla completa (ej. bienvenida del home vía
1530
+ * `kf-page-header[size=xl|xxl]`) — ver tokens/base.css.
1531
+ *
1532
+ * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
1533
+ * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
1534
+ * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
1535
+ */
1536
+ declare class Text {
1537
+ /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
1538
+ readonly as: _angular_core.InputSignal<TextAs>;
1539
+ readonly variant: _angular_core.InputSignal<TextVariant>;
1540
+ readonly tone: _angular_core.InputSignal<TextTone>;
1541
+ /**
1542
+ * Copia de `as` para uso dentro del template: `as` es palabra reservada
1543
+ * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
1544
+ * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
1545
+ * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
1546
+ */
1547
+ protected readonly tag: _angular_core.Signal<TextAs>;
1548
+ protected hostClass(): string;
1549
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
1550
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Text, "kf-text", never, { "as": { "alias": "as"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1551
+ }
1552
+
1553
+ /**
1554
+ * Tamaño del título — `md` es el tamaño de siempre (no rompe a nadie que no
1555
+ * lo especifique). Cada valor mapea 1:1 a una variante `title-*` de
1556
+ * `kf-text` (ver `Text.ts`); `xl`/`xxl` son el tier "hero" de esa escala,
1557
+ * pensado para casos de un título más protagonista (ej. bienvenida del home).
1558
+ */
1559
+ type PageHeaderTitleSize = 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
1225
1560
  /**
1226
1561
  * Bloque de título al tope de una página: chip de ícono opcional, título (h1),
1227
1562
  * subtítulo opcional y un área de acciones opcional proyectada vía
@@ -1233,13 +1568,17 @@ declare class MultiSelect {
1233
1568
  * </kf-page-header>
1234
1569
  *
1235
1570
  * <kf-page-header icon="settings" title="Configuración" />
1571
+ *
1572
+ * <kf-page-header size="xl" title="Bienvenido de nuevo" />
1236
1573
  */
1237
1574
  declare class PageHeader {
1238
1575
  readonly icon: _angular_core.InputSignal<IconName | undefined>;
1239
1576
  readonly title: _angular_core.InputSignal<string>;
1240
1577
  readonly subtitle: _angular_core.InputSignal<string | undefined>;
1578
+ readonly size: _angular_core.InputSignal<PageHeaderTitleSize>;
1579
+ protected readonly titleVariant: _angular_core.Signal<TextVariant>;
1241
1580
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PageHeader, never>;
1242
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PageHeader, "kf-page-header", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1581
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PageHeader, "kf-page-header", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1243
1582
  }
1244
1583
 
1245
1584
  /** Un número de página clickeable o un separador "…" no interactivo. */
@@ -1285,6 +1624,13 @@ declare class Pagination {
1285
1624
  * <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos">
1286
1625
  * <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
1287
1626
  * </kf-panel>
1627
+ *
1628
+ * El host y el `kf-card` interno son flex column con `flex:1` de punta a
1629
+ * punta — si un grid/flex externo estira `kf-panel` a la altura de un
1630
+ * hermano más alto, el contenido proyectado hereda esa altura en vez de
1631
+ * dejar un hueco vacío bajo el `kf-card`. Sin esa presión externa (altura
1632
+ * "auto" del contenedor), `flex:1` no tiene espacio extra que repartir y el
1633
+ * panel se comporta igual que antes.
1288
1634
  */
1289
1635
  declare class Panel {
1290
1636
  readonly title: _angular_core.InputSignal<string>;
@@ -1294,11 +1640,14 @@ declare class Panel {
1294
1640
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Panel, "kf-panel", never, { "title": { "alias": "title"; "required": true; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1295
1641
  }
1296
1642
 
1297
- /** Un ítem de navegación de la barra de marca. */
1643
+ /** Un ítem de navegación de la barra de marca. `icon` es opcional y solo se
1644
+ * usa en el drawer mobile (columna, ver template) — el `<nav>` horizontal de
1645
+ * escritorio se mantiene solo texto, sin ícono ni chevron. */
1298
1646
  interface PortalHeaderNavItem {
1299
1647
  label: string;
1300
1648
  href: string;
1301
1649
  active?: boolean;
1650
+ icon?: IconName;
1302
1651
  }
1303
1652
  /** Una acción de la franja de administrador (ej. "Consultar deuda clientes"). */
1304
1653
  interface PortalHeaderAdminAction {
@@ -1321,21 +1670,43 @@ interface PortalHeaderAdminAction {
1321
1670
  *
1322
1671
  * Bajo los 768px, el `<nav>` deja de mostrarse en línea junto al logo y
1323
1672
  * colapsa detrás de un botón de menú (ícono `menu`/`x`) que despliega un
1324
- * drawer lateral (desde el borde derecho, `position: fixed` + overlay de
1325
- * fondo) con los mismos `navItems` en columna. Ese drawer expone un slot
1326
- * (`<ng-content select="[kf-portal-header-extra]">`) al final, pensado
1327
- * para que el consumidor proyecte contenido propio que en escritorio vive
1328
- * en otro lugar (ej. el menú de sesión) — `PortalHeader` no sabe qué es
1329
- * ese contenido ni cómo se abre, solo le da un lugar dentro del drawer ya
1330
- * desplegado. El overlay cierra el drawer al tocarlo, y `Escape` también.
1673
+ * `kf-drawer` (panel lateral genérico, ver `Drawer.ts`) anclado al borde
1674
+ * IZQUIERDO — mismo lado que el botón que lo abre, convención dominante
1675
+ * Material/iOS/Android — con los mismos `navItems` en columna dentro del
1676
+ * body (fondo claro) de ese drawer.
1677
+ *
1678
+ * `PortalHeader` expone dos slots propios pensados para que el consumidor
1679
+ * proyecte contenido que en escritorio vive en otro lugar:
1680
+ * - `<ng-content select="[kf-portal-header-extra]">`: identidad de sesión
1681
+ * (ej. `kf-drawer-session`). Cae en `[kf-drawer-header]` de `kf-drawer` —
1682
+ * el único contenedor con fondo oscuro (`--kf-color-tertiary-deep`, ver
1683
+ * `Drawer.ts`) que `kf-drawer-session` espera como fondo (no trae uno
1684
+ * propio, ver `DrawerSession.ts`).
1685
+ * - `<ng-content select="[kf-portal-header-footer]">`: acciones separadas del
1686
+ * resto (ej. `kf-drawer-logout`). Cae en `[kf-drawer-footer]` de
1687
+ * `kf-drawer`, mismo criterio de "no reimplementar nada del drawer": el
1688
+ * footer ya trae su propio padding/separador (ver `Drawer.ts`).
1689
+ *
1690
+ * `PortalHeader` no sabe qué es ninguno de los dos contenidos ni cómo se
1691
+ * abren, solo les da el lugar correcto dentro del drawer ya desplegado.
1692
+ * `kf-drawer` resuelve overlay con blur, cierre con click-afuera/`Escape`,
1693
+ * atrapado de foco real y devolución de foco al cerrar — `PortalHeader` no
1694
+ * reimplementa nada de eso, solo controla su `open` desde el botón de
1695
+ * hamburguesa.
1331
1696
  *
1332
1697
  * <kf-portal-header
1333
1698
  * [isAdmin]="true"
1334
1699
  * [adminActions]="[{ label: 'Consultar deuda clientes', href: '/deuda' }, { label: 'Cambiar DNI', href: '/dni' }]"
1335
- * [navItems]="[{ label: 'Home', href: '/', active: true }, { label: 'Vehículos', href: '/vehiculos' }]"
1700
+ * [navItems]="[{ label: 'Home', href: '/', active: true, icon: 'grid' }, { label: 'Vehículos', href: '/vehiculos', icon: 'truck' }]"
1336
1701
  * userInitials="TS"
1337
1702
  * >
1338
- * <div kf-portal-header-extra>contenido propio, solo visible en el panel mobile</div>
1703
+ * <kf-drawer-session
1704
+ * kf-portal-header-extra
1705
+ * brand="Portal Kaufmann LATAM"
1706
+ * name="Tomás Soto"
1707
+ * email="tomas.soto@kaufmann.com"
1708
+ * />
1709
+ * <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
1339
1710
  * </kf-portal-header>
1340
1711
  */
1341
1712
  declare class PortalHeader {
@@ -1345,10 +1716,10 @@ declare class PortalHeader {
1345
1716
  readonly navItems: _angular_core.InputSignal<PortalHeaderNavItem[]>;
1346
1717
  readonly userInitials: _angular_core.InputSignal<string>;
1347
1718
  readonly sessionClick: _angular_core.OutputEmitterRef<void>;
1348
- /** Visibilidad del drawer de navegación mobile (bajo los 768px). */
1719
+ /** Visibilidad del drawer de navegación mobile (bajo los 768px). Se pasa a
1720
+ * `kf-drawer` como `[(open)]` — `PortalHeader` ya no implementa overlay,
1721
+ * `Escape` ni atrapado de foco por su cuenta, eso vive en `Drawer`. */
1349
1722
  protected readonly mobileMenuOpen: _angular_core.WritableSignal<boolean>;
1350
- /** Cierra el drawer con Escape — mismo patrón que cualquier drawer/modal. */
1351
- protected onEscape(): void;
1352
1723
  /**
1353
1724
  * `ElementRef` real del botón de sesión (el avatar en la esquina
1354
1725
  * derecha), no de todo el header. Pensado para anclar un `kf-dropdown`
@@ -1361,7 +1732,7 @@ declare class PortalHeader {
1361
1732
  */
1362
1733
  readonly sessionButtonRef: _angular_core.Signal<ElementRef<any>>;
1363
1734
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<PortalHeader, never>;
1364
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "isAdmin": { "alias": "isAdmin"; "required": false; "isSignal": true; }; "adminActions": { "alias": "adminActions"; "required": false; "isSignal": true; }; "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "userInitials": { "alias": "userInitials"; "required": true; "isSignal": true; }; }, { "sessionClick": "sessionClick"; }, never, ["[kf-portal-header-extra]"], true, never>;
1735
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<PortalHeader, "kf-portal-header", never, { "brand": { "alias": "brand"; "required": false; "isSignal": true; }; "isAdmin": { "alias": "isAdmin"; "required": false; "isSignal": true; }; "adminActions": { "alias": "adminActions"; "required": false; "isSignal": true; }; "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "userInitials": { "alias": "userInitials"; "required": true; "isSignal": true; }; }, { "sessionClick": "sessionClick"; }, never, ["[kf-portal-header-extra]", "[kf-portal-header-footer]"], true, never>;
1365
1736
  }
1366
1737
 
1367
1738
  /**
@@ -1375,7 +1746,12 @@ declare class PortalHeader {
1375
1746
  * invitación + las acciones como un bloque compuesto propio del dominio
1376
1747
  * "taller", no un placeholder de una sola pieza.
1377
1748
  *
1378
- * <kf-reserve-block emptyLabel="No tienes reservas de taller"
1749
+ * `grow` estira el panel de invitación para llenar el espacio vertical
1750
+ * disponible — solo cuando el consumidor comparte fila con una tarjeta más
1751
+ * alta (ej. junto a "Total de vehículos"); en un lugar sin esa presión
1752
+ * (home, debajo del carousel) se deja en su alto natural.
1753
+ *
1754
+ * <kf-reserve-block emptyLabel="No tienes reservas de taller" grow
1379
1755
  * title="¿Necesitas mantención?" description="Tenemos la mejor red de talleres."
1380
1756
  * actionLabel="Agenda tu reserva aquí" actionHref="/taller/agendar">
1381
1757
  * <kf-button actions icon="calendar">Reservar hora</kf-button>
@@ -1388,8 +1764,10 @@ declare class ReserveBlock {
1388
1764
  readonly description: _angular_core.InputSignal<string | undefined>;
1389
1765
  readonly actionLabel: _angular_core.InputSignal<string | undefined>;
1390
1766
  readonly actionHref: _angular_core.InputSignal<string | undefined>;
1767
+ /** La invitación se estira para llenar la tarjeta (ej. junto a "Total de vehículos"). */
1768
+ readonly grow: _angular_core.InputSignalWithTransform<boolean, unknown>;
1391
1769
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ReserveBlock, never>;
1392
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<ReserveBlock, "kf-reserve-block", never, { "emptyLabel": { "alias": "emptyLabel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1770
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ReserveBlock, "kf-reserve-block", never, { "emptyLabel": { "alias": "emptyLabel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; "grow": { "alias": "grow"; "required": false; "isSignal": true; }; }, {}, never, ["[actions]"], true, never>;
1393
1771
  }
1394
1772
 
1395
1773
  /**
@@ -1490,16 +1868,30 @@ declare class Select {
1490
1868
  readonly value: _angular_core.InputSignal<string | undefined>;
1491
1869
  readonly placeholder: _angular_core.InputSignal<string>;
1492
1870
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
1871
+ /**
1872
+ * Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
1873
+ * se activa solo cuando `options().length` supera `searchThreshold()`. Si
1874
+ * el consumidor define `true`/`false` explícitamente, ese valor manda por
1875
+ * sobre el umbral automático.
1876
+ */
1877
+ readonly searchable: _angular_core.InputSignal<boolean | undefined>;
1878
+ readonly searchThreshold: _angular_core.InputSignal<number>;
1493
1879
  readonly valueChange: _angular_core.OutputEmitterRef<string>;
1494
1880
  private readonly instanceId;
1495
1881
  readonly triggerId: string;
1496
1882
  readonly listboxId: string;
1497
1883
  readonly isOpen: _angular_core.WritableSignal<boolean>;
1884
+ readonly searchQuery: _angular_core.WritableSignal<string>;
1885
+ readonly activeIndex: _angular_core.WritableSignal<number>;
1498
1886
  /** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
1499
1887
  readonly normalizedOptions: _angular_core.Signal<SelectOption[]>;
1500
1888
  readonly selectedOption: _angular_core.Signal<SelectOption | undefined>;
1889
+ readonly showSearch: _angular_core.Signal<boolean>;
1890
+ /** Opciones visibles tras aplicar el filtro de búsqueda (case/acento-insensitive). */
1891
+ readonly filteredOptions: _angular_core.Signal<SelectOption[]>;
1501
1892
  private readonly triggerRef;
1502
1893
  private readonly panelTemplate;
1894
+ private readonly searchInputRef;
1503
1895
  private readonly overlayService;
1504
1896
  private readonly viewContainerRef;
1505
1897
  private readonly destroyRef;
@@ -1514,11 +1906,107 @@ declare class Select {
1514
1906
  onDocumentEscape(): void;
1515
1907
  toggle(): void;
1516
1908
  select(option: SelectOption): void;
1909
+ onSearchInput(event: Event): void;
1910
+ /**
1911
+ * Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp
1912
+ * simple (sin wraparound): llegar al final o al principio de la lista se
1913
+ * queda ahí en vez de dar la vuelta — es el comportamiento más predecible
1914
+ * cuando se combina con un buscador que puede reducir la lista a 1 opción.
1915
+ */
1916
+ moveActive(delta: number): void;
1917
+ selectActive(): void;
1517
1918
  private attach;
1518
1919
  private detach;
1519
1920
  private getOrCreateOverlayRef;
1520
1921
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Select, never>;
1521
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1922
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1923
+ }
1924
+
1925
+ /** Tipo que consume `kf-sidebar-shell.navItems`. `icon` es requerido: en el
1926
+ * sidebar persistente el ícono es parte permanente del diseño. */
1927
+ interface SidebarNavItemData {
1928
+ icon: IconName;
1929
+ label: string;
1930
+ href: string;
1931
+ active?: boolean;
1932
+ }
1933
+ /**
1934
+ * Ítem de navegación de `kf-sidebar-shell` — ícono + label en fila, estado
1935
+ * activo, sin agrupación. Es el equivalente vertical/persistente del `<a>`
1936
+ * que arma `kf-portal-header` para su `<nav>` horizontal.
1937
+ *
1938
+ * `collapsed` se recibe como input explícito (no vía `:host-context`): el
1939
+ * componente no necesita saber cómo funciona el estado de colapso del
1940
+ * shell, solo qué hacer cuando `collapsed` es `true`.
1941
+ *
1942
+ * No sabe nada de routing — emite `href` como cualquier link y deja que el
1943
+ * consumidor intercepte el click para integrarlo con el Router.
1944
+ */
1945
+ declare class SidebarNavItem {
1946
+ readonly icon: _angular_core.InputSignal<IconName>;
1947
+ readonly label: _angular_core.InputSignal<string>;
1948
+ readonly href: _angular_core.InputSignal<string>;
1949
+ readonly active: _angular_core.InputSignalWithTransform<boolean, unknown>;
1950
+ /** Oculta el label y centra el ícono — lo controla `kf-sidebar-shell` según su propio estado de colapso. */
1951
+ readonly collapsed: _angular_core.InputSignalWithTransform<boolean, unknown>;
1952
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarNavItem, never>;
1953
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarNavItem, "kf-sidebar-nav-item", never, { "icon": { "alias": "icon"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "href": { "alias": "href"; "required": true; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1954
+ }
1955
+
1956
+ /**
1957
+ * Shell de layout con navegación lateral persistente — el complemento
1958
+ * vertical de `kf-portal-header` (navbar horizontal). Pensado para
1959
+ * portales tipo "aplicación interna" (Portal OTD, Servicio Star).
1960
+ *
1961
+ * `navItems` se recibe como array de datos (no como contenido proyectado):
1962
+ * el componente no sabe nada de routing, emite `navItemClick` con el ítem
1963
+ * clickeado y deja que el consumidor decida cómo navegar.
1964
+ *
1965
+ * Slots: `[kf-sidebar-brand]` (logo del rail expandido, visible en desktop
1966
+ * y en el panel mobile — es el MISMO rail), `[kf-sidebar-brand-collapsed]`
1967
+ * (reemplazo opcional cuando el rail está colapsado en desktop),
1968
+ * `[kf-sidebar-brand-mobile]` (logo de la topbar angosta de mobile, fondo
1969
+ * claro) y `[kf-sidebar-footer]` (tarjeta de usuario / acciones al pie).
1970
+ *
1971
+ * Botón de colapso: vive como último ítem de la propia lista de
1972
+ * navegación, no flotando en el borde del rail — mismo criterio de
1973
+ * tap-target grande y predecible que el resto de los ítems.
1974
+ *
1975
+ * Responsive: bajo los 900px el sidebar NO se reemplaza por un panel
1976
+ * distinto — es el MISMO rail el que pasa a `position: fixed` fuera de la
1977
+ * pantalla (`translateX(-100%)`) y se desliza a la vista (`translateX(0)`)
1978
+ * sobre un backdrop cuando `mobileOpen` es `true`. Una topbar angosta con
1979
+ * botón de hamburguesa reemplaza al rail en el flujo del documento
1980
+ * mientras el panel está cerrado.
1981
+ *
1982
+ * A propósito NO reutiliza `kf-drawer` (el panel lateral genérico de la
1983
+ * librería): `kf-drawer` tiene su propio fondo claro fijo y duplicaría el
1984
+ * árbol de navegación en un segundo lugar del DOM. Bug de contraste ya
1985
+ * corregido en el historial: los `kf-sidebar-nav-item`, con texto claro
1986
+ * pensado para el gradiente oscuro, quedaban invisibles sobre el fondo
1987
+ * blanco de `kf-drawer`. Acá hay un solo rail, un solo árbol de nav, que
1988
+ * cambia de posición según el viewport.
1989
+ */
1990
+ declare class SidebarShell {
1991
+ /** Ítems del menú del rail. */
1992
+ readonly navItems: _angular_core.InputSignal<SidebarNavItemData[]>;
1993
+ /** Estado de colapso del rail en desktop, two-way. Sin efecto bajo los 900px (ver estilos). */
1994
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
1995
+ /** Visibilidad del rail deslizado como panel en mobile, two-way. */
1996
+ readonly mobileOpen: _angular_core.ModelSignal<boolean>;
1997
+ /** Se emite al clickear un ítem — el consumidor decide cómo navegar (ej. `router.navigateByUrl`). */
1998
+ readonly navItemClick: _angular_core.OutputEmitterRef<SidebarNavItemData>;
1999
+ /** `true` bajo los 900px — sin esto, un rail dejado colapsado en desktop
2000
+ * seguía ocultando el label de cada ítem al abrir el panel en mobile. */
2001
+ private readonly isMobileViewport;
2002
+ /** Estado de colapso que efectivamente se le pasa a cada `kf-sidebar-nav-item`
2003
+ * y al propio botón de colapso. */
2004
+ protected readonly itemsCollapsed: _angular_core.Signal<boolean>;
2005
+ /** Cierra el panel mobile con Escape — mismo criterio que `Dropdown`/`Modal`/`Drawer`. */
2006
+ protected onEscape(): void;
2007
+ protected onNavItemClick(event: Event, item: SidebarNavItemData): void;
2008
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarShell, never>;
2009
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarShell, "kf-sidebar-shell", never, { "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "mobileOpen": { "alias": "mobileOpen"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "mobileOpen": "mobileOpenChange"; "navItemClick": "navItemClick"; }, never, ["[kf-sidebar-brand]", "[kf-sidebar-brand-collapsed]", "[kf-sidebar-footer]", "[kf-sidebar-brand-mobile]"], true, never>;
1522
2010
  }
1523
2011
 
1524
2012
  /** Forma del placeholder. */
@@ -1649,44 +2137,6 @@ declare class Tabs {
1649
2137
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<Tabs, "kf-tabs", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
1650
2138
  }
1651
2139
 
1652
- type TextAs = 'h1' | 'h2' | 'h3' | 'p' | 'span';
1653
- type TextVariant = 'title-lg' | 'title-md' | 'title-sm' | 'body' | 'body-muted' | 'caption';
1654
- type TextTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
1655
- /**
1656
- * Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
1657
- * texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
1658
- * ve). Antes de este componente, cada pantalla que consume la librería
1659
- * definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
1660
- * (título de página, subtítulo, texto secundario) — auditoría sobre
1661
- * `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
1662
- * home sin ningún paso de escala en común. `variant` resuelve eso contra
1663
- * la escala de `tokens/base.css` (`--kf-font-size-*`).
1664
- *
1665
- * `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
1666
- * escape hatch de color libre. Si un caso necesita un color que `tone` no
1667
- * cubre, es una discusión de design system, no una prop nueva acá.
1668
- *
1669
- * <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
1670
- * <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
1671
- * <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
1672
- */
1673
- declare class Text {
1674
- /** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
1675
- readonly as: _angular_core.InputSignal<TextAs>;
1676
- readonly variant: _angular_core.InputSignal<TextVariant>;
1677
- readonly tone: _angular_core.InputSignal<TextTone>;
1678
- /**
1679
- * Copia de `as` para uso dentro del template: `as` es palabra reservada
1680
- * en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
1681
- * no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
1682
- * atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
1683
- */
1684
- protected readonly tag: _angular_core.Signal<TextAs>;
1685
- protected hostClass(): string;
1686
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<Text, never>;
1687
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Text, "kf-text", never, { "as": { "alias": "as"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1688
- }
1689
-
1690
2140
  /**
1691
2141
  * Visual tone of a toast. Maps to the state tokens in
1692
2142
  * projects/ui/src/tokens/base.css. `loading` has no auto-dismiss and shows an
@@ -1784,6 +2234,34 @@ declare class ToastHost {
1784
2234
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ToastHost, "kf-toast-host", never, {}, {}, never, never, true, never>;
1785
2235
  }
1786
2236
 
2237
+ type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
2238
+ /**
2239
+ * Tooltip de hover/focus — reemplaza el atributo `title` nativo del
2240
+ * navegador (lento en aparecer, sin estilo, inconsistente entre
2241
+ * navegadores/SO) por un panel propio, mismo mecanismo de
2242
+ * `@angular/cdk/overlay` que ya usa `Dropdown`.
2243
+ *
2244
+ * Pensado para el caso "ícono sin label visible necesita comunicar qué
2245
+ * es" (ej. `kf-sidebar-nav-item` colapsado). No es two-way: a diferencia
2246
+ * de `Dropdown`, esto se abre/cierra solo con hover/focus, nunca por click.
2247
+ *
2248
+ * <a [kfTooltip]="label()" kfTooltipPosition="right">…</a>
2249
+ */
2250
+ declare class TooltipDirective {
2251
+ text: string;
2252
+ kfTooltipPosition: TooltipPosition;
2253
+ private readonly host;
2254
+ private readonly overlayService;
2255
+ private readonly viewContainerRef;
2256
+ private readonly injector;
2257
+ private overlayRef;
2258
+ protected show(): void;
2259
+ protected hide(): void;
2260
+ private positionsFor;
2261
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<TooltipDirective, never>;
2262
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<TooltipDirective, "[kfTooltip]", never, { "text": { "alias": "kfTooltip"; "required": false; }; "kfTooltipPosition": { "alias": "kfTooltipPosition"; "required": false; }; }, {}, never, never, true, never>;
2263
+ }
2264
+
1787
2265
  /**
1788
2266
  * Estado puntual mostrado junto a la identidad del vehículo (ej. "En ruta",
1789
2267
  * "Mantenimiento", "Fuera de servicio"). `tone` define el color semántico:
@@ -1842,5 +2320,5 @@ declare class ViewToggle {
1842
2320
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<ViewToggle, "kf-view-toggle", never, { "view": { "alias": "view"; "required": true; "isSignal": true; }; }, { "viewChange": "viewChange"; }, never, never, true, never>;
1843
2321
  }
1844
2322
 
1845
- export { ActionTile, AdminBadge, Alert, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, FolderTabsPanel, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, VehicleHeader, ViewToggle };
1846
- export type { AlertTone, BreadcrumbItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, DataTableCardRole, DataTableColumn, DataTableView, DividerOrientation, DropdownItemTone, FolderTabOption, IconName, IconSize, LinkVariant, MultiSelectOption, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, SegmentedOption, SkeletonVariant, SpinnerSize, TabOption, TextAs, TextTone, TextVariant, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, VehicleHeaderStatus, ViewToggleOption };
2323
+ export { ActionTile, AdminBadge, Alert, AuthLayout, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Drawer, DrawerLogout, DrawerSession, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, FolderTabsPanel, Footer, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, SidebarNavItem, SidebarShell, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, TooltipDirective, VehicleHeader, ViewToggle };
2324
+ export type { AlertTone, BreadcrumbItem, CompanySelectorMeta, ContentStateKind, ContentStateLoad, DataTableCardRole, DataTableColumn, DataTableView, DividerOrientation, DrawerSide, DropdownItemTone, DropdownPanelVariant, FolderTabOption, FooterLink, IconName, IconSize, LinkVariant, MultiSelectOption, PageHeaderTitleSize, PaginationItem, PortalHeaderAdminAction, PortalHeaderNavItem, SegmentedOption, SidebarNavItemData, SkeletonVariant, SpinnerSize, TabOption, TextAs, TextTone, TextVariant, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, TooltipPosition, VehicleHeaderStatus, ViewToggleOption };