@kaufmann-lab/ui 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, booleanAttribute, inject, computed, effect, Component, viewChild, model, output, ElementRef, Injector, signal, afterNextRender, ChangeDetectionStrategy, numberAttribute, contentChildren, DestroyRef, forwardRef, TemplateRef, contentChild, ViewContainerRef, HostListener, afterRenderEffect, untracked, isDevMode, viewChildren, Input as Input$1, Directive, Injectable } from '@angular/core';
2
+ import { input, booleanAttribute, inject, computed, effect, Component, viewChild, model, output, ElementRef, Injector, signal, afterNextRender, ChangeDetectionStrategy, numberAttribute, contentChildren, DestroyRef, forwardRef, TemplateRef, contentChild, ViewContainerRef, afterRenderEffect, untracked, Directive, HostListener, isDevMode, viewChildren, Input as Input$1, Injectable } from '@angular/core';
3
3
  import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
6
6
  import { Overlay, OverlayContainer } from '@angular/cdk/overlay';
7
7
  import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
8
8
  import { Subscription } from 'rxjs';
9
- import { DomSanitizer } from '@angular/platform-browser';
10
9
  import { normalizeForSearch, matchMediaSignal, handleArrowKeyNavigation } from '@kaufmann-lab/ui/shared';
10
+ import { DomSanitizer } from '@angular/platform-browser';
11
11
  import { FocusTrapFactory } from '@angular/cdk/a11y';
12
12
 
13
13
  /** Símbolo de trazo "activity" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
@@ -1236,10 +1236,23 @@ const TONE_ICON = {
1236
1236
  *
1237
1237
  * <kf-alert tone="warn" title="SOAT por vencer">Vence el 15/08.</kf-alert>
1238
1238
  * <kf-alert tone="ok" [(open)]="saved" dismissible>Pago registrado.</kf-alert>
1239
+ *
1240
+ * `variant="bar"` es una franja compacta de ancho completo, sin tarjeta: sin
1241
+ * esquinas ni bordes laterales (solo la línea superior e inferior del tono),
1242
+ * ícono, título y mensaje en una sola línea que envuelve si no entra. Mismos
1243
+ * tonos y mismo `role` (`alert` para `error`/`warn`, `status` para el resto)
1244
+ * que la variante `card`. Pensada para el borde de una página o de un panel
1245
+ * (ej. "Estás viendo datos de otro cliente"), donde una tarjeta con margen
1246
+ * sobra.
1247
+ *
1248
+ * <kf-alert variant="bar" tone="warn" title="Modo suplantación">Viendo como Ana Pérez.</kf-alert>
1239
1249
  */
1240
1250
  class Alert {
1241
1251
  tone = input('info', /* @ts-ignore */
1242
1252
  ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
1253
+ /** `card` (default) o `bar` (franja compacta de ancho completo). */
1254
+ variant = input('card', /* @ts-ignore */
1255
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
1243
1256
  /** Línea en negrita arriba del mensaje. Vacío deja solo el contenido proyectado. */
1244
1257
  title = input('', /* @ts-ignore */
1245
1258
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
@@ -1256,12 +1269,12 @@ class Alert {
1256
1269
  toneIcon = computed(() => TONE_ICON[this.tone()], /* @ts-ignore */
1257
1270
  ...(ngDevMode ? [{ debugName: "toneIcon" }] : /* istanbul ignore next */ []));
1258
1271
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Alert, deps: [], target: i0.ɵɵFactoryTarget.Component });
1259
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Alert, isStandalone: true, selector: "kf-alert", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, ngImport: i0, template: "@if (open()) {\n <div class=\"kf-alert\" [class]=\"'kf-alert--' + tone()\" [attr.role]=\"ariaRole()\">\n <kf-icon class=\"kf-alert__icon\" [name]=\"toneIcon()\" size=\"sm\" />\n <div class=\"kf-alert__body\">\n @if (title()) {\n <p class=\"kf-alert__title\">{{ title() }}</p>\n }\n <div class=\"kf-alert__message\"><ng-content /></div>\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-alert__close\"\n aria-label=\"Cerrar aviso\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-alert{display:flex;align-items:flex-start;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);font-size:.9rem}.kf-alert--info{background:var(--kf-color-info-soft);border-color:var(--kf-color-info);color:var(--kf-color-info-strong)}.kf-alert--ok{background:var(--kf-color-success-soft);border-color:var(--kf-color-success);color:var(--kf-color-success-strong)}.kf-alert--warn{background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning);color:var(--kf-color-warning-strong)}.kf-alert--error{background:var(--kf-color-error-soft);border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-alert__body{flex:1;min-width:0}.kf-alert__title{margin:0;font-family:var(--kf-font-title);font-weight:700}.kf-alert__message{color:inherit}.kf-alert__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:inherit;border-radius:var(--kf-radius-xs);cursor:pointer;opacity:.7}.kf-alert__close:hover{opacity:1}.kf-alert__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
1272
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Alert, isStandalone: true, selector: "kf-alert", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, ngImport: i0, template: "@if (open()) {\n <div\n class=\"kf-alert\"\n [class]=\"'kf-alert--' + tone()\"\n [class.kf-alert--bar]=\"variant() === 'bar'\"\n [attr.role]=\"ariaRole()\"\n >\n <kf-icon class=\"kf-alert__icon\" [name]=\"toneIcon()\" size=\"sm\" />\n <div class=\"kf-alert__body\">\n @if (title()) {\n <p class=\"kf-alert__title\">{{ title() }}</p>\n }\n <div class=\"kf-alert__message\"><ng-content /></div>\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-alert__close\"\n aria-label=\"Cerrar aviso\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-alert{display:flex;align-items:flex-start;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);font-size:.9rem}.kf-alert--info{background:var(--kf-color-info-soft);border-color:var(--kf-color-info);color:var(--kf-color-info-strong)}.kf-alert--ok{background:var(--kf-color-success-soft);border-color:var(--kf-color-success);color:var(--kf-color-success-strong)}.kf-alert--warn{background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning);color:var(--kf-color-warning-strong)}.kf-alert--error{background:var(--kf-color-error-soft);border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-alert__body{flex:1;min-width:0}.kf-alert__title{margin:0;font-family:var(--kf-font-title);font-weight:700}.kf-alert__message{color:inherit}.kf-alert__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:inherit;border-radius:var(--kf-radius-xs);cursor:pointer;opacity:.7}.kf-alert__close:hover{opacity:1}.kf-alert__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-alert--bar{align-items:center;padding:var(--kf-space-xs) var(--kf-space-md);border-width:1px 0;border-radius:0;font-size:.875rem}.kf-alert--bar .kf-alert__body{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--kf-space-xs)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
1260
1273
  }
1261
1274
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Alert, decorators: [{
1262
1275
  type: Component,
1263
- args: [{ selector: 'kf-alert', imports: [Icon], template: "@if (open()) {\n <div class=\"kf-alert\" [class]=\"'kf-alert--' + tone()\" [attr.role]=\"ariaRole()\">\n <kf-icon class=\"kf-alert__icon\" [name]=\"toneIcon()\" size=\"sm\" />\n <div class=\"kf-alert__body\">\n @if (title()) {\n <p class=\"kf-alert__title\">{{ title() }}</p>\n }\n <div class=\"kf-alert__message\"><ng-content /></div>\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-alert__close\"\n aria-label=\"Cerrar aviso\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-alert{display:flex;align-items:flex-start;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);font-size:.9rem}.kf-alert--info{background:var(--kf-color-info-soft);border-color:var(--kf-color-info);color:var(--kf-color-info-strong)}.kf-alert--ok{background:var(--kf-color-success-soft);border-color:var(--kf-color-success);color:var(--kf-color-success-strong)}.kf-alert--warn{background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning);color:var(--kf-color-warning-strong)}.kf-alert--error{background:var(--kf-color-error-soft);border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-alert__body{flex:1;min-width:0}.kf-alert__title{margin:0;font-family:var(--kf-font-title);font-weight:700}.kf-alert__message{color:inherit}.kf-alert__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:inherit;border-radius:var(--kf-radius-xs);cursor:pointer;opacity:.7}.kf-alert__close:hover{opacity:1}.kf-alert__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
1264
- }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }] } });
1276
+ args: [{ selector: 'kf-alert', imports: [Icon], template: "@if (open()) {\n <div\n class=\"kf-alert\"\n [class]=\"'kf-alert--' + tone()\"\n [class.kf-alert--bar]=\"variant() === 'bar'\"\n [attr.role]=\"ariaRole()\"\n >\n <kf-icon class=\"kf-alert__icon\" [name]=\"toneIcon()\" size=\"sm\" />\n <div class=\"kf-alert__body\">\n @if (title()) {\n <p class=\"kf-alert__title\">{{ title() }}</p>\n }\n <div class=\"kf-alert__message\"><ng-content /></div>\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-alert__close\"\n aria-label=\"Cerrar aviso\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-alert{display:flex;align-items:flex-start;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);font-size:.9rem}.kf-alert--info{background:var(--kf-color-info-soft);border-color:var(--kf-color-info);color:var(--kf-color-info-strong)}.kf-alert--ok{background:var(--kf-color-success-soft);border-color:var(--kf-color-success);color:var(--kf-color-success-strong)}.kf-alert--warn{background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning);color:var(--kf-color-warning-strong)}.kf-alert--error{background:var(--kf-color-error-soft);border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-alert__body{flex:1;min-width:0}.kf-alert__title{margin:0;font-family:var(--kf-font-title);font-weight:700}.kf-alert__message{color:inherit}.kf-alert__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:inherit;border-radius:var(--kf-radius-xs);cursor:pointer;opacity:.7}.kf-alert__close:hover{opacity:1}.kf-alert__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-alert--bar{align-items:center;padding:var(--kf-space-xs) var(--kf-space-md);border-width:1px 0;border-radius:0;font-size:.875rem}.kf-alert--bar .kf-alert__body{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--kf-space-xs)}\n"] }]
1277
+ }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }] } });
1265
1278
 
1266
1279
  /**
1267
1280
  * Fila clickeable de una lista de pendientes/alertas: ícono circular con
@@ -1783,6 +1796,16 @@ function keyboardTarget(key, iso) {
1783
1796
  * <kf-button>Guardar</kf-button>
1784
1797
  * </div>
1785
1798
  * </kf-card>
1799
+ *
1800
+ * `loading` (default `false`): cubre el cuerpo con un bloque de carga animado
1801
+ * sin sacar el contenido del layout (queda invisible y fuera del foco y de
1802
+ * lectores de pantalla), así la card conserva exactamente sus dimensiones. Los
1803
+ * slots `[kf-card-media]` y `[kf-card-actions]` también se ocultan. Marca el
1804
+ * host con `aria-busy="true"`. Para una forma más fiel (líneas separadas) usa
1805
+ * `kf-skeleton` dentro de la card, o la card propia del dominio con su
1806
+ * `loading` (`kf-stat-card`, `kf-metric-card`).
1807
+ *
1808
+ * <kf-card [loading]="isLoading()"><h3>{{ title() }}</h3><p>{{ text() }}</p></kf-card>
1786
1809
  */
1787
1810
  class Card {
1788
1811
  /** Quita la sombra — para cards anidadas dentro de otra superficie ya elevada. */
@@ -1790,20 +1813,24 @@ class Card {
1790
1813
  /** Espacio entre los hijos directos del cuerpo. `md` es el default. */
1791
1814
  gap = input('md', /* @ts-ignore */
1792
1815
  ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
1816
+ /** Cubre el cuerpo con un bloque de carga conservando las dimensiones. */
1817
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1793
1818
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Card, deps: [], target: i0.ɵɵFactoryTarget.Component });
1794
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: Card, isStandalone: true, selector: "kf-card", inputs: { flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-card--flush": "flush()", "class.kf-card--gap-xs": "gap() === 'xs'", "class.kf-card--gap-sm": "gap() === 'sm'", "class.kf-card--gap-md": "gap() === 'md'", "class.kf-card--gap-lg": "gap() === 'lg'", "class.kf-card--gap-xl": "gap() === 'xl'" } }, ngImport: i0, template: "<ng-content select=\"[kf-card-media]\" />\n<div class=\"kf-card__body\">\n <ng-content />\n</div>\n<ng-content select=\"[kf-card-actions]\" />\n", styles: [":host{display:flex;flex-direction:column;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-xs);overflow:hidden}:host.kf-card--flush{box-shadow:none}.kf-card__body{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-md);flex:1 1 auto}:host.kf-card--gap-xs .kf-card__body{gap:var(--kf-space-xs)}:host.kf-card--gap-sm .kf-card__body{gap:var(--kf-space-sm)}:host.kf-card--gap-md .kf-card__body{gap:var(--kf-space-md)}:host.kf-card--gap-lg .kf-card__body{gap:var(--kf-space-lg)}:host.kf-card--gap-xl .kf-card__body{gap:var(--kf-space-xl)}::ng-deep [kf-card-media]{display:block;inline-size:100%;block-size:auto;object-fit:cover}::ng-deep [kf-card-actions]{display:flex;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}\n"] });
1819
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: Card, isStandalone: true, selector: "kf-card", inputs: { flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-card--flush": "flush()", "class.kf-card--loading": "loading()", "attr.aria-busy": "loading() ? 'true' : null", "class.kf-card--gap-xs": "gap() === 'xs'", "class.kf-card--gap-sm": "gap() === 'sm'", "class.kf-card--gap-md": "gap() === 'md'", "class.kf-card--gap-lg": "gap() === 'lg'", "class.kf-card--gap-xl": "gap() === 'xl'" } }, ngImport: i0, template: "<ng-content select=\"[kf-card-media]\" />\n<div\n class=\"kf-card__body\"\n [class.kf-card__body--loading]=\"loading()\"\n [class.kf-bone-overlay]=\"loading()\"\n>\n <ng-content />\n</div>\n<ng-content select=\"[kf-card-actions]\" />\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;flex-direction:column;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-xs);overflow:hidden}:host.kf-card--flush{box-shadow:none}.kf-card__body{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-md);flex:1 1 auto}:host.kf-card--gap-xs .kf-card__body{gap:var(--kf-space-xs)}:host.kf-card--gap-sm .kf-card__body{gap:var(--kf-space-sm)}:host.kf-card--gap-md .kf-card__body{gap:var(--kf-space-md)}:host.kf-card--gap-lg .kf-card__body{gap:var(--kf-space-lg)}:host.kf-card--gap-xl .kf-card__body{gap:var(--kf-space-xl)}::ng-deep [kf-card-media]{display:block;inline-size:100%;block-size:auto;object-fit:cover}::ng-deep [kf-card-actions]{display:flex;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-card__body--loading{position:relative;visibility:hidden}.kf-card__body--loading:after{content:\"\";visibility:visible;position:absolute;inset:var(--kf-space-md);border-radius:var(--kf-radius-sm)}:host.kf-card--loading ::ng-deep [kf-card-media],:host.kf-card--loading ::ng-deep [kf-card-actions]{visibility:hidden}\n"] });
1795
1820
  }
1796
1821
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Card, decorators: [{
1797
1822
  type: Component,
1798
1823
  args: [{ selector: 'kf-card', host: {
1799
1824
  '[class.kf-card--flush]': 'flush()',
1825
+ '[class.kf-card--loading]': 'loading()',
1826
+ '[attr.aria-busy]': "loading() ? 'true' : null",
1800
1827
  '[class.kf-card--gap-xs]': "gap() === 'xs'",
1801
1828
  '[class.kf-card--gap-sm]': "gap() === 'sm'",
1802
1829
  '[class.kf-card--gap-md]': "gap() === 'md'",
1803
1830
  '[class.kf-card--gap-lg]': "gap() === 'lg'",
1804
1831
  '[class.kf-card--gap-xl]': "gap() === 'xl'",
1805
- }, template: "<ng-content select=\"[kf-card-media]\" />\n<div class=\"kf-card__body\">\n <ng-content />\n</div>\n<ng-content select=\"[kf-card-actions]\" />\n", styles: [":host{display:flex;flex-direction:column;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-xs);overflow:hidden}:host.kf-card--flush{box-shadow:none}.kf-card__body{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-md);flex:1 1 auto}:host.kf-card--gap-xs .kf-card__body{gap:var(--kf-space-xs)}:host.kf-card--gap-sm .kf-card__body{gap:var(--kf-space-sm)}:host.kf-card--gap-md .kf-card__body{gap:var(--kf-space-md)}:host.kf-card--gap-lg .kf-card__body{gap:var(--kf-space-lg)}:host.kf-card--gap-xl .kf-card__body{gap:var(--kf-space-xl)}::ng-deep [kf-card-media]{display:block;inline-size:100%;block-size:auto;object-fit:cover}::ng-deep [kf-card-actions]{display:flex;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}\n"] }]
1806
- }], propDecorators: { flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }] } });
1832
+ }, template: "<ng-content select=\"[kf-card-media]\" />\n<div\n class=\"kf-card__body\"\n [class.kf-card__body--loading]=\"loading()\"\n [class.kf-bone-overlay]=\"loading()\"\n>\n <ng-content />\n</div>\n<ng-content select=\"[kf-card-actions]\" />\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;flex-direction:column;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-xs);overflow:hidden}:host.kf-card--flush{box-shadow:none}.kf-card__body{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-md);flex:1 1 auto}:host.kf-card--gap-xs .kf-card__body{gap:var(--kf-space-xs)}:host.kf-card--gap-sm .kf-card__body{gap:var(--kf-space-sm)}:host.kf-card--gap-md .kf-card__body{gap:var(--kf-space-md)}:host.kf-card--gap-lg .kf-card__body{gap:var(--kf-space-lg)}:host.kf-card--gap-xl .kf-card__body{gap:var(--kf-space-xl)}::ng-deep [kf-card-media]{display:block;inline-size:100%;block-size:auto;object-fit:cover}::ng-deep [kf-card-actions]{display:flex;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-card__body--loading{position:relative;visibility:hidden}.kf-card__body--loading:after{content:\"\";visibility:visible;position:absolute;inset:var(--kf-space-md);border-radius:var(--kf-radius-sm)}:host.kf-card--loading ::ng-deep [kf-card-media],:host.kf-card--loading ::ng-deep [kf-card-actions]{visibility:hidden}\n"] }]
1833
+ }], propDecorators: { flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
1807
1834
 
1808
1835
  /**
1809
1836
  * Slide individual de un `kf-carousel`. Es un contenedor mínimo: solo existe
@@ -2029,7 +2056,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
2029
2056
  }, template: "@if (dot()) {\n <span\n class=\"kf-chip__dot\"\n [class.kf-chip__dot--ok]=\"tone() === 'ok'\"\n [class.kf-chip__dot--warn]=\"tone() === 'warn'\"\n [class.kf-chip__dot--error]=\"tone() === 'error'\"\n [class.kf-chip__dot--neutral]=\"tone() === 'neutral'\"\n ></span>\n}\n<span class=\"kf-chip__label\"><ng-content /></span>\n", styles: [":host{display:inline-flex;align-items:center;gap:var(--kf-space-xs);padding:var(--kf-space-xs) var(--kf-space-sm);border-radius:var(--kf-radius-pill);border:1px solid transparent;font:inherit;font-size:.875em;line-height:1;color:var(--kf-color-text);background:var(--kf-color-surface);border-color:var(--kf-color-border);white-space:nowrap}:host(.kf-chip--ok){background:var(--kf-color-success-soft);border-color:var(--kf-color-success-soft);color:var(--kf-color-text)}:host(.kf-chip--warn){background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning-soft);color:var(--kf-color-text)}:host(.kf-chip--error){background:var(--kf-color-error-soft);border-color:var(--kf-color-error-soft);color:var(--kf-color-text)}:host(.kf-chip--neutral){background:var(--kf-color-surface);border-color:var(--kf-color-border);color:var(--kf-color-text)}.kf-chip__dot{inline-size:.5em;block-size:.5em;border-radius:var(--kf-radius-pill);flex:none;background:var(--kf-color-text-muted)}.kf-chip__label{display:inline-block}.kf-chip__dot--ok{background:var(--kf-color-success)}.kf-chip__dot--warn{background:var(--kf-color-warning)}.kf-chip__dot--error{background:var(--kf-color-error)}.kf-chip__dot--neutral{background:var(--kf-color-text-muted)}\n"] }]
2030
2057
  }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
2031
2058
 
2032
- let nextId$4 = 0;
2059
+ let nextId$5 = 0;
2033
2060
  /**
2034
2061
  * Bloque colapsable genérico: un header clickeable (título + chevron) que
2035
2062
  * expande/colapsa contenido proyectado libre, sin saber nada de lo que hay
@@ -2056,7 +2083,7 @@ class Collapsible {
2056
2083
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
2057
2084
  expanded = model(false, /* @ts-ignore */
2058
2085
  ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
2059
- instanceId = `kf-collapsible-${nextId$4++}`;
2086
+ instanceId = `kf-collapsible-${nextId$5++}`;
2060
2087
  triggerId = `${this.instanceId}-trigger`;
2061
2088
  panelId = `${this.instanceId}-panel`;
2062
2089
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Collapsible, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2067,6 +2094,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
2067
2094
  args: [{ selector: 'kf-collapsible', template: "<button\n type=\"button\"\n class=\"kf-collapsible__trigger\"\n [id]=\"triggerId\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"panelId\"\n (click)=\"expanded.update((value) => !value)\"\n>\n <span class=\"kf-collapsible__title\">{{ title() }}</span>\n <svg\n class=\"kf-collapsible__chevron\"\n [class.kf-collapsible__chevron--expanded]=\"expanded()\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 6L8 10L12 6\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n</button>\n<div\n class=\"kf-collapsible__panel\"\n [class.kf-collapsible__panel--expanded]=\"expanded()\"\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"triggerId\"\n>\n <div class=\"kf-collapsible__body\">\n <ng-content />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface)}.kf-collapsible__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-weight:600;color:var(--kf-color-text);background:none;border:none;padding:var(--kf-space-sm) var(--kf-space-md);cursor:pointer;text-align:left}.kf-collapsible__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-collapsible__title{flex:1}.kf-collapsible__chevron{flex:none;color:var(--kf-color-text-muted);transition:transform .18s ease}.kf-collapsible__chevron--expanded{transform:rotate(180deg)}.kf-collapsible__panel{overflow:hidden;max-height:0;opacity:0;transition:max-height .24s ease,opacity .18s ease}.kf-collapsible__panel--expanded{max-height:2000px;opacity:1}.kf-collapsible__body{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md)}\n"] }]
2068
2095
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }] } });
2069
2096
 
2097
+ /**
2098
+ * Escucha `Escape` en fase de captura sobre `document`, antes que cualquier
2099
+ * listener de burbuja de un contenedor (`kf-drawer`, `kf-modal`).
2100
+ *
2101
+ * Un panel flotante (`kf-select`, `kf-multi-select`, `kf-dropdown`) debe
2102
+ * cerrarse primero y dejar su contenedor abierto. Un listener de burbuja no
2103
+ * basta: el orden entre dos `@HostListener('document:keydown.escape')` es el
2104
+ * de creación de los componentes — el contenedor se crea antes que su
2105
+ * contenido — así que el contenedor vería la tecla sin consumir y también se
2106
+ * cerraría. En captura el panel va siempre primero y marca la tecla con
2107
+ * `preventDefault()`, que es lo que el contenedor revisa.
2108
+ *
2109
+ * `onEscape` debe devolver `true` solo si había algo que cerrar: únicamente
2110
+ * entonces se consume la tecla. Devuelve la función que retira el listener.
2111
+ */
2112
+ function listenEscapeCapture(doc, onEscape) {
2113
+ const handler = (event) => {
2114
+ if (event.key !== 'Escape' || event.defaultPrevented) {
2115
+ return;
2116
+ }
2117
+ if (onEscape()) {
2118
+ event.preventDefault();
2119
+ }
2120
+ };
2121
+ doc.addEventListener('keydown', handler, true);
2122
+ return () => doc.removeEventListener('keydown', handler, true);
2123
+ }
2124
+
2070
2125
  /**
2071
2126
  * Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
2072
2127
  * (`<ng-template>`, capturada vía `TemplateRef`) — `Dropdown` la conecta al
@@ -2148,7 +2203,9 @@ class Dropdown {
2148
2203
  this.detach();
2149
2204
  }
2150
2205
  });
2206
+ const stopEscape = listenEscapeCapture(inject(DOCUMENT), () => this.onDocumentEscape());
2151
2207
  this.destroyRef.onDestroy(() => {
2208
+ stopEscape();
2152
2209
  this.overlaySubscription?.unsubscribe();
2153
2210
  this.overlayRef?.dispose();
2154
2211
  });
@@ -2157,10 +2214,17 @@ class Dropdown {
2157
2214
  * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo
2158
2215
  * ve teclas con el foco DENTRO del panel. Si el foco queda en el botón
2159
2216
  * disparador (caso común), `keydownEvents()` nunca dispara — este
2160
- * listener a nivel de documento cubre ese caso.
2217
+ * listener a nivel de documento cubre ese caso. Va en fase de captura
2218
+ * (ver `listenEscapeCapture`) y consume la tecla solo si había un menú
2219
+ * abierto, así un `kf-drawer`/`kf-modal` contenedor no se cierra con el
2220
+ * mismo Escape. Devuelve si cerró algo.
2161
2221
  */
2162
2222
  onDocumentEscape() {
2223
+ if (!this.open()) {
2224
+ return false;
2225
+ }
2163
2226
  this.open.set(false);
2227
+ return true;
2164
2228
  }
2165
2229
  attach() {
2166
2230
  const panel = this.panel();
@@ -2231,15 +2295,12 @@ class Dropdown {
2231
2295
  return this.overlayRef;
2232
2296
  }
2233
2297
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
2234
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Dropdown, isStandalone: true, selector: "kf-dropdown", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" } }, queries: [{ propertyName: "panel", first: true, predicate: DropdownPanel, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:inline-block}\n"] });
2298
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Dropdown, isStandalone: true, selector: "kf-dropdown", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, queries: [{ propertyName: "panel", first: true, predicate: DropdownPanel, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:inline-block}\n"] });
2235
2299
  }
2236
2300
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dropdown, decorators: [{
2237
2301
  type: Component,
2238
2302
  args: [{ selector: 'kf-dropdown', template: `<ng-content />`, styles: [":host{display:inline-block}\n"] }]
2239
- }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], panel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DropdownPanel), { isSignal: true }] }], onDocumentEscape: [{
2240
- type: HostListener,
2241
- args: ['document:keydown.escape']
2242
- }] } });
2303
+ }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], panel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DropdownPanel), { isSignal: true }] }] } });
2243
2304
 
2244
2305
  /**
2245
2306
  * Popover de "Datos de contacto" de una persona (asesor, ejecutivo — nombre
@@ -2442,177 +2503,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
2442
2503
  args: [{ selector: 'kf-skeleton', template: "<span\n class=\"kf-skeleton\"\n [class]=\"'kf-skeleton--' + variant()\"\n aria-hidden=\"true\"\n [style.width]=\"width()\"\n [style.height]=\"height() || null\"\n></span>\n", styles: [":host{display:block}.kf-skeleton{display:block;background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-skeleton-shimmer 1.4s ease infinite}.kf-skeleton--text{height:14px;border-radius:var(--kf-radius-xs)}.kf-skeleton--rect{height:80px;border-radius:var(--kf-radius-sm)}.kf-skeleton--circle{aspect-ratio:1 / 1;border-radius:var(--kf-radius-pill)}@keyframes kf-skeleton-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-skeleton{animation:none;background:var(--kf-color-border)}}\n"] }]
2443
2504
  }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
2444
2505
 
2445
- /**
2446
- * Wrapper de los cuatro estados que toda sección tiene: cargando, con datos,
2447
- * vacía y con error. Un componente en vez de tres `@if` sueltos, porque es
2448
- * siempre el mismo slot el que va rotando entre los cuatro.
2449
- *
2450
- * Con `state="ready"` proyecta el contenido real y desaparece; en los otros
2451
- * tres dibuja el placeholder correspondiente y el contenido no se instancia.
2452
- * No inventa visuales propios: `loading` usa `kf-skeleton` (o `kf-spinner` con
2453
- * `load="spinner"`), y `empty`/`error` usan `kf-empty-state`.
2454
- *
2455
- * Las acciones (reintentar, crear) van en el slot `actions` — se ignoran
2456
- * mientras carga, porque no hay nada que reintentar todavía.
2457
- *
2458
- * <kf-content-state [state]="state()" title="No pudimos cargar tus vehículos"
2459
- * subtitle="Revisá tu conexión e intentá de nuevo.">
2460
- * <kf-button actions (click)="retry()">Reintentar</kf-button>
2461
- * <kf-data-table [columns]="cols" [rows]="rows()" />
2462
- * </kf-content-state>
2463
- */
2464
- class ContentState {
2465
- state = input.required(/* @ts-ignore */
2466
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
2467
- /** Forma del estado de carga: silueta (default) o spinner. */
2468
- load = input('skeleton', /* @ts-ignore */
2469
- ...(ngDevMode ? [{ debugName: "load" }] : /* istanbul ignore next */ []));
2470
- /** Título de `empty`/`error`. Ignorado mientras carga. */
2471
- title = input('', /* @ts-ignore */
2472
- ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
2473
- subtitle = input('', /* @ts-ignore */
2474
- ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
2475
- /** Texto al lado del spinner cuando `load="spinner"`. */
2476
- loadingLabel = input('Cargando…', /* @ts-ignore */
2477
- ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
2478
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, deps: [], target: i0.ɵɵFactoryTarget.Component });
2479
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentState, isStandalone: true, selector: "kf-content-state", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, load: { classPropertyName: "load", publicName: "load", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: EmptyState, selector: "kf-empty-state", inputs: ["icon", "tone", "variant", "title"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
2480
- }
2481
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, decorators: [{
2482
- type: Component,
2483
- args: [{ selector: 'kf-content-state', imports: [EmptyState, Skeleton, Spinner], template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
2484
- }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], load: [{ type: i0.Input, args: [{ isSignal: true, alias: "load", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }] } });
2485
-
2486
- const RAW_BASE64_MIME = {
2487
- image: 'image/jpeg',
2488
- video: 'video/mp4',
2489
- pdf: 'application/pdf',
2490
- };
2491
- /** Solo contenido ya en memoria: un `<iframe>` con una URL remota arbitraria podría cargar cualquier página. */
2492
- const LOCAL_PDF_SRC = /^(blob:|data:application\/pdf[;,])/i;
2493
- let contentViewerId = 0;
2494
- /**
2495
- * Visor a pantalla completa para ampliar una pieza de contenido: imagen,
2496
- * video o PDF (`type="pdf"`, renderizado por el visor nativo del navegador).
2497
- * Mismo mecanismo que `kf-modal` (el `<dialog>` nativo resuelve backdrop,
2498
- * atrapado de foco, top-layer y `Escape`), pero sin panel/título/footer:
2499
- * el contenido ocupa toda la pantalla sobre un backdrop oscuro.
2500
- *
2501
- * `src` acepta lo que venga del backend sin que el consumidor tenga que
2502
- * normalizarlo primero: una URL (`http(s)://`), un data URI ya armado
2503
- * (`data:...`), o base64 crudo (se le antepone el prefijo `data:` según
2504
- * `type`, asumiendo JPEG para imagen, MP4 para video y PDF para pdf). Un PDF
2505
- * solo acepta contenido local (`blob:`, data URI o base64), nunca una URL remota.
2506
- *
2507
- * Lo proyectado con el atributo `actions` (p. ej. un botón de descarga) va en
2508
- * la barra superior, junto al título.
2509
- *
2510
- * <kf-content-viewer [(open)]="viewerOpen" [src]="viewerSrc()" alt="Foto del vehículo" />
2511
- * <kf-content-viewer [(open)]="viewerOpen" type="video" [src]="viewerSrc()" title="Inspección" />
2512
- * <kf-content-viewer [(open)]="viewerOpen" type="pdf" [src]="blobUrl()" title="Contrato">
2513
- * <kf-button actions variant="surface" icon="download" (click)="save()">Descargar</kf-button>
2514
- * </kf-content-viewer>
2515
- */
2516
- class ContentViewer {
2517
- type = input('image', /* @ts-ignore */
2518
- ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
2519
- /** URL, data URI, o base64 crudo — ver JSDoc de la clase. */
2520
- src = input(null, /* @ts-ignore */
2521
- ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
2522
- /** Texto alternativo cuando `type="image"`. */
2523
- alt = input('', /* @ts-ignore */
2524
- ...(ngDevMode ? [{ debugName: "alt" }] : /* istanbul ignore next */ []));
2525
- title = input('', /* @ts-ignore */
2526
- ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
2527
- /** Visibilidad, two-way: `[(open)]="viewerOpen"`. */
2528
- open = model(false, /* @ts-ignore */
2529
- ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
2530
- /** Permite cerrar con el botón, `Escape` o clic en el backdrop. */
2531
- dismissible = input(true, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2532
- titleId = `kf-content-viewer-title-${contentViewerId++}`;
2533
- /** Detecta si `src` ya es una URL o un data URI armado; si no, asume
2534
- * base64 crudo y le antepone el prefijo `data:` correspondiente a `type`
2535
- * — el backend a veces manda una cosa, a veces la otra. */
2536
- resolvedSrc = computed(() => {
2537
- const raw = this.src();
2538
- if (!raw)
2539
- return null;
2540
- if (/^(https?:|data:|blob:)/i.test(raw))
2541
- return raw;
2542
- return `data:${RAW_BASE64_MIME[this.type()]};base64,${raw}`;
2543
- }, /* @ts-ignore */
2544
- ...(ngDevMode ? [{ debugName: "resolvedSrc" }] : /* istanbul ignore next */ []));
2545
- pdfSrc = computed(() => {
2546
- const src = this.resolvedSrc();
2547
- if (this.type() !== 'pdf' || !src || !LOCAL_PDF_SRC.test(src))
2548
- return null;
2549
- return this.sanitizer.bypassSecurityTrustResourceUrl(src);
2550
- }, /* @ts-ignore */
2551
- ...(ngDevMode ? [{ debugName: "pdfSrc" }] : /* istanbul ignore next */ []));
2552
- fallbackLabel = computed(() => ({ image: 'Visor de imagen', video: 'Visor de video', pdf: 'Visor de documento' })[this.type()], /* @ts-ignore */
2553
- ...(ngDevMode ? [{ debugName: "fallbackLabel" }] : /* istanbul ignore next */ []));
2554
- dialog = viewChild('dialog', /* @ts-ignore */
2555
- ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
2556
- document = inject(DOCUMENT);
2557
- sanitizer = inject(DomSanitizer);
2558
- constructor() {
2559
- afterRenderEffect(() => {
2560
- const el = this.dialog()?.nativeElement;
2561
- if (!el) {
2562
- return;
2563
- }
2564
- if (this.open() && !el.open) {
2565
- // `showModal` no existe en jsdom (tests) ni en entornos sin
2566
- // top-layer; el atributo `open` lo deja visible, aunque no modal.
2567
- if (typeof el.showModal === 'function') {
2568
- el.showModal();
2569
- }
2570
- else {
2571
- el.open = true;
2572
- }
2573
- }
2574
- else if (!this.open() && el.open) {
2575
- if (typeof el.close === 'function') {
2576
- el.close();
2577
- }
2578
- else {
2579
- el.open = false;
2580
- }
2581
- }
2582
- // `<dialog>` inertiza el resto de la página pero no frena su scroll.
2583
- this.document.body.style.overflow = this.open() ? 'hidden' : '';
2584
- });
2585
- inject(DestroyRef).onDestroy(() => {
2586
- this.document.body.style.overflow = '';
2587
- });
2588
- }
2589
- /** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
2590
- onCancel(event) {
2591
- if (!this.dismissible()) {
2592
- event.preventDefault();
2593
- }
2594
- }
2595
- /**
2596
- * Clic en el backdrop. Un clic en la zona del backdrop tiene como `target` al
2597
- * propio `<dialog>` (el contenido vive en el panel de adentro), así que basta
2598
- * comparar contra él — no hace falta cortar la propagación en el panel.
2599
- */
2600
- onHostClick(event) {
2601
- if (this.dismissible() && event.target === this.dialog()?.nativeElement) {
2602
- this.open.set(false);
2603
- }
2604
- }
2605
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
2606
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentViewer, isStandalone: true, selector: "kf-content-viewer", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "click": "onHostClick($event)" } }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
2607
- }
2608
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, decorators: [{
2609
- type: Component,
2610
- args: [{ selector: 'kf-content-viewer', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"] }]
2611
- }], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], onHostClick: [{
2612
- type: HostListener,
2613
- args: ['click', ['$event']]
2614
- }] } });
2615
-
2616
2506
  /**
2617
2507
  * Label superior compartido por todos los campos de formulario del design
2618
2508
  * system (páginas 11-12: Text Field, Select, Multiselect) — uppercase,
@@ -2755,7 +2645,7 @@ class SelectOverlay {
2755
2645
  }
2756
2646
  }
2757
2647
 
2758
- let nextId$3 = 0;
2648
+ let nextId$4 = 0;
2759
2649
  /** Ancho mínimo del panel flotante en `variant="compact"` — el trigger de
2760
2650
  * ese modo puede medir menos de 45px (ej. "10" + caret), y sin este piso
2761
2651
  * la opción quedaría apretada contra ese mismo ancho angosto. 72px es lo
@@ -2873,7 +2763,7 @@ class Select {
2873
2763
  panelWidth = input('trigger', /* @ts-ignore */
2874
2764
  ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
2875
2765
  valueChange = output();
2876
- instanceId = `kf-select-${nextId$3++}`;
2766
+ instanceId = `kf-select-${nextId$4++}`;
2877
2767
  triggerId = `${this.instanceId}-trigger`;
2878
2768
  listboxId = `${this.instanceId}-listbox`;
2879
2769
  errorId = `${this.instanceId}-error`;
@@ -2959,16 +2849,28 @@ class Select {
2959
2849
  untracked(() => this.focusOption(this.activeIndex()));
2960
2850
  }
2961
2851
  });
2962
- inject(DestroyRef).onDestroy(() => this.overlay.dispose());
2852
+ const stopEscape = listenEscapeCapture(this.document, () => this.onDocumentEscape());
2853
+ inject(DestroyRef).onDestroy(() => {
2854
+ stopEscape();
2855
+ this.overlay.dispose();
2856
+ });
2963
2857
  }
2964
2858
  /**
2965
- * Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo ve
2966
- * teclas presionadas con el foco DENTRO del pane del overlay. Si el foco
2967
- * sigue en el trigger (o en cualquier otro lado de la página) mientras el
2968
- * panel está abierto, `keydownEvents()` nunca dispara.
2859
+ * Escape con el panel abierto, escuchado en fase de captura sobre
2860
+ * `document` (ver `listenEscapeCapture`). Respaldo de
2861
+ * `overlayRef.keydownEvents()`: ese observable del CDK solo ve teclas
2862
+ * presionadas con el foco DENTRO del pane del overlay; si el foco sigue en
2863
+ * el trigger (o en cualquier otro lado) mientras el panel está abierto,
2864
+ * nunca dispara. Consume la tecla (`preventDefault`) solo si había un
2865
+ * panel que cerrar, así un `kf-drawer`/`kf-modal` que contenga al select
2866
+ * no se cierra con el mismo Escape. Devuelve si cerró algo.
2969
2867
  */
2970
2868
  onDocumentEscape() {
2869
+ if (!this.isOpen()) {
2870
+ return false;
2871
+ }
2971
2872
  this.close();
2873
+ return true;
2972
2874
  }
2973
2875
  toggle() {
2974
2876
  if (this.disabled()) {
@@ -3137,7 +3039,7 @@ class Select {
3137
3039
  }
3138
3040
  }
3139
3041
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
3140
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Select, isStandalone: true, selector: "kf-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, panelTitle: { classPropertyName: "panelTitle", publicName: "panelTitle", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" }, properties: { "class.kf-select--compact": "variant() === 'compact'", "class.kf-select--width-sm": "width() === 'sm'", "class.kf-select--width-lg": "width() === 'lg'", "class.kf-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [class.kf-select__trigger--with-icon]=\"!!icon()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-select__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ triggerText() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div #panel class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (panelTitle(); as title) {\n <div class=\"kf-select__panel-title kf-overline-text\" aria-hidden=\"true\">{{ title }}</div>\n }\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n aria-label=\"Buscar opci\u00F3n\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"filteredOptions().length ? optionId(activeIndex()) : null\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n }\n <ul\n class=\"kf-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : triggerId\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [id]=\"optionId(i)\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown)=\"onOptionKeydown($event, option, i)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}.kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__icon{flex:none;color:var(--kf-color-text-muted)}.kf-select__trigger--with-icon .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}:host(.kf-select--compact) .kf-select__trigger--with-icon .kf-select__value{color:var(--kf-color-ink);font-weight:600}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;font-family:var(--kf-font-body);width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__panel-title{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-xs)}.kf-select__panel--compact .kf-select__panel-title{padding:var(--kf-space-xs) var(--kf-space-sm) 2px}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);outline:none;color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__option:focus-visible{background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot", "truncate"] }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
3042
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Select, isStandalone: true, selector: "kf-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, panelTitle: { classPropertyName: "panelTitle", publicName: "panelTitle", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.kf-select--compact": "variant() === 'compact'", "class.kf-select--width-sm": "width() === 'sm'", "class.kf-select--width-lg": "width() === 'lg'", "class.kf-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [class.kf-select__trigger--with-icon]=\"!!icon()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-select__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ triggerText() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div #panel class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (panelTitle(); as title) {\n <div class=\"kf-select__panel-title kf-overline-text\" aria-hidden=\"true\">{{ title }}</div>\n }\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n aria-label=\"Buscar opci\u00F3n\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"filteredOptions().length ? optionId(activeIndex()) : null\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n }\n <ul\n class=\"kf-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : triggerId\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [id]=\"optionId(i)\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown)=\"onOptionKeydown($event, option, i)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}.kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__icon{flex:none;color:var(--kf-color-text-muted)}.kf-select__trigger--with-icon .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}:host(.kf-select--compact) .kf-select__trigger--with-icon .kf-select__value{color:var(--kf-color-ink);font-weight:600}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;font-family:var(--kf-font-body);width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__panel-title{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-xs)}.kf-select__panel--compact .kf-select__panel-title{padding:var(--kf-space-xs) var(--kf-space-sm) 2px}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);outline:none;color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__option:focus-visible{background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot", "truncate"] }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
3141
3043
  }
3142
3044
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, decorators: [{
3143
3045
  type: Component,
@@ -3147,10 +3049,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3147
3049
  '[class.kf-select--width-lg]': "width() === 'lg'",
3148
3050
  '[class.kf-select--width-full]': "width() === 'full'",
3149
3051
  }, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [class.kf-select__trigger--with-icon]=\"!!icon()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-select__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ triggerText() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div #panel class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (panelTitle(); as title) {\n <div class=\"kf-select__panel-title kf-overline-text\" aria-hidden=\"true\">{{ title }}</div>\n }\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n aria-label=\"Buscar opci\u00F3n\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"filteredOptions().length ? optionId(activeIndex()) : null\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n }\n <ul\n class=\"kf-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : triggerId\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [id]=\"optionId(i)\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown)=\"onOptionKeydown($event, option, i)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}.kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__icon{flex:none;color:var(--kf-color-text-muted)}.kf-select__trigger--with-icon .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}:host(.kf-select--compact) .kf-select__trigger--with-icon .kf-select__value{color:var(--kf-color-ink);font-weight:600}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;font-family:var(--kf-font-body);width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__panel-title{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-xs)}.kf-select__panel--compact .kf-select__panel-title{padding:var(--kf-space-xs) var(--kf-space-sm) 2px}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);outline:none;color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__option:focus-visible{background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
3150
- }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], panelTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelTitle", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onDocumentEscape: [{
3151
- type: HostListener,
3152
- args: ['document:keydown.escape']
3153
- }] } });
3052
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], panelTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelTitle", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
3154
3053
 
3155
3054
  const SIBLINGS = 1;
3156
3055
  /**
@@ -3327,6 +3226,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3327
3226
  const INTERACTIVE_CONTROL_SELECTOR = 'a[href], button, input, select, textarea, summary, label, [role="button"], [role="link"], [role="menuitem"], [role="checkbox"], [role="switch"], [role="tab"], [contenteditable="true"]';
3328
3227
  /** Piso de ancho de una columna `grow` que no declara `minWidth` ni `width`. */
3329
3228
  const GROW_MIN_WIDTH = '8rem';
3229
+ /** Tope de filas (o tarjetas) esqueleto: con `loading`, una página de 50 no pinta 50. */
3230
+ const DATA_TABLE_MAX_SKELETON_ROWS = 12;
3330
3231
  /** Opciones de "filas por página" estándar del portal — 10 como default
3331
3232
  * cubre bien tanto un dataset chico (ej. Beneficios) como uno con volumen
3332
3233
  * real (ej. 30+ cuotas en Billing), sin sentirse vacío ni insuficiente; 25
@@ -3365,12 +3266,21 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
3365
3266
  * <kf-data-table [columns]="columns" [rows]="rows" [cardable]="true" defaultView="cards" />
3366
3267
  *
3367
3268
  * Loading nativo: con `loading` en `true`, la tabla se reemplaza por un
3368
- * placeholder (`kf-skeleton` por celda/card) — mismo número de filas que
3369
- * `pageSize`, o 5 si no hay paginación activa. Ignora `rows()`/el slot
3269
+ * placeholder (`kf-skeleton` por celda/card) — `skeletonRows` filas si se
3270
+ * pasa; si no, las de `pageSize`, o 5 si no hay paginación activa. Nunca más
3271
+ * de 12 (`DATA_TABLE_MAX_SKELETON_ROWS`): con una página de 50 no se pintan
3272
+ * 50 placeholders. Ignora `rows()`/el slot
3370
3273
  * `[empty]` mientras dura (no sabemos aún si el resultado real estará
3371
3274
  * vacío), y respeta la vista activa (tabla o cards) para no cambiar el
3372
3275
  * layout de golpe cuando los datos reales lleguen. El toggle tabla/cards y
3373
- * el header de columnas siguen visibles — solo el cuerpo se reemplaza:
3276
+ * el header de columnas siguen visibles — solo el cuerpo se reemplaza.
3277
+ *
3278
+ * El placeholder respeta la columna: en la tabla cada celda conserva su
3279
+ * `align`, `width`, `minWidth` y `maxWidth` (una columna numérica lleva una
3280
+ * barra corta alineada al final, una de acciones un cuadrado chico, el resto
3281
+ * barras de anchos distintos) y la fila reserva el alto de una línea de texto.
3282
+ * En tarjetas reproduce la estructura según `cardRole`: encabezado (miniatura,
3283
+ * título y estado), campos con su rótulo, fila de `meta` y pie de acciones:
3374
3284
  *
3375
3285
  * <kf-data-table [columns]="columns" [rows]="rows()" [loading]="isLoading()">
3376
3286
  * <kf-empty-state empty>No hay resultados para mostrar.</kf-empty-state>
@@ -3463,6 +3373,30 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
3463
3373
  * <kf-data-table [columns]="columns" [rows]="quotes" cardable [(view)]="view"
3464
3374
  * [showViewToggle]="false" cardMinWidth="300px" [rowClickable]="true"
3465
3375
  * (rowClick)="open($event.row)" />
3376
+ *
3377
+ * Celdas interactivas (modo aditivo, ej. una matriz unidad × etapa donde cada
3378
+ * celda de etapa es un indicador de estado): una columna con
3379
+ * `interactive: true` renderiza su contenido dentro de un `<button>` real
3380
+ * —focusable, con foco visible y activación con Enter o Espacio— en la vista
3381
+ * de tabla y en la de tarjetas, y emite `cellClick` con `{ row, column, index }`.
3382
+ * `cellAriaLabel` da el nombre accesible de cada celda cuando el contenido no
3383
+ * lo aporta por sí solo (un indicador sin texto); en tarjetas conviene incluir
3384
+ * el contexto de la fila, porque no hay encabezado de columna que lo dé:
3385
+ *
3386
+ * columns = [
3387
+ * { key: 'unit', label: 'Unidad', cardRole: 'title', sticky: 'left' },
3388
+ * { key: 'stage1', label: 'Recepción', interactive: true, align: 'center' },
3389
+ * { key: 'stage2', label: 'Entrega', interactive: true, align: 'center' },
3390
+ * ];
3391
+ * <kf-data-table [columns]="columns" [rows]="units" cardable [rowKey]="unitId"
3392
+ * [cellTemplates]="{ stage1: stageCell, stage2: stageCell }"
3393
+ * [cellAriaLabel]="stageLabel" (cellClick)="openStage($event.row, $event.column)" />
3394
+ *
3395
+ * Convive con `rowClick`: la fila sigue emitiéndolo desde cualquier punto que
3396
+ * no sea una celda interactiva. En tarjetas, la tarjeta deja de ser un botón
3397
+ * (un botón dentro de un `role="button"` es inválido) salvo que se fuerce con
3398
+ * `[rowClickable]="true"`. `sticky`, `cellTemplates`, `currentRowKey`, la
3399
+ * ordenación y la paginación no cambian.
3466
3400
  */
3467
3401
  class DataTable {
3468
3402
  columns = input.required(/* @ts-ignore */
@@ -3473,6 +3407,11 @@ class DataTable {
3473
3407
  * ver el doc-comment de la clase para el detalle. */
3474
3408
  loading = input(false, /* @ts-ignore */
3475
3409
  ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
3410
+ /** Filas (o tarjetas) esqueleto mientras `loading` es `true`. Sin valor: el
3411
+ * `pageSize` con paginación activa, o 5. Máximo
3412
+ * {@link DATA_TABLE_MAX_SKELETON_ROWS}. */
3413
+ skeletonRows = input(undefined, /* @ts-ignore */
3414
+ ...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
3476
3415
  scrollEl = viewChild('scroll', /* @ts-ignore */
3477
3416
  ...(ngDevMode ? [{ debugName: "scrollEl" }] : /* istanbul ignore next */ []));
3478
3417
  destroyRef = inject(DestroyRef);
@@ -3593,7 +3532,9 @@ class DataTable {
3593
3532
  */
3594
3533
  render = input(/* @ts-ignore */
3595
3534
  ...(ngDevMode ? [undefined, { debugName: "render" }] : /* istanbul ignore next */ []));
3596
- /** Mapa `columnKey -> TemplateRef` para celdas con contenido custom. */
3535
+ /** Mapa `columnKey -> TemplateRef` para celdas con contenido custom. El
3536
+ * contexto trae `$implicit` (la fila), `index` y `column` (la columna), así
3537
+ * que un mismo template puede servir a varias columnas. */
3597
3538
  cellTemplates = input(/* @ts-ignore */
3598
3539
  ...(ngDevMode ? [undefined, { debugName: "cellTemplates" }] : /* istanbul ignore next */ []));
3599
3540
  /** Densidad de fila — ver `DataTableDensity`. */
@@ -3604,6 +3545,19 @@ class DataTable {
3604
3545
  rowClickable = input(null, /* @ts-ignore */
3605
3546
  ...(ngDevMode ? [{ debugName: "rowClickable" }] : /* istanbul ignore next */ []));
3606
3547
  rowClick = output();
3548
+ /**
3549
+ * Se emite al activar (clic, Enter o Espacio) una celda de una columna
3550
+ * `interactive`. `index` es el de la fila dentro de las filas mostradas
3551
+ * (misma convención que `rowClick`). No emite `rowClick`.
3552
+ */
3553
+ cellClick = output();
3554
+ /**
3555
+ * Nombre accesible (`aria-label`) del botón de cada celda interactiva:
3556
+ * `(row, column, index) => string`. Sin esta función, el botón se nombra
3557
+ * por su contenido. Se usa solo en columnas `interactive`.
3558
+ */
3559
+ cellAriaLabel = input(/* @ts-ignore */
3560
+ ...(ngDevMode ? [undefined, { debugName: "cellAriaLabel" }] : /* istanbul ignore next */ []));
3607
3561
  /** Nombre accesible del contenedor con scroll horizontal (solo se aplica cuando hay overflow). */
3608
3562
  ariaLabel = input('Tabla de datos', /* @ts-ignore */
3609
3563
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
@@ -3657,11 +3611,42 @@ class DataTable {
3657
3611
  effectivePageSize = computed(() => this.internalPageSize() ?? this.pageSize(), /* @ts-ignore */
3658
3612
  ...(ngDevMode ? [{ debugName: "effectivePageSize" }] : /* istanbul ignore next */ []));
3659
3613
  /** Cantidad de filas/cards placeholder mientras `loading` es true — el
3660
- * `pageSize` efectivo si hay paginación activa (así el placeholder ya
3661
- * "adivina" cuántas filas reales van a aparecer, sin salto de layout),
3662
- * o 5 si no hay paginación. */
3663
- skeletonRowIndexes = computed(() => Array.from({ length: this.effectivePageSize() ?? 5 }, (_, i) => i), /* @ts-ignore */
3614
+ * `skeletonRows` explícito; si no hay, el `pageSize` efectivo con
3615
+ * paginación activa (así el placeholder ya "adivina" cuántas filas reales
3616
+ * van a aparecer, sin salto de layout) o 5 sin paginación. Siempre entre 1 y
3617
+ * {@link DATA_TABLE_MAX_SKELETON_ROWS}: con una página de 50 filas no se
3618
+ * pintan 50 placeholders. */
3619
+ skeletonRowIndexes = computed(() => {
3620
+ const wanted = this.skeletonRows() ?? this.effectivePageSize() ?? 5;
3621
+ const total = Math.min(Math.max(Math.floor(wanted) || 0, 1), DATA_TABLE_MAX_SKELETON_ROWS);
3622
+ return Array.from({ length: total }, (_, i) => i);
3623
+ }, /* @ts-ignore */
3664
3624
  ...(ngDevMode ? [{ debugName: "skeletonRowIndexes" }] : /* istanbul ignore next */ []));
3625
+ /** Las tarjetas esqueleto llevan encabezado cuando la real lo tendría. */
3626
+ skeletonCardHeader = computed(() => !!this.cardMediaColumn() || !!this.cardTitleColumn() || this.cardStatusColumns().length > 0, /* @ts-ignore */
3627
+ ...(ngDevMode ? [{ debugName: "skeletonCardHeader" }] : /* istanbul ignore next */ []));
3628
+ /**
3629
+ * Ancho (%) de la barra de una celda esqueleto: no todas iguales — una
3630
+ * columna numérica (alineada al final) es corta, un identificador
3631
+ * (`mono`) mediano, una de acciones (`hideLabel`) un cuadrado chico, y el
3632
+ * resto alterna entre tres anchos según su posición para que la tabla no
3633
+ * parezca un patrón repetido.
3634
+ */
3635
+ skeletonBarWidth(column, row, index) {
3636
+ if (column.hideLabel) {
3637
+ return '2rem';
3638
+ }
3639
+ if (this.isEndAligned(column)) {
3640
+ return ['55%', '45%', '60%'][(row + index) % 3];
3641
+ }
3642
+ if (column.mono) {
3643
+ return '70%';
3644
+ }
3645
+ if (column.truncate) {
3646
+ return '90%';
3647
+ }
3648
+ return ['85%', '65%', '75%'][(row + index) % 3];
3649
+ }
3665
3650
  totalPages = computed(() => {
3666
3651
  const size = this.effectivePageSize();
3667
3652
  if (!size) {
@@ -3884,8 +3869,12 @@ class DataTable {
3884
3869
  * `role="button"` con botones adentro es inválido y deja el foco ambiguo.
3885
3870
  * Con columnas `action`, la interacción la manejan esos botones.
3886
3871
  */
3887
- cardInteractive = computed(() => this.rowClickable() ?? this.cardActionColumns().length === 0, /* @ts-ignore */
3872
+ cardInteractive = computed(() => this.rowClickable() ??
3873
+ (this.cardActionColumns().length === 0 && !this.hasInteractiveColumns()), /* @ts-ignore */
3888
3874
  ...(ngDevMode ? [{ debugName: "cardInteractive" }] : /* istanbul ignore next */ []));
3875
+ /** Hay al menos una columna `interactive` (celdas con botón propio). */
3876
+ hasInteractiveColumns = computed(() => this.columns().some((column) => column.interactive), /* @ts-ignore */
3877
+ ...(ngDevMode ? [{ debugName: "hasInteractiveColumns" }] : /* istanbul ignore next */ []));
3889
3878
  /**
3890
3879
  * Mismo criterio que `cardInteractive`, para la vista de tabla: la fila
3891
3880
  * entera dispara `rowClick` solo si no hay una columna `action` que ya
@@ -3952,6 +3941,14 @@ class DataTable {
3952
3941
  }
3953
3942
  this.rowClick.emit({ row, index });
3954
3943
  }
3944
+ /** Activación de una celda interactiva (clic, o Enter/Espacio sobre su botón). */
3945
+ onCellClick(row, column, index) {
3946
+ this.cellClick.emit({ row, column, index });
3947
+ }
3948
+ /** `aria-label` del botón de una celda interactiva, o `null` si lo da su contenido. */
3949
+ cellLabel(row, column, index) {
3950
+ return this.cellAriaLabel()?.(row, column, index) || null;
3951
+ }
3955
3952
  originatesInInteractiveControl(event) {
3956
3953
  const target = event?.target;
3957
3954
  const host = event?.currentTarget;
@@ -3990,43 +3987,545 @@ class DataTable {
3990
3987
  this.stickyOffsets.set(offsets);
3991
3988
  }
3992
3989
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
3993
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DataTable, isStandalone: true, selector: "kf-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, cardable: { classPropertyName: "cardable", publicName: "cardable", isSignal: true, isRequired: false, transformFunction: null }, defaultView: { classPropertyName: "defaultView", publicName: "defaultView", isSignal: true, isRequired: false, transformFunction: null }, showViewToggle: { classPropertyName: "showViewToggle", publicName: "showViewToggle", isSignal: true, isRequired: false, transformFunction: null }, cardMinWidth: { classPropertyName: "cardMinWidth", publicName: "cardMinWidth", isSignal: true, isRequired: false, transformFunction: null }, render: { classPropertyName: "render", publicName: "render", isSignal: true, isRequired: false, transformFunction: null }, cellTemplates: { classPropertyName: "cellTemplates", publicName: "cellTemplates", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, rowClickable: { classPropertyName: "rowClickable", publicName: "rowClickable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentRowKey: { classPropertyName: "currentRowKey", publicName: "currentRowKey", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, keepPageOnRowsChange: { classPropertyName: "keepPageOnRowsChange", publicName: "keepPageOnRowsChange", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", page: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", view: "viewChange" }, viewQueries: [{ propertyName: "scrollEl", first: true, predicate: ["scroll"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\">\n <kf-skeleton variant=\"rect\" height=\"120px\" />\n </div>\n }\n </div>\n } @else {\n <div class=\"kf-data-table__scroll\" aria-busy=\"true\">\n <table class=\"kf-data-table__table\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [style.width]=\"column.width\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n >\n <kf-skeleton />\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.enter)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.space)=\"cardInteractive() && onRowSpace($event, row, i)\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto) \u2014 compartido por la celda normal y la `truncate`. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Pagination, selector: "kf-pagination", inputs: ["page", "pages", "pageSizeOptions", "pageSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: ViewToggle, selector: "kf-view-toggle", inputs: ["view", "iconOnly", "tableLabel", "cardsLabel", "ariaLabel"], outputs: ["viewChange"] }] });
3990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DataTable, isStandalone: true, selector: "kf-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, skeletonRows: { classPropertyName: "skeletonRows", publicName: "skeletonRows", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, cardable: { classPropertyName: "cardable", publicName: "cardable", isSignal: true, isRequired: false, transformFunction: null }, defaultView: { classPropertyName: "defaultView", publicName: "defaultView", isSignal: true, isRequired: false, transformFunction: null }, showViewToggle: { classPropertyName: "showViewToggle", publicName: "showViewToggle", isSignal: true, isRequired: false, transformFunction: null }, cardMinWidth: { classPropertyName: "cardMinWidth", publicName: "cardMinWidth", isSignal: true, isRequired: false, transformFunction: null }, render: { classPropertyName: "render", publicName: "render", isSignal: true, isRequired: false, transformFunction: null }, cellTemplates: { classPropertyName: "cellTemplates", publicName: "cellTemplates", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, rowClickable: { classPropertyName: "rowClickable", publicName: "rowClickable", isSignal: true, isRequired: false, transformFunction: null }, cellAriaLabel: { classPropertyName: "cellAriaLabel", publicName: "cellAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentRowKey: { classPropertyName: "currentRowKey", publicName: "currentRowKey", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, keepPageOnRowsChange: { classPropertyName: "keepPageOnRowsChange", publicName: "keepPageOnRowsChange", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", cellClick: "cellClick", page: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", view: "viewChange" }, viewQueries: [{ propertyName: "scrollEl", first: true, predicate: ["scroll"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\" aria-hidden=\"true\">\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn()) {\n <div class=\"kf-data-table__card-media\">\n <span class=\"kf-data-table__sk-media kf-bone-fill\"></span>\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n <span\n class=\"kf-data-table__card-title kf-bone\"\n [class.kf-data-table__card-title--mono]=\"cardTitleColumn()?.mono\"\n style=\"--kf-bone-width: 8rem\"\n >COT-2026-0000</span\n >\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n <span class=\"kf-data-table__sk-pill kf-bone-fill\"></span>\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n >\n <span class=\"kf-data-table__sk-lead kf-bone\" style=\"--kf-bone-width: 90%\"\n >Texto</span\n >\n </div>\n }\n @for (column of cardFieldColumns(); track column.key; let first = $first) {\n <div class=\"kf-data-table__card-field\">\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >{{ column.label }}</span\n >\n @if (first && !skeletonCardHeader()) {\n <kf-skeleton class=\"kf-data-table__sk-value\" width=\"100%\" />\n } @else {\n <span class=\"kf-data-table__sk-value kf-bone\" style=\"--kf-bone-width: 40%\"\n >Valor</span\n >\n }\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 4rem\"\n >{{ column.label }}</span\n >\n <span\n class=\"kf-data-table__card-meta-value kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >Valor</span\n >\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n <span class=\"kf-data-table__sk-action kf-bone-fill\"></span>\n </div>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n aria-busy=\"true\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key; let c = $index) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n <div\n class=\"kf-data-table__sk-cell\"\n [class.kf-data-table__sk-cell--end]=\"isEndAligned(column)\"\n [class.kf-data-table__sk-cell--center]=\"column.align === 'center'\"\n >\n <kf-skeleton\n class=\"kf-data-table__sk-bar\"\n [style.width]=\"skeletonBarWidth(column, i, c)\"\n />\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.enter)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.space)=\"cardInteractive() ? onRowSpace($event, row, i) : undefined\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: media }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Celda de la vista tabla \u2014 compartida por la celda normal y la `truncate`.\n Una columna `interactive` envuelve su contenido en un bot\u00F3n real. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto). -->\n<ng-template #tableCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Zona de la tarjeta: igual que la celda de tabla, con bot\u00F3n si la columna es `interactive`. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);max-width:100%;min-width:32px;min-height:32px;padding:var(--kf-space-xs);border:0;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;text-align:inherit;cursor:pointer}.kf-data-table__cell-button:hover{box-shadow:inset 0 0 0 1px var(--kf-color-border-strong)}.kf-data-table__cell-button:focus-visible{outline:2px solid transparent;outline-offset:-2px;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}@media(max-width:640px),(pointer:coarse){.kf-data-table__cell-button{min-width:44px;min-height:44px}}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}.kf-data-table__sk-cell{display:flex;align-items:center;justify-content:flex-start;min-height:1lh}.kf-data-table__sk-cell--center{justify-content:center}.kf-data-table__sk-cell--end{justify-content:flex-end}.kf-data-table__sk-bar{flex:none}.kf-data-table__sk-media{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs)}.kf-data-table__sk-pill{display:block;width:4.5rem;height:1.25rem;border-radius:var(--kf-radius-pill)}.kf-data-table__sk-value{flex:none;width:40%}.kf-data-table__sk-lead{line-height:1.35}.kf-data-table__sk-action{display:block;width:5rem;height:2rem;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Pagination, selector: "kf-pagination", inputs: ["page", "pages", "pageSizeOptions", "pageSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: ViewToggle, selector: "kf-view-toggle", inputs: ["view", "iconOnly", "tableLabel", "cardsLabel", "ariaLabel"], outputs: ["viewChange"] }] });
3994
3991
  }
3995
3992
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, decorators: [{
3996
3993
  type: Component,
3997
- args: [{ selector: 'kf-data-table', imports: [Icon, NgTemplateOutlet, Pagination, Skeleton, ViewToggle], template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\">\n <kf-skeleton variant=\"rect\" height=\"120px\" />\n </div>\n }\n </div>\n } @else {\n <div class=\"kf-data-table__scroll\" aria-busy=\"true\">\n <table class=\"kf-data-table__table\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [style.width]=\"column.width\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n >\n <kf-skeleton />\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.enter)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.space)=\"cardInteractive() && onRowSpace($event, row, i)\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto) \u2014 compartido por la celda normal y la `truncate`. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}\n"] }]
3998
- }], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], scrollEl: [{ type: i0.ViewChild, args: ['scroll', { isSignal: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], cardable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardable", required: false }] }], defaultView: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultView", required: false }] }], showViewToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showViewToggle", required: false }] }], cardMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardMinWidth", required: false }] }], render: [{ type: i0.Input, args: [{ isSignal: true, alias: "render", required: false }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], rowClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClickable", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentRowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentRowKey", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }], keepPageOnRowsChange: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepPageOnRowsChange", required: false }] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }] } });
3994
+ args: [{ selector: 'kf-data-table', imports: [Icon, NgTemplateOutlet, Pagination, Skeleton, ViewToggle], template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\" aria-hidden=\"true\">\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn()) {\n <div class=\"kf-data-table__card-media\">\n <span class=\"kf-data-table__sk-media kf-bone-fill\"></span>\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n <span\n class=\"kf-data-table__card-title kf-bone\"\n [class.kf-data-table__card-title--mono]=\"cardTitleColumn()?.mono\"\n style=\"--kf-bone-width: 8rem\"\n >COT-2026-0000</span\n >\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n <span class=\"kf-data-table__sk-pill kf-bone-fill\"></span>\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n >\n <span class=\"kf-data-table__sk-lead kf-bone\" style=\"--kf-bone-width: 90%\"\n >Texto</span\n >\n </div>\n }\n @for (column of cardFieldColumns(); track column.key; let first = $first) {\n <div class=\"kf-data-table__card-field\">\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >{{ column.label }}</span\n >\n @if (first && !skeletonCardHeader()) {\n <kf-skeleton class=\"kf-data-table__sk-value\" width=\"100%\" />\n } @else {\n <span class=\"kf-data-table__sk-value kf-bone\" style=\"--kf-bone-width: 40%\"\n >Valor</span\n >\n }\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 4rem\"\n >{{ column.label }}</span\n >\n <span\n class=\"kf-data-table__card-meta-value kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >Valor</span\n >\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n <span class=\"kf-data-table__sk-action kf-bone-fill\"></span>\n </div>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n aria-busy=\"true\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key; let c = $index) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n <div\n class=\"kf-data-table__sk-cell\"\n [class.kf-data-table__sk-cell--end]=\"isEndAligned(column)\"\n [class.kf-data-table__sk-cell--center]=\"column.align === 'center'\"\n >\n <kf-skeleton\n class=\"kf-data-table__sk-bar\"\n [style.width]=\"skeletonBarWidth(column, i, c)\"\n />\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.enter)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.space)=\"cardInteractive() ? onRowSpace($event, row, i) : undefined\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: media }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Celda de la vista tabla \u2014 compartida por la celda normal y la `truncate`.\n Una columna `interactive` envuelve su contenido en un bot\u00F3n real. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto). -->\n<ng-template #tableCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Zona de la tarjeta: igual que la celda de tabla, con bot\u00F3n si la columna es `interactive`. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);max-width:100%;min-width:32px;min-height:32px;padding:var(--kf-space-xs);border:0;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;text-align:inherit;cursor:pointer}.kf-data-table__cell-button:hover{box-shadow:inset 0 0 0 1px var(--kf-color-border-strong)}.kf-data-table__cell-button:focus-visible{outline:2px solid transparent;outline-offset:-2px;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}@media(max-width:640px),(pointer:coarse){.kf-data-table__cell-button{min-width:44px;min-height:44px}}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}.kf-data-table__sk-cell{display:flex;align-items:center;justify-content:flex-start;min-height:1lh}.kf-data-table__sk-cell--center{justify-content:center}.kf-data-table__sk-cell--end{justify-content:flex-end}.kf-data-table__sk-bar{flex:none}.kf-data-table__sk-media{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs)}.kf-data-table__sk-pill{display:block;width:4.5rem;height:1.25rem;border-radius:var(--kf-radius-pill)}.kf-data-table__sk-value{flex:none;width:40%}.kf-data-table__sk-lead{line-height:1.35}.kf-data-table__sk-action{display:block;width:5rem;height:2rem;border-radius:var(--kf-radius-sm)}\n"] }]
3995
+ }], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], skeletonRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonRows", required: false }] }], scrollEl: [{ type: i0.ViewChild, args: ['scroll', { isSignal: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], cardable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardable", required: false }] }], defaultView: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultView", required: false }] }], showViewToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showViewToggle", required: false }] }], cardMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardMinWidth", required: false }] }], render: [{ type: i0.Input, args: [{ isSignal: true, alias: "render", required: false }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], rowClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClickable", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], cellClick: [{ type: i0.Output, args: ["cellClick"] }], cellAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentRowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentRowKey", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }], keepPageOnRowsChange: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepPageOnRowsChange", required: false }] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }] } });
3999
3996
 
4000
3997
  /**
4001
- * Signal con la vista de `kf-data-table` ('table' | 'cards') derivada de un
4002
- * breakpoint — mismo criterio que cualquier tabla con muchas columnas: bajo
4003
- * el ancho dado, la vista de tabla no entra sin scroll horizontal, así que
4004
- * se fuerza `cards`. Centraliza el patrón `matchMediaSignal` +
4005
- * `computed<DataTableView>` que cada tabla del portal repetía igual.
3998
+ * Acción de bajo énfasis con apariencia de texto — "¿Olvidaste tu
3999
+ * contraseña?", "Volver a iniciar sesión", o un link real de navegación
4000
+ * como "Ir a pagos →".
4006
4001
  *
4007
- * protected readonly tableView = dataTableViewSignal();
4008
- * // con un breakpoint distinto al default de 900px:
4009
- * protected readonly tableView = dataTableViewSignal('(max-width: 768px)');
4010
- */
4011
- function dataTableViewSignal(query = '(max-width: 900px)') {
4012
- const isMobile = matchMediaSignal(query);
4013
- return computed(() => (isMobile() ? 'cards' : 'table'));
4014
- }
4015
-
4016
- /**
4017
- * Separador visual simple entre bloques de contenido. `orientation`
4018
- * controla si es una línea horizontal (para separar secciones apiladas) o
4019
- * vertical (para separar elementos en una fila, ej. acciones en una toolbar,
4020
- * o las columnas de metadata en `kf-company-selector`).
4002
+ * Sin `href` (default) es un `<button type="button">`: dispara una acción
4003
+ * en la misma pantalla, vía `(click)`. Con `href`, es un `<a>` real: navega
4004
+ * a otra URL. `showArrow` agrega un ícono `chevron-right` al final — para
4005
+ * el patrón "texto + flecha" de un link que lleva a otro lado.
4021
4006
  *
4022
- * En `vertical`, el divisor necesita una altura del contexto donde vive —
4023
- * `:host { align-self: stretch }` lo hace ocupar la altura del contenedor
4024
- * flex/grid padre sin que el consumidor tenga que fijarla a mano. `flex:
4025
- * none` evita que el propio divisor se achique a 0 dentro de ese contenedor
4026
- * flex cuando hay contenido hermano compitiendo por espacio.
4007
+ * `variant="default"` (color primario, subrayado en hover) es para la
4008
+ * acción de mayor jerarquía relativa de la pantalla; `variant="muted"`
4009
+ * (gris que pasa a primario en hover) es para acciones de retroceso/salida.
4027
4010
  *
4028
- * <kf-divider />
4029
- * <kf-divider orientation="vertical" />
4011
+ * <kf-link (click)="onForgotPassword()">¿Olvidaste tu contraseña?</kf-link>
4012
+ * <kf-link variant="muted" (click)="onBack()">← Volver a iniciar sesión</kf-link>
4013
+ * <kf-link href="/pagos" showArrow>Ir a pagos</kf-link>
4014
+ */
4015
+ class Link {
4016
+ variant = input('default', /* @ts-ignore */
4017
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
4018
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4019
+ /** Si se pasa, renderiza un `<a>` real en vez de `<button>` — navega en vez de disparar `(click)`. */
4020
+ href = input(/* @ts-ignore */
4021
+ ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
4022
+ /** Ícono `chevron-right` al final — para el patrón "texto + flecha" de un link que lleva a otro lado. */
4023
+ showArrow = input(false, { ...(ngDevMode ? { debugName: "showArrow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4024
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, deps: [], target: i0.ɵɵFactoryTarget.Component });
4025
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Link, isStandalone: true, selector: "kf-link", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, showArrow: { classPropertyName: "showArrow", publicName: "showArrow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
4026
+ }
4027
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, decorators: [{
4028
+ type: Component,
4029
+ args: [{ selector: 'kf-link', imports: [Icon, NgTemplateOutlet], template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"] }]
4030
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], showArrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArrow", required: false }] }] } });
4031
+
4032
+ /**
4033
+ * Fila de un documento descargable: chip de ícono coloreado por tipo,
4034
+ * título, metadata y la acción de descarga al final. Sin `href` rinde
4035
+ * `pendingLabel` en vez de un link roto. El contenido proyectado cae entre
4036
+ * la metadata y la acción, como slot para un `kf-badge` de estado.
4037
+ *
4038
+ * `loading` (default `false`): dibuja el esqueleto de la fila — chip, título,
4039
+ * metadata y acción como bloques del alto real de cada texto — con el mismo
4040
+ * cascarón, así que al llegar los datos no hay salto de layout. Pasa los
4041
+ * mismos inputs que usarás cargada (`meta` incluido, para reservar su línea).
4042
+ * Marca el host con `aria-busy="true"` y oculta el contenido proyectado.
4043
+ *
4044
+ * <kf-document-row title="Factura" meta="PDF · 120 KB" [loading]="isLoading()" />
4045
+ */
4046
+ class DocumentRow {
4047
+ icon = input('file', /* @ts-ignore */
4048
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
4049
+ tone = input('neutral', /* @ts-ignore */
4050
+ ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
4051
+ /** Qué documento es. El nombre humano del tipo, no el del archivo. */
4052
+ title = input.required(/* @ts-ignore */
4053
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4054
+ /** Línea secundaria — archivo, fecha, tamaño. El consumidor la arma ya
4055
+ * formateada: la librería no decide en qué unidad se lee un tamaño ni en
4056
+ * qué formato una fecha. */
4057
+ meta = input(/* @ts-ignore */
4058
+ ...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
4059
+ /** Ausente rinde `pendingLabel` en vez del link de descarga. */
4060
+ href = input(/* @ts-ignore */
4061
+ ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
4062
+ downloadLabel = input('Descargar', /* @ts-ignore */
4063
+ ...(ngDevMode ? [{ debugName: "downloadLabel" }] : /* istanbul ignore next */ []));
4064
+ /** Qué se dice cuando no hay `href`. */
4065
+ pendingLabel = input('En generación', /* @ts-ignore */
4066
+ ...(ngDevMode ? [{ debugName: "pendingLabel" }] : /* istanbul ignore next */ []));
4067
+ /** Dibuja el esqueleto en lugar del contenido, conservando las dimensiones. */
4068
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4069
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
4070
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DocumentRow, isStandalone: true, selector: "kf-document-row", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, downloadLabel: { classPropertyName: "downloadLabel", publicName: "downloadLabel", isSignal: true, isRequired: false, transformFunction: null }, pendingLabel: { classPropertyName: "pendingLabel", publicName: "pendingLabel", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kf-document-row--\" + tone()", "class.kf-document-row--loading": "loading()", "attr.aria-busy": "loading() ? 'true' : null" } }, ngImport: i0, template: "@if (loading()) {\n <span class=\"kf-document-row__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n\n <span class=\"kf-document-row__body\" aria-hidden=\"true\">\n <span class=\"kf-document-row__title kf-bone\" style=\"--kf-bone-width: 55%\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text kf-bone\" style=\"--kf-bone-width: 35%\">{{\n line\n }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions kf-document-row__actions--loading\" aria-hidden=\"true\">\n <ng-content />\n <span class=\"kf-document-row__pending kf-caption-text kf-bone\" style=\"--kf-bone-width: 5rem\">{{\n href() ? downloadLabel() : pendingLabel()\n }}</span>\n </span>\n} @else {\n <span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n </span>\n\n <span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n </span>\n}\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}.kf-document-row__actions--loading>:not(.kf-bone){visibility:hidden}.kf-document-row__icon.kf-bone-fill{border-radius:var(--kf-radius-xs)}:host(.kf-document-row--loading) .kf-document-row__icon{background:var(--kf-color-border);animation:none}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
4071
+ }
4072
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, decorators: [{
4073
+ type: Component,
4074
+ args: [{ selector: 'kf-document-row', imports: [Icon, Link], host: {
4075
+ '[class]': '"kf-document-row--" + tone()',
4076
+ '[class.kf-document-row--loading]': 'loading()',
4077
+ '[attr.aria-busy]': "loading() ? 'true' : null",
4078
+ }, template: "@if (loading()) {\n <span class=\"kf-document-row__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n\n <span class=\"kf-document-row__body\" aria-hidden=\"true\">\n <span class=\"kf-document-row__title kf-bone\" style=\"--kf-bone-width: 55%\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text kf-bone\" style=\"--kf-bone-width: 35%\">{{\n line\n }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions kf-document-row__actions--loading\" aria-hidden=\"true\">\n <ng-content />\n <span class=\"kf-document-row__pending kf-caption-text kf-bone\" style=\"--kf-bone-width: 5rem\">{{\n href() ? downloadLabel() : pendingLabel()\n }}</span>\n </span>\n} @else {\n <span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n </span>\n\n <span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n </span>\n}\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}.kf-document-row__actions--loading>:not(.kf-bone){visibility:hidden}.kf-document-row__icon.kf-bone-fill{border-radius:var(--kf-radius-xs)}:host(.kf-document-row--loading) .kf-document-row__icon{background:var(--kf-color-border);animation:none}\n"] }]
4079
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], downloadLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadLabel", required: false }] }], pendingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingLabel", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
4080
+
4081
+ /**
4082
+ * Barra de progreso de un solo valor (0-100%) con color por severidad —
4083
+ * para un porcentaje simple, a diferencia de `kf-bucket-progress`, que
4084
+ * dibuja varias categorías proporcionales en la misma barra.
4085
+ *
4086
+ * <kf-progress [value]="80" tone="ok" label="80%" />
4087
+ * <kf-progress [value]="ratio()" [tone]="severityTone()" />
4088
+ *
4089
+ * `value` se clampea a [0, 100] — un consumidor que calcule un ratio fuera
4090
+ * de rango (ej. tiempo transcurrido sobre un SLA ya vencido) no rompe el
4091
+ * ancho de la barra. `size` cambia el grosor de la pista (`sm` 4px, `md` 8px,
4092
+ * `lg` 12px; sin él, 4px como siempre); el `role="progressbar"` y sus valores
4093
+ * accesibles no cambian. `label` es texto libre opcional sobre la barra (ej.
4094
+ * "80%"); sin él, no se renderiza ningún texto, solo la barra.
4095
+ */
4096
+ class Progress {
4097
+ value = input.required(/* @ts-ignore */
4098
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4099
+ tone = input('neutral', /* @ts-ignore */
4100
+ ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
4101
+ /** Grosor de la pista. Default `sm` (4px). */
4102
+ size = input('sm', /* @ts-ignore */
4103
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4104
+ label = input(/* @ts-ignore */
4105
+ ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
4106
+ /** Nombre accesible de la barra (`aria-label`). Sin él, el `progressbar`
4107
+ * queda sin nombre: dalo siempre que el contexto no lo nombre ya (por
4108
+ * ejemplo con un `label` visible asociado). */
4109
+ ariaLabel = input(/* @ts-ignore */
4110
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4111
+ clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100), /* @ts-ignore */
4112
+ ...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
4113
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, deps: [], target: i0.ɵɵFactoryTarget.Component });
4114
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Progress, isStandalone: true, selector: "kf-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track kf-progress__track--{{ size() }}\"\n role=\"progressbar\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__track--md{height:8px}.kf-progress__track--lg{height:12px}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}.kf-progress--purple{color:var(--kf-color-purple)}.kf-progress__fill.kf-progress--purple{background:var(--kf-color-purple)}\n"] });
4115
+ }
4116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, decorators: [{
4117
+ type: Component,
4118
+ args: [{ selector: 'kf-progress', template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track kf-progress__track--{{ size() }}\"\n role=\"progressbar\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__track--md{height:8px}.kf-progress__track--lg{height:12px}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}.kf-progress--purple{color:var(--kf-color-purple)}.kf-progress__fill.kf-progress--purple{background:var(--kf-color-purple)}\n"] }]
4119
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
4120
+
4121
+ /** Tono de `kf-progress` que acompaña a cada tono de la card. */
4122
+ const BAR_TONE = {
4123
+ primary: 'primary',
4124
+ success: 'ok',
4125
+ error: 'error',
4126
+ warn: 'warn',
4127
+ purple: 'primary',
4128
+ info: 'primary',
4129
+ neutral: 'primary',
4130
+ };
4131
+ /**
4132
+ * Card de estadística horizontal: ícono circular grande a la izquierda,
4133
+ * valor + label + nota apilados al centro, chevron opcional al final si es
4134
+ * clickeable. Distinta de `kf-metric-card` (layout vertical, label arriba +
4135
+ * ícono a la derecha) — este es el patrón real del dashboard de OTD ("Bonos
4136
+ * otorgados", "Cortesías otorgadas"), no una variante de la misma card.
4137
+ *
4138
+ * <kf-stat-card icon="gift" tone="purple" value="4" label="Bonos otorgados" note="$15,000.00 en beneficios" (cardClick)="onNavigate('beneficios')" />
4139
+ *
4140
+ * Extensiones opcionales (la card sin ellas se ve y se comporta como siempre):
4141
+ * - `tone`: además de `primary|success|error|purple`, `warn`, `info` y `neutral`.
4142
+ * - `bar` (0-100): barra de progreso accesible (`kf-progress`, `role="progressbar"`
4143
+ * con `aria-valuenow`/`min`/`max` y el `label` como nombre) bajo la nota. Se
4144
+ * acota a [0, 100]; `null`/`undefined` no la dibuja. Es un refuerzo visual:
4145
+ * el valor exacto conviene darlo también en texto (`hint`), porque el relleno
4146
+ * de algunos tonos de `kf-progress` tiene poco contraste con su pista.
4147
+ * - `hint`: texto secundario bajo la barra (ej. "12 de 20 completadas").
4148
+ * - `pressed` (`boolean | null`): con `clickable`, la card es un botón de
4149
+ * alternancia (`aria-pressed`), útil como filtro. `null` (default) = no
4150
+ * aplica y no emite el atributo. Sin `clickable` no tiene efecto.
4151
+ *
4152
+ * <kf-stat-card icon="clock" tone="warn" value="7" label="Por vencer" [bar]="35" hint="7 de 20"
4153
+ * clickable [pressed]="filter() === 'due'" (cardClick)="toggle('due')" />
4154
+ *
4155
+ * `loading` (default `false`): mientras es `true` la card dibuja su esqueleto
4156
+ * en vez del contenido, con el MISMO cascarón (borde, padding, ícono de 50px)
4157
+ * y líneas del alto real de cada texto — valor, etiqueta y, si se pasaron,
4158
+ * nota, barra y `hint` —, así que al llegar los datos no hay salto de
4159
+ * layout. Pasa los mismos inputs que usarás cargada para que el esqueleto
4160
+ * reserve las mismas líneas. Marca la card con `aria-busy="true"`, no es
4161
+ * clicable ni expone su texto a lectores de pantalla mientras carga.
4162
+ *
4163
+ * <kf-stat-card icon="clock" value="7" label="Por vencer" note="Esta semana" [loading]="isLoading()" />
4164
+ */
4165
+ class StatCard {
4166
+ icon = input.required(/* @ts-ignore */
4167
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
4168
+ tone = input('primary', /* @ts-ignore */
4169
+ ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
4170
+ value = input.required(/* @ts-ignore */
4171
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4172
+ label = input.required(/* @ts-ignore */
4173
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4174
+ note = input(/* @ts-ignore */
4175
+ ...(ngDevMode ? [undefined, { debugName: "note" }] : /* istanbul ignore next */ []));
4176
+ /** Avance 0-100 (barra de progreso accesible); `null`/`undefined` = sin barra. */
4177
+ bar = input(undefined, /* @ts-ignore */
4178
+ ...(ngDevMode ? [{ debugName: "bar" }] : /* istanbul ignore next */ []));
4179
+ /** Texto secundario bajo la barra (o bajo la nota si no hay barra). */
4180
+ hint = input(/* @ts-ignore */
4181
+ ...(ngDevMode ? [undefined, { debugName: "hint" }] : /* istanbul ignore next */ []));
4182
+ /**
4183
+ * Estado de alternancia (`aria-pressed`) cuando la card es `clickable`.
4184
+ * `null` (default) = no aplica: el atributo no se emite.
4185
+ */
4186
+ pressed = input(null, /* @ts-ignore */
4187
+ ...(ngDevMode ? [{ debugName: "pressed" }] : /* istanbul ignore next */ []));
4188
+ /** Dibuja el esqueleto en lugar del contenido, conservando las dimensiones. */
4189
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4190
+ /** Si se escucha `cardClick`, la card se renderiza como botón clickeable con chevron. */
4191
+ clickable = input(false, { ...(ngDevMode ? { debugName: "clickable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4192
+ cardClick = output();
4193
+ barTone = computed(() => BAR_TONE[this.tone()], /* @ts-ignore */
4194
+ ...(ngDevMode ? [{ debugName: "barTone" }] : /* istanbul ignore next */ []));
4195
+ hasBar = computed(() => {
4196
+ const value = this.bar();
4197
+ return value !== null && value !== undefined && Number.isFinite(value);
4198
+ }, /* @ts-ignore */
4199
+ ...(ngDevMode ? [{ debugName: "hasBar" }] : /* istanbul ignore next */ []));
4200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
4201
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: StatCard, isStandalone: true, selector: "kf-stat-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, bar: { classPropertyName: "bar", publicName: "bar", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, pressed: { classPropertyName: "pressed", publicName: "pressed", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "@if (loading()) {\n <div class=\"kf-stat-card\" aria-busy=\"true\">\n <span\n class=\"kf-stat-card__icon kf-stat-card__icon--bone kf-bone-fill\"\n aria-hidden=\"true\"\n ></span>\n <span class=\"kf-stat-card__body\" aria-hidden=\"true\">\n <span class=\"kf-stat-card__value kf-bone\" style=\"--kf-bone-width: 40%\">{{ value() }}</span>\n <span class=\"kf-stat-card__label kf-bone\" style=\"--kf-bone-width: 70%\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note kf-bone\" style=\"--kf-bone-width: 55%\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <span class=\"kf-stat-card__bar\">\n <span class=\"kf-stat-card__bar-bone kf-bone-fill\"></span>\n </span>\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint kf-bone\" style=\"--kf-bone-width: 45%\">{{ hint() }}</span>\n }\n </span>\n </div>\n} @else if (clickable()) {\n <button\n type=\"button\"\n class=\"kf-stat-card kf-stat-card--clickable\"\n [class.kf-stat-card--pressed]=\"pressed() === true\"\n [attr.aria-pressed]=\"pressed()\"\n (click)=\"cardClick.emit()\"\n >\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <kf-progress\n class=\"kf-stat-card__bar\"\n [value]=\"bar()!\"\n [tone]=\"barTone()\"\n [ariaLabel]=\"label()\"\n />\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint\">{{ hint() }}</span>\n }\n </span>\n</ng-template>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:2px solid transparent;box-shadow:var(--kf-shadow-focus)}.kf-stat-card--pressed{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 1px var(--kf-color-primary)}.kf-stat-card--pressed:focus-visible{box-shadow:inset 0 0 0 1px var(--kf-color-primary),var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__icon--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}.kf-stat-card__icon--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}.kf-stat-card__icon--neutral{background:var(--kf-color-gray-100);color:var(--kf-color-text)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__bar{padding-block-start:var(--kf-space-xs)}.kf-stat-card__hint{font-size:.78rem;color:var(--kf-color-text)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}.kf-stat-card__icon--bone{border-radius:var(--kf-radius-md)}.kf-stat-card__bar-bone{display:block;height:4px;border-radius:var(--kf-radius-pill)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Progress, selector: "kf-progress", inputs: ["value", "tone", "size", "label", "ariaLabel"] }] });
4202
+ }
4203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, decorators: [{
4204
+ type: Component,
4205
+ args: [{ selector: 'kf-stat-card', imports: [Icon, NgTemplateOutlet, Progress], template: "@if (loading()) {\n <div class=\"kf-stat-card\" aria-busy=\"true\">\n <span\n class=\"kf-stat-card__icon kf-stat-card__icon--bone kf-bone-fill\"\n aria-hidden=\"true\"\n ></span>\n <span class=\"kf-stat-card__body\" aria-hidden=\"true\">\n <span class=\"kf-stat-card__value kf-bone\" style=\"--kf-bone-width: 40%\">{{ value() }}</span>\n <span class=\"kf-stat-card__label kf-bone\" style=\"--kf-bone-width: 70%\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note kf-bone\" style=\"--kf-bone-width: 55%\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <span class=\"kf-stat-card__bar\">\n <span class=\"kf-stat-card__bar-bone kf-bone-fill\"></span>\n </span>\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint kf-bone\" style=\"--kf-bone-width: 45%\">{{ hint() }}</span>\n }\n </span>\n </div>\n} @else if (clickable()) {\n <button\n type=\"button\"\n class=\"kf-stat-card kf-stat-card--clickable\"\n [class.kf-stat-card--pressed]=\"pressed() === true\"\n [attr.aria-pressed]=\"pressed()\"\n (click)=\"cardClick.emit()\"\n >\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <kf-progress\n class=\"kf-stat-card__bar\"\n [value]=\"bar()!\"\n [tone]=\"barTone()\"\n [ariaLabel]=\"label()\"\n />\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint\">{{ hint() }}</span>\n }\n </span>\n</ng-template>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:2px solid transparent;box-shadow:var(--kf-shadow-focus)}.kf-stat-card--pressed{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 1px var(--kf-color-primary)}.kf-stat-card--pressed:focus-visible{box-shadow:inset 0 0 0 1px var(--kf-color-primary),var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__icon--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}.kf-stat-card__icon--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}.kf-stat-card__icon--neutral{background:var(--kf-color-gray-100);color:var(--kf-color-text)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__bar{padding-block-start:var(--kf-space-xs)}.kf-stat-card__hint{font-size:.78rem;color:var(--kf-color-text)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}.kf-stat-card__icon--bone{border-radius:var(--kf-radius-md)}.kf-stat-card__bar-bone{display:block;height:4px;border-radius:var(--kf-radius-pill)}\n"] }]
4206
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], bar: [{ type: i0.Input, args: [{ isSignal: true, alias: "bar", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], pressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "pressed", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
4207
+
4208
+ /** Cantidad de elementos por defecto de cada variante (`count` sin valor). */
4209
+ const DEFAULT_COUNT = {
4210
+ 'kpi-grid': 4,
4211
+ 'card-grid': 6,
4212
+ form: 4,
4213
+ list: 4,
4214
+ table: 5,
4215
+ timeline: 4,
4216
+ 'detail-header': 1,
4217
+ };
4218
+ /** Tope de elementos: un `count` mayor se acota, no tiene sentido pintar más. */
4219
+ const SKELETON_PRESET_MAX_COUNT = 12;
4220
+ /**
4221
+ * Esqueletos listos para usar que reproducen la FORMA del contenido real, para
4222
+ * que al llegar los datos nada se mueva. Cada variante se arma con la misma
4223
+ * geometría que su equivalente cargado (mismos cascarones, alturas, radios y
4224
+ * gaps, todo por tokens):
4225
+ *
4226
+ * | `variant` | Equivalente cargado | `count` | `columns` |
4227
+ * | ---------------- | ---------------------------- | -------------- | ------------------ |
4228
+ * | `kpi-grid` | `kf-stat-card` | tarjetas (4) | columnas de la grilla |
4229
+ * | `card-grid` | `kf-card` | tarjetas (6) | columnas de la grilla |
4230
+ * | `form` | `kf-input` con label | campos (4) | `1` o `2` |
4231
+ * | `list` | `kf-document-row` | filas (4) | — |
4232
+ * | `table` | `kf-data-table` | filas (5) | columnas (4) |
4233
+ * | `timeline` | `kf-milestone-timeline` | etapas (4) | — |
4234
+ * | `detail-header` | `kf-page-header` con ícono | — | — |
4235
+ *
4236
+ * Sin `columns`, las grillas (`kpi-grid`, `card-grid`) se reparten solas con un
4237
+ * ancho mínimo por tarjeta. `count` se acota a 12.
4238
+ *
4239
+ * El preset es decorativo (`aria-hidden` por dentro) y marca su contenedor con
4240
+ * `aria-busy="true"`. Con `label` agrega un texto visualmente oculto con
4241
+ * `role="status"` que lo anuncia a lectores de pantalla; dentro de
4242
+ * `kf-content-state` no hace falta (ya lo anuncia él).
4243
+ *
4244
+ * <kf-skeleton-preset variant="kpi-grid" [count]="4" />
4245
+ * <kf-skeleton-preset variant="form" [count]="3" [columns]="2" label="Cargando formulario" />
4246
+ * <kf-content-state [state]="state()" loadingPreset="table">…</kf-content-state>
4247
+ */
4248
+ class SkeletonPreset {
4249
+ variant = input('list', /* @ts-ignore */
4250
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
4251
+ /** Cantidad de elementos. Sin valor, el de la variante. Máximo 12. */
4252
+ count = input(undefined, /* @ts-ignore */
4253
+ ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
4254
+ /** Columnas: de la grilla (`kpi-grid`, `card-grid`), del formulario (`1`|`2`) o de la tabla. */
4255
+ columns = input(undefined, /* @ts-ignore */
4256
+ ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
4257
+ /** Texto accesible (visualmente oculto, `role="status"`). Vacío, no se emite. */
4258
+ label = input('', /* @ts-ignore */
4259
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4260
+ total = computed(() => {
4261
+ const requested = this.count() ?? DEFAULT_COUNT[this.variant()];
4262
+ return Math.min(Math.max(Math.floor(requested) || 0, 1), SKELETON_PRESET_MAX_COUNT);
4263
+ }, /* @ts-ignore */
4264
+ ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
4265
+ items = computed(() => Array.from({ length: this.total() }, (_, i) => i), /* @ts-ignore */
4266
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
4267
+ /** Columnas de la tabla sintética (encabezados en blanco: lo real aún no llegó). */
4268
+ tableColumns = computed(() => Array.from({ length: Math.min(Math.max(this.columns() ?? 4, 1), 8) }, (_, i) => ({
4269
+ key: `c${i}`,
4270
+ label: ' ',
4271
+ })), /* @ts-ignore */
4272
+ ...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
4273
+ formColumns = computed(() => (this.columns() === 2 ? 2 : 1), /* @ts-ignore */
4274
+ ...(ngDevMode ? [{ debugName: "formColumns" }] : /* istanbul ignore next */ []));
4275
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SkeletonPreset, deps: [], target: i0.ɵɵFactoryTarget.Component });
4276
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SkeletonPreset, isStandalone: true, selector: "kf-skeleton-preset", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n [class]=\"'kf-skeleton-preset kf-skeleton-preset--' + variant()\"\n aria-busy=\"true\"\n [style.--kf-skeleton-preset-columns]=\"columns() ?? null\"\n>\n @if (label()) {\n <span class=\"kf-sr-only\" role=\"status\">{{ label() }}</span>\n }\n @switch (variant()) {\n @case ('kpi-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-stat-card [loading]=\"true\" icon=\"bell\" value=\"0\" label=\"Etiqueta\" note=\"Nota\" />\n }\n </div>\n }\n @case ('card-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-card gap=\"sm\" aria-hidden=\"true\">\n <kf-skeleton variant=\"rect\" height=\"7.5rem\" />\n <kf-skeleton width=\"60%\" height=\"1.25rem\" />\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n </kf-card>\n }\n </div>\n }\n @case ('form') {\n <div\n class=\"kf-skeleton-preset__form\"\n [class.kf-skeleton-preset__form--two]=\"formColumns() === 2\"\n aria-hidden=\"true\"\n >\n @for (i of items(); track i) {\n <div class=\"kf-skeleton-preset__field\">\n <span\n class=\"kf-skeleton-preset__label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Etiqueta\n </span>\n <span class=\"kf-skeleton-preset__control\">\n <span class=\"kf-skeleton-preset__value kf-bone\" style=\"--kf-bone-width: 55%\"\n >Valor</span\n >\n </span>\n </div>\n }\n </div>\n }\n @case ('list') {\n <div class=\"kf-skeleton-preset__stack\">\n @for (i of items(); track i) {\n <kf-document-row [loading]=\"true\" title=\"Documento\" meta=\"Archivo \u00B7 120 KB\" />\n }\n </div>\n }\n @case ('table') {\n <kf-data-table\n [columns]=\"tableColumns()\"\n [rows]=\"[]\"\n [loading]=\"true\"\n [skeletonRows]=\"total()\"\n />\n }\n @case ('timeline') {\n <div class=\"kf-skeleton-preset__timeline\" aria-hidden=\"true\">\n <div class=\"kf-skeleton-preset__timeline-toolbar\">\n <span class=\"kf-skeleton-preset__timeline-heading kf-bone\" style=\"--kf-bone-width: 30%\">\n Etapas\n </span>\n <span class=\"kf-skeleton-preset__timeline-toggle kf-bone\" style=\"--kf-bone-width: 7rem\">\n Expandir todo\n </span>\n </div>\n <ol class=\"kf-skeleton-preset__timeline-list\">\n @for (i of items(); track i) {\n <li class=\"kf-skeleton-preset__timeline-group\">\n <span class=\"kf-skeleton-preset__timeline-node kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__timeline-text\">\n <span\n class=\"kf-skeleton-preset__timeline-title kf-bone\"\n style=\"--kf-bone-width: 45%\"\n >\n Etapa\n </span>\n <span\n class=\"kf-skeleton-preset__timeline-meta kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Detalle\n </span>\n </span>\n </li>\n }\n </ol>\n </div>\n }\n @case ('detail-header') {\n <div class=\"kf-skeleton-preset__header\" aria-hidden=\"true\">\n <span class=\"kf-skeleton-preset__header-icon kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__header-heading\">\n <span class=\"kf-skeleton-preset__header-title kf-bone\" style=\"--kf-bone-width: 40%\">\n T\u00EDtulo\n </span>\n <span class=\"kf-skeleton-preset__header-subtitle kf-bone\" style=\"--kf-bone-width: 60%\">\n Subt\u00EDtulo\n </span>\n </span>\n </div>\n }\n }\n</div>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}.kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-skeleton-preset__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,15rem),1fr));gap:var(--kf-space-md)}.kf-skeleton-preset__grid--fixed{grid-template-columns:repeat(var(--kf-skeleton-preset-columns),minmax(0,1fr))}.kf-skeleton-preset__stack{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__form{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--kf-space-md)}.kf-skeleton-preset__form--two{grid-template-columns:repeat(2,minmax(0,1fr))}@media(max-width:640px){.kf-skeleton-preset__form--two{grid-template-columns:minmax(0,1fr)}}.kf-skeleton-preset__field{display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-skeleton-preset__control{display:block;padding:var(--kf-space-sm) var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-skeleton-preset__timeline{--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-heading{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title)}.kf-skeleton-preset__timeline-toggle{box-sizing:border-box;padding:6px var(--kf-space-sm);border:1px solid transparent;font-size:.85rem;font-weight:600;border-radius:var(--kf-radius-sm)}@media(max-width:640px),(pointer:coarse){.kf-skeleton-preset__timeline-toggle{min-height:44px}}.kf-skeleton-preset__timeline-list{list-style:none;margin:0;padding:0}.kf-skeleton-preset__timeline-group{position:relative;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter)}.kf-skeleton-preset__timeline-group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border)}.kf-skeleton-preset__timeline-node{flex:none;width:var(--_node);height:var(--_node);border-radius:50%}.kf-skeleton-preset__timeline-text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-skeleton-preset__timeline-title{font-weight:700;font-size:var(--kf-font-size-body)}.kf-skeleton-preset__timeline-meta{font-size:var(--kf-font-size-body-sm)}.kf-skeleton-preset__header{display:flex;align-items:center;gap:var(--kf-space-md);padding-bottom:var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-skeleton-preset__header-icon{flex:none;width:52px;height:52px;border:1px solid transparent;border-radius:var(--kf-radius-md)}.kf-skeleton-preset__header-heading{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}.kf-skeleton-preset__header-title{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);line-height:1.2}.kf-skeleton-preset__header-subtitle{font-size:.9375rem}@media(max-width:900px){.kf-skeleton-preset__header-icon{width:40px;height:40px}.kf-skeleton-preset__header-title{font-size:var(--kf-font-size-title-sm)}.kf-skeleton-preset__header-subtitle{font-size:.8125rem}}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap", "loading"] }, { kind: "component", type: DataTable, selector: "kf-data-table", inputs: ["columns", "rows", "loading", "skeletonRows", "rowKey", "flush", "cardable", "defaultView", "showViewToggle", "cardMinWidth", "render", "cellTemplates", "density", "rowClickable", "cellAriaLabel", "ariaLabel", "currentRowKey", "pageSize", "pageSizeOptions", "page", "keepPageOnRowsChange", "itemLabel", "sort", "view"], outputs: ["rowClick", "cellClick", "pageChange", "pageSizeChange", "sortChange", "viewChange"] }, { kind: "component", type: DocumentRow, selector: "kf-document-row", inputs: ["icon", "tone", "title", "meta", "href", "downloadLabel", "pendingLabel", "loading"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: StatCard, selector: "kf-stat-card", inputs: ["icon", "tone", "value", "label", "note", "bar", "hint", "pressed", "loading", "clickable"], outputs: ["cardClick"] }] });
4277
+ }
4278
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SkeletonPreset, decorators: [{
4279
+ type: Component,
4280
+ args: [{ selector: 'kf-skeleton-preset', imports: [Card, DataTable, DocumentRow, Skeleton, StatCard], template: "<div\n [class]=\"'kf-skeleton-preset kf-skeleton-preset--' + variant()\"\n aria-busy=\"true\"\n [style.--kf-skeleton-preset-columns]=\"columns() ?? null\"\n>\n @if (label()) {\n <span class=\"kf-sr-only\" role=\"status\">{{ label() }}</span>\n }\n @switch (variant()) {\n @case ('kpi-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-stat-card [loading]=\"true\" icon=\"bell\" value=\"0\" label=\"Etiqueta\" note=\"Nota\" />\n }\n </div>\n }\n @case ('card-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-card gap=\"sm\" aria-hidden=\"true\">\n <kf-skeleton variant=\"rect\" height=\"7.5rem\" />\n <kf-skeleton width=\"60%\" height=\"1.25rem\" />\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n </kf-card>\n }\n </div>\n }\n @case ('form') {\n <div\n class=\"kf-skeleton-preset__form\"\n [class.kf-skeleton-preset__form--two]=\"formColumns() === 2\"\n aria-hidden=\"true\"\n >\n @for (i of items(); track i) {\n <div class=\"kf-skeleton-preset__field\">\n <span\n class=\"kf-skeleton-preset__label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Etiqueta\n </span>\n <span class=\"kf-skeleton-preset__control\">\n <span class=\"kf-skeleton-preset__value kf-bone\" style=\"--kf-bone-width: 55%\"\n >Valor</span\n >\n </span>\n </div>\n }\n </div>\n }\n @case ('list') {\n <div class=\"kf-skeleton-preset__stack\">\n @for (i of items(); track i) {\n <kf-document-row [loading]=\"true\" title=\"Documento\" meta=\"Archivo \u00B7 120 KB\" />\n }\n </div>\n }\n @case ('table') {\n <kf-data-table\n [columns]=\"tableColumns()\"\n [rows]=\"[]\"\n [loading]=\"true\"\n [skeletonRows]=\"total()\"\n />\n }\n @case ('timeline') {\n <div class=\"kf-skeleton-preset__timeline\" aria-hidden=\"true\">\n <div class=\"kf-skeleton-preset__timeline-toolbar\">\n <span class=\"kf-skeleton-preset__timeline-heading kf-bone\" style=\"--kf-bone-width: 30%\">\n Etapas\n </span>\n <span class=\"kf-skeleton-preset__timeline-toggle kf-bone\" style=\"--kf-bone-width: 7rem\">\n Expandir todo\n </span>\n </div>\n <ol class=\"kf-skeleton-preset__timeline-list\">\n @for (i of items(); track i) {\n <li class=\"kf-skeleton-preset__timeline-group\">\n <span class=\"kf-skeleton-preset__timeline-node kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__timeline-text\">\n <span\n class=\"kf-skeleton-preset__timeline-title kf-bone\"\n style=\"--kf-bone-width: 45%\"\n >\n Etapa\n </span>\n <span\n class=\"kf-skeleton-preset__timeline-meta kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Detalle\n </span>\n </span>\n </li>\n }\n </ol>\n </div>\n }\n @case ('detail-header') {\n <div class=\"kf-skeleton-preset__header\" aria-hidden=\"true\">\n <span class=\"kf-skeleton-preset__header-icon kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__header-heading\">\n <span class=\"kf-skeleton-preset__header-title kf-bone\" style=\"--kf-bone-width: 40%\">\n T\u00EDtulo\n </span>\n <span class=\"kf-skeleton-preset__header-subtitle kf-bone\" style=\"--kf-bone-width: 60%\">\n Subt\u00EDtulo\n </span>\n </span>\n </div>\n }\n }\n</div>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}.kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-skeleton-preset__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,15rem),1fr));gap:var(--kf-space-md)}.kf-skeleton-preset__grid--fixed{grid-template-columns:repeat(var(--kf-skeleton-preset-columns),minmax(0,1fr))}.kf-skeleton-preset__stack{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__form{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--kf-space-md)}.kf-skeleton-preset__form--two{grid-template-columns:repeat(2,minmax(0,1fr))}@media(max-width:640px){.kf-skeleton-preset__form--two{grid-template-columns:minmax(0,1fr)}}.kf-skeleton-preset__field{display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-skeleton-preset__control{display:block;padding:var(--kf-space-sm) var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-skeleton-preset__timeline{--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-heading{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title)}.kf-skeleton-preset__timeline-toggle{box-sizing:border-box;padding:6px var(--kf-space-sm);border:1px solid transparent;font-size:.85rem;font-weight:600;border-radius:var(--kf-radius-sm)}@media(max-width:640px),(pointer:coarse){.kf-skeleton-preset__timeline-toggle{min-height:44px}}.kf-skeleton-preset__timeline-list{list-style:none;margin:0;padding:0}.kf-skeleton-preset__timeline-group{position:relative;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter)}.kf-skeleton-preset__timeline-group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border)}.kf-skeleton-preset__timeline-node{flex:none;width:var(--_node);height:var(--_node);border-radius:50%}.kf-skeleton-preset__timeline-text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-skeleton-preset__timeline-title{font-weight:700;font-size:var(--kf-font-size-body)}.kf-skeleton-preset__timeline-meta{font-size:var(--kf-font-size-body-sm)}.kf-skeleton-preset__header{display:flex;align-items:center;gap:var(--kf-space-md);padding-bottom:var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-skeleton-preset__header-icon{flex:none;width:52px;height:52px;border:1px solid transparent;border-radius:var(--kf-radius-md)}.kf-skeleton-preset__header-heading{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}.kf-skeleton-preset__header-title{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);line-height:1.2}.kf-skeleton-preset__header-subtitle{font-size:.9375rem}@media(max-width:900px){.kf-skeleton-preset__header-icon{width:40px;height:40px}.kf-skeleton-preset__header-title{font-size:var(--kf-font-size-title-sm)}.kf-skeleton-preset__header-subtitle{font-size:.8125rem}}\n"] }]
4281
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
4282
+
4283
+ /**
4284
+ * Marca el `ng-template` que reemplaza el esqueleto genérico de
4285
+ * `kf-content-state` mientras carga (`state="loading"`, `load="skeleton"`).
4286
+ *
4287
+ * <kf-content-state [state]="state()">
4288
+ * <ng-template kfContentStateLoading><kf-skeleton-preset variant="kpi-grid" /></ng-template>
4289
+ * …
4290
+ * </kf-content-state>
4291
+ */
4292
+ class ContentStateLoading {
4293
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentStateLoading, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4294
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: ContentStateLoading, isStandalone: true, selector: "ng-template[kfContentStateLoading]", ngImport: i0 });
4295
+ }
4296
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentStateLoading, decorators: [{
4297
+ type: Directive,
4298
+ args: [{ selector: 'ng-template[kfContentStateLoading]' }]
4299
+ }] });
4300
+ /**
4301
+ * Wrapper de los cuatro estados que toda sección tiene: cargando, con datos,
4302
+ * vacía y con error. Un componente en vez de tres `@if` sueltos, porque es
4303
+ * siempre el mismo slot el que va rotando entre los cuatro.
4304
+ *
4305
+ * Con `state="ready"` proyecta el contenido real y desaparece; en los otros
4306
+ * tres dibuja el placeholder correspondiente y el contenido no se instancia.
4307
+ * No inventa visuales propios: `loading` usa `kf-skeleton` (o `kf-spinner` con
4308
+ * `load="spinner"`), y `empty`/`error` usan `kf-empty-state`.
4309
+ *
4310
+ * Las acciones (reintentar, crear) van en el slot `actions` — se ignoran
4311
+ * mientras carga, porque no hay nada que reintentar todavía.
4312
+ *
4313
+ * <kf-content-state [state]="state()" title="No pudimos cargar tus vehículos"
4314
+ * subtitle="Revisá tu conexión e intentá de nuevo.">
4315
+ * <kf-button actions (click)="retry()">Reintentar</kf-button>
4316
+ * <kf-data-table [columns]="cols" [rows]="rows()" />
4317
+ * </kf-content-state>
4318
+ *
4319
+ * Esqueleto de carga: por defecto son tres líneas genéricas. Para que la
4320
+ * carga se parezca a lo que va a aparecer, hay tres vías (de la más corta a la
4321
+ * más libre): `loadingPreset` (una variante de `kf-skeleton-preset`), el input
4322
+ * `loadingTemplate` (un `TemplateRef`) o un `ng-template` marcado con
4323
+ * `kfContentStateLoading` dentro del contenido. Si hay más de una, gana la
4324
+ * plantilla proyectada, luego `loadingTemplate` y por último `loadingPreset`.
4325
+ *
4326
+ * <kf-content-state [state]="state()" loadingPreset="kpi-grid">…</kf-content-state>
4327
+ *
4328
+ * Accesibilidad: en modo skeleton el contenedor lleva `aria-busy="true"` y un
4329
+ * texto visualmente oculto con `role="status"` (`loadingLabel`, "Cargando…" por
4330
+ * defecto) que anuncia el estado una vez. Con `load="spinner"` el texto es el
4331
+ * del propio `kf-spinner`. El contenedor reserva un ancho mínimo, así que el
4332
+ * esqueleto no colapsa a 0 dentro de un padre que se ajusta a su contenido.
4333
+ */
4334
+ class ContentState {
4335
+ state = input.required(/* @ts-ignore */
4336
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
4337
+ /** Forma del estado de carga: silueta (default) o spinner. */
4338
+ load = input('skeleton', /* @ts-ignore */
4339
+ ...(ngDevMode ? [{ debugName: "load" }] : /* istanbul ignore next */ []));
4340
+ /** Título de `empty`/`error`. Ignorado mientras carga. */
4341
+ title = input('', /* @ts-ignore */
4342
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4343
+ subtitle = input('', /* @ts-ignore */
4344
+ ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
4345
+ /** Texto de carga: al lado del spinner con `load="spinner"` y, en modo skeleton,
4346
+ * texto visualmente oculto con `role="status"` para lectores de pantalla. */
4347
+ loadingLabel = input('Cargando…', /* @ts-ignore */
4348
+ ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
4349
+ /** Plantilla propia del esqueleto (reemplaza las tres líneas genéricas). */
4350
+ loadingTemplate = input(null, /* @ts-ignore */
4351
+ ...(ngDevMode ? [{ debugName: "loadingTemplate" }] : /* istanbul ignore next */ []));
4352
+ /** Variante de `kf-skeleton-preset` como esqueleto de carga. */
4353
+ loadingPreset = input(null, /* @ts-ignore */
4354
+ ...(ngDevMode ? [{ debugName: "loadingPreset" }] : /* istanbul ignore next */ []));
4355
+ /** `ng-template[kfContentStateLoading]` proyectado como contenido. */
4356
+ projectedLoading = contentChild(ContentStateLoading, { ...(ngDevMode ? { debugName: "projectedLoading" } : /* istanbul ignore next */ {}), read: TemplateRef });
4357
+ customLoading = computed(() => this.projectedLoading() ?? this.loadingTemplate(), /* @ts-ignore */
4358
+ ...(ngDevMode ? [{ debugName: "customLoading" }] : /* istanbul ignore next */ []));
4359
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, deps: [], target: i0.ɵɵFactoryTarget.Component });
4360
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentState, isStandalone: true, selector: "kf-content-state", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, load: { classPropertyName: "load", publicName: "load", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null }, loadingTemplate: { classPropertyName: "loadingTemplate", publicName: "loadingTemplate", isSignal: true, isRequired: false, transformFunction: null }, loadingPreset: { classPropertyName: "loadingPreset", publicName: "loadingPreset", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedLoading", first: true, predicate: ContentStateLoading, descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <span class=\"kf-sr-only\" role=\"status\">{{ loadingLabel() }}</span>\n @if (customLoading(); as template) {\n <ng-container [ngTemplateOutlet]=\"template\" />\n } @else if (loadingPreset(); as preset) {\n <kf-skeleton-preset [variant]=\"preset\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm);min-width:min(12rem,100vw);box-sizing:border-box}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: EmptyState, selector: "kf-empty-state", inputs: ["icon", "tone", "variant", "title"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: SkeletonPreset, selector: "kf-skeleton-preset", inputs: ["variant", "count", "columns", "label"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
4361
+ }
4362
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, decorators: [{
4363
+ type: Component,
4364
+ args: [{ selector: 'kf-content-state', imports: [EmptyState, NgTemplateOutlet, Skeleton, SkeletonPreset, Spinner], template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <span class=\"kf-sr-only\" role=\"status\">{{ loadingLabel() }}</span>\n @if (customLoading(); as template) {\n <ng-container [ngTemplateOutlet]=\"template\" />\n } @else if (loadingPreset(); as preset) {\n <kf-skeleton-preset [variant]=\"preset\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm);min-width:min(12rem,100vw);box-sizing:border-box}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
4365
+ }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], load: [{ type: i0.Input, args: [{ isSignal: true, alias: "load", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], loadingTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingTemplate", required: false }] }], loadingPreset: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingPreset", required: false }] }], projectedLoading: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ContentStateLoading), { ...{
4366
+ read: TemplateRef,
4367
+ }, isSignal: true }] }] } });
4368
+
4369
+ const RAW_BASE64_MIME = {
4370
+ image: 'image/jpeg',
4371
+ video: 'video/mp4',
4372
+ pdf: 'application/pdf',
4373
+ };
4374
+ /** Solo contenido ya en memoria: un `<iframe>` con una URL remota arbitraria podría cargar cualquier página. */
4375
+ const LOCAL_PDF_SRC = /^(blob:|data:application\/pdf[;,])/i;
4376
+ let contentViewerId = 0;
4377
+ /**
4378
+ * Visor a pantalla completa para ampliar una pieza de contenido: imagen,
4379
+ * video o PDF (`type="pdf"`, renderizado por el visor nativo del navegador).
4380
+ * Mismo mecanismo que `kf-modal` (el `<dialog>` nativo resuelve backdrop,
4381
+ * atrapado de foco, top-layer y `Escape`), pero sin panel/título/footer:
4382
+ * el contenido ocupa toda la pantalla sobre un backdrop oscuro.
4383
+ *
4384
+ * `src` acepta lo que venga del backend sin que el consumidor tenga que
4385
+ * normalizarlo primero: una URL (`http(s)://`), un data URI ya armado
4386
+ * (`data:...`), o base64 crudo (se le antepone el prefijo `data:` según
4387
+ * `type`, asumiendo JPEG para imagen, MP4 para video y PDF para pdf). Un PDF
4388
+ * solo acepta contenido local (`blob:`, data URI o base64), nunca una URL remota.
4389
+ *
4390
+ * Lo proyectado con el atributo `actions` (p. ej. un botón de descarga) va en
4391
+ * la barra superior, junto al título.
4392
+ *
4393
+ * <kf-content-viewer [(open)]="viewerOpen" [src]="viewerSrc()" alt="Foto del vehículo" />
4394
+ * <kf-content-viewer [(open)]="viewerOpen" type="video" [src]="viewerSrc()" title="Inspección" />
4395
+ * <kf-content-viewer [(open)]="viewerOpen" type="pdf" [src]="blobUrl()" title="Contrato">
4396
+ * <kf-button actions variant="surface" icon="download" (click)="save()">Descargar</kf-button>
4397
+ * </kf-content-viewer>
4398
+ */
4399
+ class ContentViewer {
4400
+ type = input('image', /* @ts-ignore */
4401
+ ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
4402
+ /** URL, data URI, o base64 crudo — ver JSDoc de la clase. */
4403
+ src = input(null, /* @ts-ignore */
4404
+ ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
4405
+ /** Texto alternativo cuando `type="image"`. */
4406
+ alt = input('', /* @ts-ignore */
4407
+ ...(ngDevMode ? [{ debugName: "alt" }] : /* istanbul ignore next */ []));
4408
+ title = input('', /* @ts-ignore */
4409
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4410
+ /** Visibilidad, two-way: `[(open)]="viewerOpen"`. */
4411
+ open = model(false, /* @ts-ignore */
4412
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
4413
+ /** Permite cerrar con el botón, `Escape` o clic en el backdrop. */
4414
+ dismissible = input(true, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4415
+ titleId = `kf-content-viewer-title-${contentViewerId++}`;
4416
+ /** Detecta si `src` ya es una URL o un data URI armado; si no, asume
4417
+ * base64 crudo y le antepone el prefijo `data:` correspondiente a `type`
4418
+ * — el backend a veces manda una cosa, a veces la otra. */
4419
+ resolvedSrc = computed(() => {
4420
+ const raw = this.src();
4421
+ if (!raw)
4422
+ return null;
4423
+ if (/^(https?:|data:|blob:)/i.test(raw))
4424
+ return raw;
4425
+ return `data:${RAW_BASE64_MIME[this.type()]};base64,${raw}`;
4426
+ }, /* @ts-ignore */
4427
+ ...(ngDevMode ? [{ debugName: "resolvedSrc" }] : /* istanbul ignore next */ []));
4428
+ pdfSrc = computed(() => {
4429
+ const src = this.resolvedSrc();
4430
+ if (this.type() !== 'pdf' || !src || !LOCAL_PDF_SRC.test(src))
4431
+ return null;
4432
+ return this.sanitizer.bypassSecurityTrustResourceUrl(src);
4433
+ }, /* @ts-ignore */
4434
+ ...(ngDevMode ? [{ debugName: "pdfSrc" }] : /* istanbul ignore next */ []));
4435
+ fallbackLabel = computed(() => ({ image: 'Visor de imagen', video: 'Visor de video', pdf: 'Visor de documento' })[this.type()], /* @ts-ignore */
4436
+ ...(ngDevMode ? [{ debugName: "fallbackLabel" }] : /* istanbul ignore next */ []));
4437
+ dialog = viewChild('dialog', /* @ts-ignore */
4438
+ ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
4439
+ document = inject(DOCUMENT);
4440
+ sanitizer = inject(DomSanitizer);
4441
+ constructor() {
4442
+ afterRenderEffect(() => {
4443
+ const el = this.dialog()?.nativeElement;
4444
+ if (!el) {
4445
+ return;
4446
+ }
4447
+ if (this.open() && !el.open) {
4448
+ // `showModal` no existe en jsdom (tests) ni en entornos sin
4449
+ // top-layer; el atributo `open` lo deja visible, aunque no modal.
4450
+ if (typeof el.showModal === 'function') {
4451
+ el.showModal();
4452
+ }
4453
+ else {
4454
+ el.open = true;
4455
+ }
4456
+ }
4457
+ else if (!this.open() && el.open) {
4458
+ if (typeof el.close === 'function') {
4459
+ el.close();
4460
+ }
4461
+ else {
4462
+ el.open = false;
4463
+ }
4464
+ }
4465
+ // `<dialog>` inertiza el resto de la página pero no frena su scroll.
4466
+ this.document.body.style.overflow = this.open() ? 'hidden' : '';
4467
+ });
4468
+ inject(DestroyRef).onDestroy(() => {
4469
+ this.document.body.style.overflow = '';
4470
+ });
4471
+ }
4472
+ /** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
4473
+ onCancel(event) {
4474
+ if (!this.dismissible()) {
4475
+ event.preventDefault();
4476
+ }
4477
+ }
4478
+ /**
4479
+ * Clic en el backdrop. Un clic en la zona del backdrop tiene como `target` al
4480
+ * propio `<dialog>` (el contenido vive en el panel de adentro), así que basta
4481
+ * comparar contra él — no hace falta cortar la propagación en el panel.
4482
+ */
4483
+ onHostClick(event) {
4484
+ if (this.dismissible() && event.target === this.dialog()?.nativeElement) {
4485
+ this.open.set(false);
4486
+ }
4487
+ }
4488
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
4489
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentViewer, isStandalone: true, selector: "kf-content-viewer", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "click": "onHostClick($event)" } }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
4490
+ }
4491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, decorators: [{
4492
+ type: Component,
4493
+ args: [{ selector: 'kf-content-viewer', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"] }]
4494
+ }], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], onHostClick: [{
4495
+ type: HostListener,
4496
+ args: ['click', ['$event']]
4497
+ }] } });
4498
+
4499
+ /**
4500
+ * Signal con la vista de `kf-data-table` ('table' | 'cards') derivada de un
4501
+ * breakpoint — mismo criterio que cualquier tabla con muchas columnas: bajo
4502
+ * el ancho dado, la vista de tabla no entra sin scroll horizontal, así que
4503
+ * se fuerza `cards`. Centraliza el patrón `matchMediaSignal` +
4504
+ * `computed<DataTableView>` que cada tabla del portal repetía igual.
4505
+ *
4506
+ * protected readonly tableView = dataTableViewSignal();
4507
+ * // con un breakpoint distinto al default de 900px:
4508
+ * protected readonly tableView = dataTableViewSignal('(max-width: 768px)');
4509
+ */
4510
+ function dataTableViewSignal(query = '(max-width: 900px)') {
4511
+ const isMobile = matchMediaSignal(query);
4512
+ return computed(() => (isMobile() ? 'cards' : 'table'));
4513
+ }
4514
+
4515
+ /**
4516
+ * Separador visual simple entre bloques de contenido. `orientation`
4517
+ * controla si es una línea horizontal (para separar secciones apiladas) o
4518
+ * vertical (para separar elementos en una fila, ej. acciones en una toolbar,
4519
+ * o las columnas de metadata en `kf-company-selector`).
4520
+ *
4521
+ * En `vertical`, el divisor necesita una altura del contexto donde vive —
4522
+ * `:host { align-self: stretch }` lo hace ocupar la altura del contenedor
4523
+ * flex/grid padre sin que el consumidor tenga que fijarla a mano. `flex:
4524
+ * none` evita que el propio divisor se achique a 0 dentro de ese contenedor
4525
+ * flex cuando hay contenido hermano compitiendo por espacio.
4526
+ *
4527
+ * <kf-divider />
4528
+ * <kf-divider orientation="vertical" />
4030
4529
  */
4031
4530
  class Divider {
4032
4531
  orientation = input('horizontal', /* @ts-ignore */
@@ -4044,78 +4543,126 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4044
4543
  }], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }] } });
4045
4544
 
4046
4545
  /**
4047
- * Acción de bajo énfasis con apariencia de texto — "¿Olvidaste tu
4048
- * contraseña?", "Volver a iniciar sesión", o un link real de navegación
4049
- * como "Ir a pagos →".
4546
+ * Contenedores modales abiertos actualmente: un `<dialog>` abierto vía
4547
+ * `showModal()` (`kf-modal`) o el panel de un `kf-drawer` con
4548
+ * `role="dialog"` (`aria-modal="true"`), en orden de apertura. Con diálogos
4549
+ * anidados el navegador apila el top layer: el más reciente queda arriba e
4550
+ * inertiza a los anteriores (incluidos otros `<dialog>` ya abiertos), así
4551
+ * que el ÚLTIMO de esta lista es el único candidato válido para recibir el
4552
+ * overlay container — nunca "cualquier `dialog[open]` de la página", que
4553
+ * rompería con dos `kf-modal` anidados o con dos independientes donde solo
4554
+ * uno está abierto.
4555
+ */
4556
+ const openModalDialogs = [];
4557
+ /**
4558
+ * Reubicadores de los `OverlayContainer` parchados. Cada cambio en el
4559
+ * registro los ejecuta, así el contenedor vuelve a `<body>` (o pasa al
4560
+ * siguiente contenedor modal) al cerrar, sin esperar a que otro overlay
4561
+ * llame a `getContainerElement()`: un panel con el `overlayRef` ya creado
4562
+ * que se vuelve a abrir no pasa por esa llamada, y si el contenedor seguía
4563
+ * dentro de un diálogo cerrado quedaba invisible.
4564
+ */
4565
+ const relocators = new Set();
4566
+ function relocateContainers() {
4567
+ for (const relocate of [...relocators]) {
4568
+ if (!relocate()) {
4569
+ relocators.delete(relocate);
4570
+ }
4571
+ }
4572
+ }
4573
+ /** Llamado por `Modal` cuando su `<dialog>` pasa a estar abierto, y por `Drawer` (`role="dialog"`) con su panel. */
4574
+ function registerOpenModalDialog(dialog) {
4575
+ if (!openModalDialogs.includes(dialog)) {
4576
+ openModalDialogs.push(dialog);
4577
+ relocateContainers();
4578
+ }
4579
+ }
4580
+ /** Llamado por `Modal`/`Drawer` cuando su contenedor se cierra o el componente se destruye. */
4581
+ function unregisterOpenModalDialog(dialog) {
4582
+ const index = openModalDialogs.indexOf(dialog);
4583
+ if (index !== -1) {
4584
+ openModalDialogs.splice(index, 1);
4585
+ relocateContainers();
4586
+ }
4587
+ }
4588
+ function topmostOpenModalDialog() {
4589
+ return openModalDialogs.at(-1);
4590
+ }
4591
+ /**
4592
+ * Instancias de `OverlayContainer` ya parchadas — evita parchar dos veces
4593
+ * el mismo objeto (varios `kf-modal` en el árbol llaman a esta función) y,
4594
+ * a la vez, no interfiere con instancias nuevas que arma un `TestBed` fresco
4595
+ * en cada spec (root providers se recrean por test).
4596
+ */
4597
+ const patchedContainers = new WeakSet();
4598
+ /**
4599
+ * `showModal()` promueve el `<dialog>` al top layer del navegador y vuelve
4600
+ * `inert` todo lo que quede fuera de su subárbol — pero Angular CDK
4601
+ * (`Overlay`/`OverlayContainer`, usado por `kf-select`, `kf-multi-select`,
4602
+ * `kf-dropdown`, `kf-tooltip` y el selector de tamaño de `kf-pagination`)
4603
+ * siempre appendea `.cdk-overlay-container` como hijo de `<body>`, que
4604
+ * queda fuera del `<dialog>` y por lo tanto inerte: el overlay se ve pero
4605
+ * no recibe clicks.
4050
4606
  *
4051
- * Sin `href` (default) es un `<button type="button">`: dispara una acción
4052
- * en la misma pantalla, vía `(click)`. Con `href`, es un `<a>` real: navega
4053
- * a otra URL. `showArrow` agrega un ícono `chevron-right` al final — para
4054
- * el patrón "texto + flecha" de un link que lleva a otro lado.
4607
+ * No existe una forma de reemplazar, sin que el consumidor configure nada,
4608
+ * QUÉ IMPLEMENTACIÓN resuelve Angular DI para el token `OverlayContainer`:
4609
+ * es `providedIn: 'root'`, y la instancia se crea una sola vez contra el
4610
+ * inyector RAÍZ de la app la primera vez que algo la necesita — incluida
4611
+ * la propia `Overlay`, que la inyecta puertas adentro de SU construcción,
4612
+ * no en el inyector del componente que gatilló esa construcción. Un
4613
+ * `providers` a nivel de componente (`kf-modal`, o incluso `kf-select`)
4614
+ * nunca llega a interceptar esa creación; solo un `providers` explícito en
4615
+ * el bootstrap de cada app lo haría, y eso es justo la configuración manual
4616
+ * que se quiere evitar.
4055
4617
  *
4056
- * `variant="default"` (color primario, subrayado en hover) es para la
4057
- * acción de mayor jerarquía relativa de la pantalla; `variant="muted"`
4058
- * (gris que pasa a primario en hover) es para acciones de retroceso/salida.
4618
+ * Por eso el fix no reemplaza la clase vía DI: parcha, una sola vez, el
4619
+ * MISMO objeto singleton que ya usa toda la app (la instancia real que
4620
+ * `Modal` recibe con un `inject(OverlayContainer)` normal — automático
4621
+ * apenas se usa `kf-modal`, sin tocar ningún `providers`). `getContainerElement`
4622
+ * sigue delegando en el original para crear/cachear el div la primera vez;
4623
+ * lo único que se agrega es reubicarlo, en cada llamada, dentro del
4624
+ * `<dialog>` abierto más reciente (o de vuelta en `<body>` si no hay
4625
+ * ninguno) antes de devolverlo — mover el div se lleva puestos a sus hijos,
4626
+ * así que overlays ya adjuntos antes de abrir el modal quedan igual de
4627
+ * bien ubicados que los que se abren después.
4059
4628
  *
4060
- * <kf-link (click)="onForgotPassword()">¿Olvidaste tu contraseña?</kf-link>
4061
- * <kf-link variant="muted" (click)="onBack()">← Volver a iniciar sesión</kf-link>
4062
- * <kf-link href="/pagos" showArrow>Ir a pagos</kf-link>
4063
- */
4064
- class Link {
4065
- variant = input('default', /* @ts-ignore */
4066
- ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
4067
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4068
- /** Si se pasa, renderiza un `<a>` real en vez de `<button>` — navega en vez de disparar `(click)`. */
4069
- href = input(/* @ts-ignore */
4070
- ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
4071
- /** Ícono `chevron-right` al final — para el patrón "texto + flecha" de un link que lleva a otro lado. */
4072
- showArrow = input(false, { ...(ngDevMode ? { debugName: "showArrow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4073
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, deps: [], target: i0.ɵɵFactoryTarget.Component });
4074
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Link, isStandalone: true, selector: "kf-link", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, showArrow: { classPropertyName: "showArrow", publicName: "showArrow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
4075
- }
4076
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, decorators: [{
4077
- type: Component,
4078
- args: [{ selector: 'kf-link', imports: [Icon, NgTemplateOutlet], template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"] }]
4079
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], showArrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArrow", required: false }] }] } });
4080
-
4081
- /**
4082
- * Fila de un documento descargable: chip de ícono coloreado por tipo,
4083
- * título, metadata y la acción de descarga al final. Sin `href` rinde
4084
- * `pendingLabel` en vez de un link roto. El contenido proyectado cae entre
4085
- * la metadata y la acción, como slot para un `kf-badge` de estado.
4629
+ * Un `kf-drawer` con `role="dialog"` no usa el top layer, pero declara
4630
+ * `aria-modal="true"`: un overlay fuera de su subárbol queda, para un lector
4631
+ * de pantalla, fuera del diálogo. Por eso también registra su panel acá y
4632
+ * el contenedor se reubica dentro de él mientras está abierto.
4086
4633
  */
4087
- class DocumentRow {
4088
- icon = input('file', /* @ts-ignore */
4089
- ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
4090
- tone = input('neutral', /* @ts-ignore */
4091
- ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
4092
- /** Qué documento es. El nombre humano del tipo, no el del archivo. */
4093
- title = input.required(/* @ts-ignore */
4094
- ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4095
- /** Línea secundaria — archivo, fecha, tamaño. El consumidor la arma ya
4096
- * formateada: la librería no decide en qué unidad se lee un tamaño ni en
4097
- * qué formato una fecha. */
4098
- meta = input(/* @ts-ignore */
4099
- ...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
4100
- /** Ausente rinde `pendingLabel` en vez del link de descarga. */
4101
- href = input(/* @ts-ignore */
4102
- ...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
4103
- downloadLabel = input('Descargar', /* @ts-ignore */
4104
- ...(ngDevMode ? [{ debugName: "downloadLabel" }] : /* istanbul ignore next */ []));
4105
- /** Qué se dice cuando no hay `href`. */
4106
- pendingLabel = input('En generación', /* @ts-ignore */
4107
- ...(ngDevMode ? [{ debugName: "pendingLabel" }] : /* istanbul ignore next */ []));
4108
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
4109
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DocumentRow, isStandalone: true, selector: "kf-document-row", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, downloadLabel: { classPropertyName: "downloadLabel", publicName: "downloadLabel", isSignal: true, isRequired: false, transformFunction: null }, pendingLabel: { classPropertyName: "pendingLabel", publicName: "pendingLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kf-document-row--\" + tone()" } }, ngImport: i0, template: "<span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n</span>\n\n<span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n</span>\n\n<span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n</span>\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
4634
+ function installModalAwareOverlayContainer(container) {
4635
+ if (patchedContainers.has(container)) {
4636
+ return;
4637
+ }
4638
+ patchedContainers.add(container);
4639
+ const originalGetContainerElement = container.getContainerElement.bind(container);
4640
+ let knownElement = null;
4641
+ container.getContainerElement = () => {
4642
+ const element = originalGetContainerElement();
4643
+ knownElement = element;
4644
+ const targetParent = topmostOpenModalDialog() ?? element.ownerDocument.body;
4645
+ if (element.parentElement !== targetParent) {
4646
+ targetParent.appendChild(element);
4647
+ }
4648
+ return element;
4649
+ };
4650
+ // Devuelve `false` cuando el contenedor ya no existe (la app se destruyó):
4651
+ // se descarta del conjunto en vez de recrearlo. Mientras nadie lo haya
4652
+ // pedido todavía no hay nada que reubicar.
4653
+ relocators.add(() => {
4654
+ if (!knownElement) {
4655
+ return true;
4656
+ }
4657
+ if (!knownElement.isConnected) {
4658
+ return false;
4659
+ }
4660
+ container.getContainerElement();
4661
+ return true;
4662
+ });
4110
4663
  }
4111
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, decorators: [{
4112
- type: Component,
4113
- args: [{ selector: 'kf-document-row', imports: [Icon, Link], host: {
4114
- '[class]': '"kf-document-row--" + tone()',
4115
- }, template: "<span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n</span>\n\n<span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n</span>\n\n<span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n</span>\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}\n"] }]
4116
- }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], downloadLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadLabel", required: false }] }], pendingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingLabel", required: false }] }] } });
4117
4664
 
4118
- let nextId$2 = 0;
4665
+ let nextId$3 = 0;
4119
4666
  /**
4120
4667
  * Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
4121
4668
  * ni qué es sesión, solo da tres lugares para proyectar contenido
@@ -4169,8 +4716,9 @@ let nextId$2 = 0;
4169
4716
  * redondeadas, alto máximo 80vh) — pensado para un resumen en mobile.
4170
4717
  *
4171
4718
  * Escape cierra el panel salvo que un control interno ya haya consumido la
4172
- * tecla (`event.defaultPrevented`, ej. un `kf-select` abierto dentro del
4173
- * drawer cierra solo su propio panel).
4719
+ * tecla (`event.defaultPrevented`): un `kf-select`, `kf-multi-select` o
4720
+ * `kf-dropdown` abierto dentro del drawer cierra primero su propio panel y
4721
+ * un segundo Escape cierra el drawer.
4174
4722
  *
4175
4723
  * Nombres por defecto: con `role="navigation"` el panel se llama "Menú" y
4176
4724
  * el cierre "Cerrar menú" (comportamiento histórico). Con `role="dialog"`
@@ -4178,12 +4726,25 @@ let nextId$2 = 0;
4178
4726
  * avisa por consola si no hay `title` ni `ariaLabel`: un diálogo sin nombre
4179
4727
  * propio no le dice nada al lector de pantalla.
4180
4728
  *
4181
- * Limitación conocida: NO colocar un `kf-select` (ni otro control con panel
4182
- * flotante del CDK) dentro de un drawer `role="dialog"`. Su listbox se
4183
- * monta en `.cdk-overlay-container`, fuera del diálogo con `aria-modal`: un
4184
- * lector de pantalla puede tratarlo como contenido inerte y la trampa de
4185
- * foco no lo incluye. Para elegir una opción dentro del panel usar
4186
- * `kf-option-list`.
4729
+ * ## Selectores y otros paneles flotantes dentro del drawer
4730
+ *
4731
+ * `kf-select`, `kf-multi-select` y `kf-dropdown` funcionan
4732
+ * dentro de un drawer (incluido el pie `[kf-drawer-footer]`): sus paneles
4733
+ * quedan por encima del drawer, se abren hacia arriba si abajo no hay lugar
4734
+ * y no se cortan con el borde del panel ni con el scroll del cuerpo. Con
4735
+ * `role="dialog"` el contenedor de overlays de CDK se reubica dentro del
4736
+ * panel mientras está abierto: el listbox queda dentro del diálogo
4737
+ * `aria-modal`, no fuera de él, y el atrapado de foco lo incluye. Al cerrar
4738
+ * el drawer vuelve a `<body>`. Escape cierra primero el panel flotante
4739
+ * (devolviendo el foco a su disparador) y el siguiente cierra el drawer.
4740
+ *
4741
+ * <kf-drawer [(open)]="open" role="dialog" side="right" title="Unidad VIN-1003">
4742
+ * ...
4743
+ * <div kf-drawer-footer>
4744
+ * <kf-select label="Saltar a otra unidad" width="full"
4745
+ * [options]="units" [value]="unit()" (valueChange)="unit.set($event)" />
4746
+ * </div>
4747
+ * </kf-drawer>
4187
4748
  *
4188
4749
  * Al cerrar, el foco vuelve al elemento que lo tenía al abrir solo si sigue
4189
4750
  * en el documento (`isConnected`); si fue desmontado mientras el drawer
@@ -4220,14 +4781,29 @@ class Drawer {
4220
4781
  /** `closeLabel` o, sin él, el default según el rol. */
4221
4782
  resolvedCloseLabel = computed(() => this.closeLabel() ?? (this.role() === 'dialog' ? 'Cerrar' : 'Cerrar menú'), /* @ts-ignore */
4222
4783
  ...(ngDevMode ? [{ debugName: "resolvedCloseLabel" }] : /* istanbul ignore next */ []));
4223
- titleId = `kf-drawer-title-${nextId$2++}`;
4784
+ titleId = `kf-drawer-title-${nextId$3++}`;
4224
4785
  panelRef = viewChild.required('panel', /* @ts-ignore */
4225
4786
  ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
4226
4787
  focusTrapFactory = inject(FocusTrapFactory);
4227
4788
  document = inject(DOCUMENT);
4789
+ overlayContainer = inject(OverlayContainer);
4790
+ registeredPanel = null;
4228
4791
  focusTrap = null;
4229
4792
  lastFocusedElement = null;
4230
4793
  constructor() {
4794
+ // Ver `modal-overlay-container.ts`: con `role="dialog"` los paneles
4795
+ // flotantes (select, dropdown) deben vivir dentro del diálogo.
4796
+ installModalAwareOverlayContainer(this.overlayContainer);
4797
+ afterRenderEffect(() => {
4798
+ const panel = this.panelRef().nativeElement;
4799
+ this.registeredPanel = panel;
4800
+ if (this.open() && this.role() === 'dialog') {
4801
+ registerOpenModalDialog(panel);
4802
+ }
4803
+ else {
4804
+ unregisterOpenModalDialog(panel);
4805
+ }
4806
+ });
4231
4807
  afterRenderEffect(() => {
4232
4808
  if (this.open()) {
4233
4809
  this.lastFocusedElement = this.document.activeElement;
@@ -4250,7 +4826,12 @@ class Drawer {
4250
4826
  }
4251
4827
  });
4252
4828
  }
4253
- inject(DestroyRef).onDestroy(() => this.focusTrap?.destroy());
4829
+ inject(DestroyRef).onDestroy(() => {
4830
+ if (this.registeredPanel) {
4831
+ unregisterOpenModalDialog(this.registeredPanel);
4832
+ }
4833
+ this.focusTrap?.destroy();
4834
+ });
4254
4835
  }
4255
4836
  /** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
4256
4837
  onEscape(event) {
@@ -4266,7 +4847,7 @@ class Drawer {
4266
4847
  return this.focusTrap;
4267
4848
  }
4268
4849
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
4269
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Drawer, isStandalone: true, selector: "kf-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" }, properties: { "attr.role": "null", "attr.title": "null" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:translate(0);visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__panel--bottom.kf-drawer__panel--open{transform:translateY(0)}@media(prefers-reduced-motion:reduce){.kf-drawer__panel,.kf-drawer__panel--open{transition:none}}.kf-drawer__grip{flex:none;align-self:center;width:36px;height:4px;margin-top:var(--kf-space-sm);border-radius:var(--kf-radius-pill);background:var(--kf-color-border-strong)}.kf-drawer__titlebar{flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-drawer__title{margin:0;min-width:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:700;line-height:1.25;color:var(--kf-color-ink)}.kf-drawer__close{flex:none}@media(pointer:coarse),(max-width:760px){.kf-drawer__close{--kf-button-min-height: 44px;min-width:44px}}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__panel--dialog .kf-drawer__body{gap:var(--kf-space-lg)}.kf-drawer__footer{flex:none;padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}.kf-drawer__panel--dialog .kf-drawer__footer{padding:var(--kf-space-md) var(--kf-space-lg)}\n"], dependencies: [{ kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }] });
4850
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Drawer, isStandalone: true, selector: "kf-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" }, properties: { "attr.role": "null", "attr.title": "null" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:none;visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__panel--bottom.kf-drawer__panel--open{transform:none}@media(prefers-reduced-motion:reduce){.kf-drawer__panel,.kf-drawer__panel--open{transition:none}}.kf-drawer__grip{flex:none;align-self:center;width:36px;height:4px;margin-top:var(--kf-space-sm);border-radius:var(--kf-radius-pill);background:var(--kf-color-border-strong)}.kf-drawer__titlebar{flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-drawer__title{margin:0;min-width:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:700;line-height:1.25;color:var(--kf-color-ink)}.kf-drawer__close{flex:none}@media(pointer:coarse),(max-width:760px){.kf-drawer__close{--kf-button-min-height: 44px;min-width:44px}}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__panel--dialog .kf-drawer__body{gap:var(--kf-space-lg)}.kf-drawer__footer{flex:none;padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}.kf-drawer__panel--dialog .kf-drawer__footer{padding:var(--kf-space-md) var(--kf-space-lg)}\n"], dependencies: [{ kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }] });
4270
4851
  }
4271
4852
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, decorators: [{
4272
4853
  type: Component,
@@ -4277,7 +4858,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4277
4858
  // un tooltip nativo sobre todo el panel. El rol real vive en el panel.
4278
4859
  '[attr.role]': 'null',
4279
4860
  '[attr.title]': 'null',
4280
- }, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:translate(0);visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__panel--bottom.kf-drawer__panel--open{transform:translateY(0)}@media(prefers-reduced-motion:reduce){.kf-drawer__panel,.kf-drawer__panel--open{transition:none}}.kf-drawer__grip{flex:none;align-self:center;width:36px;height:4px;margin-top:var(--kf-space-sm);border-radius:var(--kf-radius-pill);background:var(--kf-color-border-strong)}.kf-drawer__titlebar{flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-drawer__title{margin:0;min-width:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:700;line-height:1.25;color:var(--kf-color-ink)}.kf-drawer__close{flex:none}@media(pointer:coarse),(max-width:760px){.kf-drawer__close{--kf-button-min-height: 44px;min-width:44px}}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__panel--dialog .kf-drawer__body{gap:var(--kf-space-lg)}.kf-drawer__footer{flex:none;padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}.kf-drawer__panel--dialog .kf-drawer__footer{padding:var(--kf-space-md) var(--kf-space-lg)}\n"] }]
4861
+ }, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:none;visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__panel--bottom.kf-drawer__panel--open{transform:none}@media(prefers-reduced-motion:reduce){.kf-drawer__panel,.kf-drawer__panel--open{transition:none}}.kf-drawer__grip{flex:none;align-self:center;width:36px;height:4px;margin-top:var(--kf-space-sm);border-radius:var(--kf-radius-pill);background:var(--kf-color-border-strong)}.kf-drawer__titlebar{flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-drawer__title{margin:0;min-width:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:700;line-height:1.25;color:var(--kf-color-ink)}.kf-drawer__close{flex:none}@media(pointer:coarse),(max-width:760px){.kf-drawer__close{--kf-button-min-height: 44px;min-width:44px}}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__panel--dialog .kf-drawer__body{gap:var(--kf-space-lg)}.kf-drawer__footer{flex:none;padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}.kf-drawer__panel--dialog .kf-drawer__footer{padding:var(--kf-space-md) var(--kf-space-lg)}\n"] }]
4281
4862
  }], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onEscape: [{
4282
4863
  type: HostListener,
4283
4864
  args: ['document:keydown.escape', ['$event']]
@@ -4409,6 +4990,326 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4409
4990
  }, template: "@if (icon(); as iconName) {\n <kf-icon class=\"kf-field__icon\" [name]=\"iconName\" size=\"sm\" />\n}\n@if (label()) {\n <span class=\"kf-field__label\">{{ label() }}</span>\n}\n<span class=\"kf-field__value\">\n @if (value()) {\n {{ value() }}\n } @else {\n <ng-content></ng-content>\n @if (isPlaceholderVisible()) {\n <span class=\"kf-field__placeholder\">{{ placeholder() }}</span>\n }\n }\n</span>\n@if (select()) {\n <kf-icon class=\"kf-field__chevron\" name=\"chevron-down\" size=\"sm\" />\n}\n", styles: [":host{display:inline-flex;align-items:center;gap:var(--kf-space-xs);padding:var(--kf-space-xs) var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);line-height:1.2}:host(.kf-field--select){cursor:pointer}:host(.kf-field--select:hover){border-color:var(--kf-color-border-strong)}.kf-field__icon{color:var(--kf-color-text-muted)}.kf-field__label{color:var(--kf-color-text-muted);white-space:nowrap}.kf-field__value{font-weight:500;color:var(--kf-color-ink);white-space:nowrap}.kf-field__placeholder{color:var(--kf-color-text-muted);font-weight:400}.kf-field__chevron{color:var(--kf-color-text-muted)}\n"] }]
4410
4991
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], select: [{ type: i0.Input, args: [{ isSignal: true, alias: "select", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }] } });
4411
4992
 
4993
+ let nextId$2 = 0;
4994
+ /** Nombre legible de un token de `accept`: `.pdf` → `PDF`, `image/png` → `PNG`, `image/*` → `Imágenes`. */
4995
+ function describeAcceptToken(token) {
4996
+ const generic = {
4997
+ 'image/*': 'Imágenes',
4998
+ 'video/*': 'Videos',
4999
+ 'audio/*': 'Audio',
5000
+ 'text/*': 'Texto',
5001
+ };
5002
+ if (generic[token]) {
5003
+ return generic[token];
5004
+ }
5005
+ if (token.startsWith('.')) {
5006
+ return token.slice(1).toUpperCase();
5007
+ }
5008
+ return (token.split('/')[1] ?? token).toUpperCase();
5009
+ }
5010
+ /** Tamaño en bytes como texto (`5 MB`, `120 KB`), base 1024. */
5011
+ function formatFileSize(bytes) {
5012
+ if (bytes >= 1024 * 1024) {
5013
+ return `${Number((bytes / (1024 * 1024)).toFixed(1))} MB`;
5014
+ }
5015
+ if (bytes >= 1024) {
5016
+ return `${Math.round(bytes / 1024)} KB`;
5017
+ }
5018
+ return `${bytes} B`;
5019
+ }
5020
+ /**
5021
+ * Selector de archivo accesible, con la misma apariencia y contrato de campo
5022
+ * que `kf-input`: label arriba, ayuda y error debajo, `invalid`.
5023
+ *
5024
+ * El control real es un `<input type="file">` nativo, visualmente oculto pero
5025
+ * enfocable: se llega con Tab, se abre con Enter o Espacio y el anillo de foco
5026
+ * se dibuja sobre el botón visible. Su nombre accesible es el `label` (o
5027
+ * `ariaLabel`); la ayuda, el archivo elegido y el error lo describen
5028
+ * (`aria-describedby`).
5029
+ *
5030
+ * **Ayuda antes de elegir.** `hint` es texto libre; si no se pasa, se arma sola
5031
+ * con los formatos de `accept` y el tamaño de `maxSize` (`PDF, JPG · máx. 5 MB`),
5032
+ * para que el usuario sepa qué sirve ANTES de abrir el selector.
5033
+ *
5034
+ * **Validación.** Cada archivo se revisa contra `accept` (extensiones `.pdf`,
5035
+ * tipos `image/png` o comodines `image/*`) y `maxSize` (bytes). Los válidos
5036
+ * quedan elegidos y se emiten en `fileChange` (siempre `File[]`, vacío al
5037
+ * quitar); los descartados se emiten uno a uno en `fileRejected` con su
5038
+ * motivo (`'type'` o `'size'`) y se muestran como error bajo el campo
5039
+ * (`rejectedTypeText` / `rejectedSizeText`); la ayuda sigue visible bajo ese
5040
+ * mensaje, para que se vea qué sí sirve. Un `error` del consumidor tiene
5041
+ * prioridad sobre ese mensaje y reemplaza a la ayuda, igual que `errorMessage`
5042
+ * sobre `hint` en `kf-input`. La validación del navegador no reemplaza la del servidor.
5043
+ *
5044
+ * **Archivo elegido.** Su nombre y tamaño aparecen UNA vez, con un botón para
5045
+ * quitarlo (`removeLabel`, con el nombre del archivo en su `aria-label`); el
5046
+ * nombre del `<input>` nativo queda oculto. Con `multiple` se listan todos y
5047
+ * cada uno se quita por separado. Elegir de nuevo reemplaza la selección.
5048
+ *
5049
+ * `dropzone` agrega arrastrar y soltar sobre el área; el botón y el teclado
5050
+ * siguen siendo la vía principal.
5051
+ *
5052
+ * Es un `ControlValueAccessor`: con `[(ngModel)]`/`formControl` el valor es
5053
+ * `File | null` (o `File[]` con `multiple`); `writeValue(null)` limpia.
5054
+ *
5055
+ * <kf-file-input label="Comprobante" accept=".pdf,.jpg,.png" [maxSize]="5242880"
5056
+ * (fileChange)="onFiles($event)" (fileRejected)="onRejected($event)" />
5057
+ * <kf-file-input label="Fotos" accept="image/*" multiple dropzone formControlName="photos" />
5058
+ * <kf-file-input label="Contrato" error="Adjunta el contrato firmado." formControlName="contract" />
5059
+ */
5060
+ class FileInput {
5061
+ nativeInput = viewChild.required('nativeInput', /* @ts-ignore */
5062
+ ...(ngDevMode ? [{ debugName: "nativeInput" }] : /* istanbul ignore next */ []));
5063
+ label = input(/* @ts-ignore */
5064
+ ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
5065
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5066
+ /** Texto de ayuda. Sin valor, se arma con `accept` y `maxSize`. */
5067
+ hint = input(/* @ts-ignore */
5068
+ ...(ngDevMode ? [undefined, { debugName: "hint" }] : /* istanbul ignore next */ []));
5069
+ /** Formatos admitidos, como el `accept` nativo: `.pdf,.jpg`, `image/png`, `image/*`. */
5070
+ accept = input(/* @ts-ignore */
5071
+ ...(ngDevMode ? [undefined, { debugName: "accept" }] : /* istanbul ignore next */ []));
5072
+ /** Tamaño máximo por archivo, en bytes. Sin valor, sin límite. */
5073
+ maxSize = input(undefined, /* @ts-ignore */
5074
+ ...(ngDevMode ? [{ debugName: "maxSize" }] : /* istanbul ignore next */ []));
5075
+ multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5076
+ /** Agrega arrastrar y soltar sobre el área del control. */
5077
+ dropzone = input(false, { ...(ngDevMode ? { debugName: "dropzone" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5078
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5079
+ /** Borde rojo y `aria-invalid`. Se activa solo con `error`; úsalo para marcar un error sin mensaje. */
5080
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5081
+ /** Mensaje de error del consumidor (`role="alert"`); reemplaza a la ayuda y al motivo de rechazo, como `errorMessage` en `kf-input`. */
5082
+ error = input(/* @ts-ignore */
5083
+ ...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
5084
+ /** Nombre accesible cuando no hay `label` visible. */
5085
+ ariaLabel = input(/* @ts-ignore */
5086
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
5087
+ /** Texto del botón cuando no hay archivo elegido. */
5088
+ buttonLabel = input('Elegir archivo', /* @ts-ignore */
5089
+ ...(ngDevMode ? [{ debugName: "buttonLabel" }] : /* istanbul ignore next */ []));
5090
+ /** Texto del botón con `multiple` o cuando ya hay un archivo (cambiar). */
5091
+ changeLabel = input('Cambiar archivo', /* @ts-ignore */
5092
+ ...(ngDevMode ? [{ debugName: "changeLabel" }] : /* istanbul ignore next */ []));
5093
+ /** Nombre accesible del botón de quitar; se le agrega el nombre del archivo. */
5094
+ removeLabel = input('Quitar', /* @ts-ignore */
5095
+ ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
5096
+ /** Texto de arrastrar y soltar (solo con `dropzone`). */
5097
+ dropzoneText = input('o arrástralo aquí', /* @ts-ignore */
5098
+ ...(ngDevMode ? [{ debugName: "dropzoneText" }] : /* istanbul ignore next */ []));
5099
+ rejectedTypeText = input('Formato no permitido.', /* @ts-ignore */
5100
+ ...(ngDevMode ? [{ debugName: "rejectedTypeText" }] : /* istanbul ignore next */ []));
5101
+ rejectedSizeText = input('El archivo supera el tamaño máximo.', /* @ts-ignore */
5102
+ ...(ngDevMode ? [{ debugName: "rejectedSizeText" }] : /* istanbul ignore next */ []));
5103
+ /** Selección vigente (`File[]`, vacío al quitar todo). */
5104
+ fileChange = output();
5105
+ /** Un archivo descartado y su motivo. */
5106
+ fileRejected = output();
5107
+ inputId = `kf-file-input-${nextId$2++}`;
5108
+ hintId = `${this.inputId}-hint`;
5109
+ errorId = `${this.inputId}-error`;
5110
+ filesId = `${this.inputId}-files`;
5111
+ files = signal([], /* @ts-ignore */
5112
+ ...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
5113
+ rejection = signal(null, /* @ts-ignore */
5114
+ ...(ngDevMode ? [{ debugName: "rejection" }] : /* istanbul ignore next */ []));
5115
+ dragging = signal(false, /* @ts-ignore */
5116
+ ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
5117
+ disabledByForm = signal(false, /* @ts-ignore */
5118
+ ...(ngDevMode ? [{ debugName: "disabledByForm" }] : /* istanbul ignore next */ []));
5119
+ isDisabled = computed(() => this.disabled() || this.disabledByForm(), /* @ts-ignore */
5120
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
5121
+ onChange = () => { };
5122
+ onTouched = () => { };
5123
+ /** Mensaje visible: el del consumidor, o el motivo del último rechazo. */
5124
+ message = computed(() => {
5125
+ const own = this.error();
5126
+ if (own) {
5127
+ return own;
5128
+ }
5129
+ const reason = this.rejection();
5130
+ if (reason === 'type') {
5131
+ return this.rejectedTypeText();
5132
+ }
5133
+ return reason === 'size' ? this.rejectedSizeText() : null;
5134
+ }, /* @ts-ignore */
5135
+ ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
5136
+ /** Ayuda efectiva: la del consumidor o la armada con formatos y tamaño. */
5137
+ effectiveHint = computed(() => {
5138
+ const own = this.hint();
5139
+ if (own) {
5140
+ return own;
5141
+ }
5142
+ const parts = [];
5143
+ const tokens = (this.accept() ?? '')
5144
+ .split(',')
5145
+ .map((token) => token.trim())
5146
+ .filter(Boolean);
5147
+ if (tokens.length) {
5148
+ parts.push([...new Set(tokens.map(describeAcceptToken))].join(', '));
5149
+ }
5150
+ const max = this.maxSize();
5151
+ if (max) {
5152
+ parts.push(`máx. ${formatFileSize(max)}`);
5153
+ }
5154
+ return parts.join(' · ');
5155
+ }, /* @ts-ignore */
5156
+ ...(ngDevMode ? [{ debugName: "effectiveHint" }] : /* istanbul ignore next */ []));
5157
+ isInvalid = computed(() => this.invalid() || !!this.message(), /* @ts-ignore */
5158
+ ...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
5159
+ describedBy = computed(() => {
5160
+ const ids = [];
5161
+ if (this.message()) {
5162
+ ids.push(this.errorId);
5163
+ }
5164
+ if (this.effectiveHint() && !this.error()) {
5165
+ ids.push(this.hintId);
5166
+ }
5167
+ if (this.files().length) {
5168
+ ids.push(this.filesId);
5169
+ }
5170
+ return ids.join(' ') || null;
5171
+ }, /* @ts-ignore */
5172
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
5173
+ triggerText = computed(() => this.files().length && !this.multiple() ? this.changeLabel() : this.buttonLabel(), /* @ts-ignore */
5174
+ ...(ngDevMode ? [{ debugName: "triggerText" }] : /* istanbul ignore next */ []));
5175
+ /** Enfoca el `<input>` nativo. */
5176
+ focus(options) {
5177
+ this.nativeInput().nativeElement.focus(options);
5178
+ }
5179
+ writeValue(value) {
5180
+ const list = Array.isArray(value) ? value : value ? [value] : [];
5181
+ this.files.set(list);
5182
+ this.rejection.set(null);
5183
+ }
5184
+ registerOnChange(fn) {
5185
+ this.onChange = fn;
5186
+ }
5187
+ registerOnTouched(fn) {
5188
+ this.onTouched = fn;
5189
+ }
5190
+ setDisabledState(isDisabled) {
5191
+ this.disabledByForm.set(isDisabled);
5192
+ }
5193
+ onPick(event) {
5194
+ const native = event.target;
5195
+ const picked = Array.from(native.files ?? []);
5196
+ // Se limpia el valor nativo: el nombre ya se muestra en la lista y así se
5197
+ // puede volver a elegir el mismo archivo después de quitarlo.
5198
+ native.value = '';
5199
+ this.receive(picked);
5200
+ }
5201
+ onDragOver(event) {
5202
+ if (this.isDisabled()) {
5203
+ return;
5204
+ }
5205
+ event.preventDefault();
5206
+ this.dragging.set(true);
5207
+ }
5208
+ onDragLeave() {
5209
+ this.dragging.set(false);
5210
+ }
5211
+ onDrop(event) {
5212
+ event.preventDefault();
5213
+ this.dragging.set(false);
5214
+ if (this.isDisabled()) {
5215
+ return;
5216
+ }
5217
+ this.receive(Array.from(event.dataTransfer?.files ?? []));
5218
+ }
5219
+ remove(index) {
5220
+ const next = this.files().filter((_, i) => i !== index);
5221
+ this.rejection.set(null);
5222
+ this.commit(next);
5223
+ this.nativeInput().nativeElement.focus();
5224
+ }
5225
+ formatSize(file) {
5226
+ return formatFileSize(file.size);
5227
+ }
5228
+ /** Valida lo recibido, emite los rechazos y reemplaza la selección si hay algo válido. */
5229
+ receive(incoming) {
5230
+ if (!incoming.length) {
5231
+ return;
5232
+ }
5233
+ const candidates = this.multiple() ? incoming : incoming.slice(0, 1);
5234
+ const accepted = [];
5235
+ let lastReason = null;
5236
+ for (const file of candidates) {
5237
+ const reason = this.reject(file);
5238
+ if (reason) {
5239
+ lastReason = reason;
5240
+ this.fileRejected.emit({ file, reason });
5241
+ }
5242
+ else {
5243
+ accepted.push(file);
5244
+ }
5245
+ }
5246
+ this.rejection.set(lastReason);
5247
+ if (accepted.length) {
5248
+ this.commit(accepted);
5249
+ }
5250
+ this.onTouched();
5251
+ }
5252
+ reject(file) {
5253
+ if (!this.matchesAccept(file)) {
5254
+ return 'type';
5255
+ }
5256
+ const max = this.maxSize();
5257
+ return max !== undefined && file.size > max ? 'size' : null;
5258
+ }
5259
+ matchesAccept(file) {
5260
+ const tokens = (this.accept() ?? '')
5261
+ .split(',')
5262
+ .map((token) => token.trim().toLowerCase())
5263
+ .filter(Boolean);
5264
+ if (!tokens.length) {
5265
+ return true;
5266
+ }
5267
+ const name = file.name.toLowerCase();
5268
+ const type = file.type.toLowerCase();
5269
+ return tokens.some((token) => {
5270
+ if (token.startsWith('.')) {
5271
+ return name.endsWith(token);
5272
+ }
5273
+ if (token.endsWith('/*')) {
5274
+ return type.startsWith(token.slice(0, -1));
5275
+ }
5276
+ return type === token;
5277
+ });
5278
+ }
5279
+ commit(files) {
5280
+ this.files.set(files);
5281
+ this.fileChange.emit(files);
5282
+ this.onChange(this.multiple() ? files : (files[0] ?? null));
5283
+ }
5284
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
5285
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FileInput, isStandalone: true, selector: "kf-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, dropzone: { classPropertyName: "dropzone", publicName: "dropzone", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, changeLabel: { classPropertyName: "changeLabel", publicName: "changeLabel", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, dropzoneText: { classPropertyName: "dropzoneText", publicName: "dropzoneText", isSignal: true, isRequired: false, transformFunction: null }, rejectedTypeText: { classPropertyName: "rejectedTypeText", publicName: "rejectedTypeText", isSignal: true, isRequired: false, transformFunction: null }, rejectedSizeText: { classPropertyName: "rejectedSizeText", publicName: "rejectedSizeText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", fileRejected: "fileRejected" }, providers: [
5286
+ {
5287
+ provide: NG_VALUE_ACCESSOR,
5288
+ useExisting: forwardRef(() => FileInput),
5289
+ multi: true,
5290
+ },
5291
+ ], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-file-input__area\"\n [class.kf-file-input__area--dropzone]=\"dropzone()\"\n [class.kf-file-input__area--dragging]=\"dragging()\"\n [class.kf-file-input__area--invalid]=\"isInvalid()\"\n [class.kf-file-input__area--disabled]=\"isDisabled()\"\n (dragover)=\"dropzone() ? onDragOver($event) : null\"\n (dragleave)=\"dropzone() ? onDragLeave() : null\"\n (drop)=\"dropzone() ? onDrop($event) : null\"\n>\n <input\n #nativeInput\n type=\"file\"\n class=\"kf-file-input__native\"\n [id]=\"inputId\"\n [attr.accept]=\"accept() || null\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required() && !files().length\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n (change)=\"onPick($event)\"\n />\n <label class=\"kf-file-input__button\" [for]=\"inputId\" aria-hidden=\"true\">\n <kf-icon name=\"upload-cloud\" size=\"sm\" />\n {{ triggerText() }}\n </label>\n @if (dropzone()) {\n <span class=\"kf-file-input__drop-text\" aria-hidden=\"true\">{{ dropzoneText() }}</span>\n }\n</div>\n@if (files().length) {\n <ul class=\"kf-file-input__files\" [id]=\"filesId\">\n @for (file of files(); track $index) {\n <li class=\"kf-file-input__file\">\n <kf-icon class=\"kf-file-input__file-icon\" name=\"file\" size=\"sm\" />\n <span class=\"kf-file-input__file-name\">{{ file.name }}</span>\n <span class=\"kf-file-input__file-size\">{{ formatSize(file) }}</span>\n <button\n type=\"button\"\n class=\"kf-file-input__remove\"\n [attr.aria-label]=\"removeLabel() + ' ' + file.name\"\n [disabled]=\"isDisabled()\"\n (click)=\"remove($index)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n}\n@if (message(); as text) {\n <span class=\"kf-file-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ text }}</span>\n}\n@if (effectiveHint() && !error()) {\n <span class=\"kf-file-input__hint kf-field-hint\" [id]=\"hintId\">{{ effectiveHint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-file-input__area{position:relative;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-file-input__native{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}.kf-file-input__native:disabled{cursor:not-allowed}.kf-file-input__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);min-height:2.25rem;padding:var(--kf-space-sm) var(--kf-space-md);box-sizing:border-box;border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary-active);font:inherit;font-weight:600;pointer-events:none;transition:background .14s,box-shadow .14s}.kf-file-input__area:hover .kf-file-input__native:not(:disabled)+.kf-file-input__button{background:var(--kf-color-primary-soft)}.kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus);outline:2px solid transparent}.kf-file-input__area--invalid .kf-file-input__button{border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-file-input__area--invalid .kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus-error)}.kf-file-input__area--disabled{opacity:.5}.kf-file-input__area--dropzone{flex-direction:column;justify-content:center;gap:var(--kf-space-xs);padding:var(--kf-space-lg) var(--kf-space-md);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);transition:background .14s,border-color .14s}.kf-file-input__area--dragging{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-file-input__area--dropzone.kf-file-input__area--invalid{border-color:var(--kf-color-error)}.kf-file-input__drop-text{font-size:.875rem;color:var(--kf-color-text);pointer-events:none}.kf-file-input__files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-file-input__file{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-xs) var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-file-input__file-icon{flex:none;color:var(--kf-color-text-muted)}.kf-file-input__file-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--kf-color-ink);font-weight:600}.kf-file-input__file-size{flex:none;font-size:.75rem;color:var(--kf-color-text)}.kf-file-input__remove{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:2rem;min-height:2rem;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-color-text);cursor:pointer}.kf-file-input__remove:hover:not(:disabled){background:var(--kf-color-surface);color:var(--kf-color-ink)}.kf-file-input__remove:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-file-input__hint{color:var(--kf-color-text)}@media(pointer:coarse),(max-width:760px){.kf-file-input__button{min-height:44px}.kf-file-input__remove{min-width:44px;min-height:44px}}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5292
+ }
5293
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileInput, decorators: [{
5294
+ type: Component,
5295
+ args: [{ selector: 'kf-file-input', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FieldLabel, Icon], providers: [
5296
+ {
5297
+ provide: NG_VALUE_ACCESSOR,
5298
+ useExisting: forwardRef(() => FileInput),
5299
+ multi: true,
5300
+ },
5301
+ ], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-file-input__area\"\n [class.kf-file-input__area--dropzone]=\"dropzone()\"\n [class.kf-file-input__area--dragging]=\"dragging()\"\n [class.kf-file-input__area--invalid]=\"isInvalid()\"\n [class.kf-file-input__area--disabled]=\"isDisabled()\"\n (dragover)=\"dropzone() ? onDragOver($event) : null\"\n (dragleave)=\"dropzone() ? onDragLeave() : null\"\n (drop)=\"dropzone() ? onDrop($event) : null\"\n>\n <input\n #nativeInput\n type=\"file\"\n class=\"kf-file-input__native\"\n [id]=\"inputId\"\n [attr.accept]=\"accept() || null\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required() && !files().length\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n (change)=\"onPick($event)\"\n />\n <label class=\"kf-file-input__button\" [for]=\"inputId\" aria-hidden=\"true\">\n <kf-icon name=\"upload-cloud\" size=\"sm\" />\n {{ triggerText() }}\n </label>\n @if (dropzone()) {\n <span class=\"kf-file-input__drop-text\" aria-hidden=\"true\">{{ dropzoneText() }}</span>\n }\n</div>\n@if (files().length) {\n <ul class=\"kf-file-input__files\" [id]=\"filesId\">\n @for (file of files(); track $index) {\n <li class=\"kf-file-input__file\">\n <kf-icon class=\"kf-file-input__file-icon\" name=\"file\" size=\"sm\" />\n <span class=\"kf-file-input__file-name\">{{ file.name }}</span>\n <span class=\"kf-file-input__file-size\">{{ formatSize(file) }}</span>\n <button\n type=\"button\"\n class=\"kf-file-input__remove\"\n [attr.aria-label]=\"removeLabel() + ' ' + file.name\"\n [disabled]=\"isDisabled()\"\n (click)=\"remove($index)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n}\n@if (message(); as text) {\n <span class=\"kf-file-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ text }}</span>\n}\n@if (effectiveHint() && !error()) {\n <span class=\"kf-file-input__hint kf-field-hint\" [id]=\"hintId\">{{ effectiveHint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-file-input__area{position:relative;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-file-input__native{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}.kf-file-input__native:disabled{cursor:not-allowed}.kf-file-input__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);min-height:2.25rem;padding:var(--kf-space-sm) var(--kf-space-md);box-sizing:border-box;border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary-active);font:inherit;font-weight:600;pointer-events:none;transition:background .14s,box-shadow .14s}.kf-file-input__area:hover .kf-file-input__native:not(:disabled)+.kf-file-input__button{background:var(--kf-color-primary-soft)}.kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus);outline:2px solid transparent}.kf-file-input__area--invalid .kf-file-input__button{border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-file-input__area--invalid .kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus-error)}.kf-file-input__area--disabled{opacity:.5}.kf-file-input__area--dropzone{flex-direction:column;justify-content:center;gap:var(--kf-space-xs);padding:var(--kf-space-lg) var(--kf-space-md);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);transition:background .14s,border-color .14s}.kf-file-input__area--dragging{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-file-input__area--dropzone.kf-file-input__area--invalid{border-color:var(--kf-color-error)}.kf-file-input__drop-text{font-size:.875rem;color:var(--kf-color-text);pointer-events:none}.kf-file-input__files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-file-input__file{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-xs) var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-file-input__file-icon{flex:none;color:var(--kf-color-text-muted)}.kf-file-input__file-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--kf-color-ink);font-weight:600}.kf-file-input__file-size{flex:none;font-size:.75rem;color:var(--kf-color-text)}.kf-file-input__remove{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:2rem;min-height:2rem;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-color-text);cursor:pointer}.kf-file-input__remove:hover:not(:disabled){background:var(--kf-color-surface);color:var(--kf-color-ink)}.kf-file-input__remove:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-file-input__hint{color:var(--kf-color-text)}@media(pointer:coarse),(max-width:760px){.kf-file-input__button{min-height:44px}.kf-file-input__remove{min-width:44px;min-height:44px}}\n"] }]
5302
+ }], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], dropzone: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropzone", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], buttonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLabel", required: false }] }], changeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "changeLabel", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], dropzoneText: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropzoneText", required: false }] }], rejectedTypeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rejectedTypeText", required: false }] }], rejectedSizeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rejectedSizeText", required: false }] }], fileChange: [{ type: i0.Output, args: ["fileChange"] }], fileRejected: [{ type: i0.Output, args: ["fileRejected"] }] } });
5303
+
5304
+ /** Vacío o `undefined` no emiten el atributo; `0` sí es un límite válido. */
5305
+ function normalizeBound(value) {
5306
+ return value === undefined || value === '' ? null : value;
5307
+ }
5308
+ /** `maxlength` como entero positivo; cualquier otra cosa (vacío, NaN, 0, negativo) no se emite. */
5309
+ function toMaxLength(value) {
5310
+ const parsed = typeof value === 'string' ? Number.parseInt(value, 10) : value;
5311
+ return typeof parsed === 'number' && Number.isInteger(parsed) && parsed > 0 ? parsed : null;
5312
+ }
4412
5313
  /**
4413
5314
  * Input de formulario apilado: label arriba (en mayúsculas vía CSS), campo con
4414
5315
  * fondo gris que pasa a blanco con anillo de foco azul al enfocar.
@@ -4472,6 +5373,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4472
5373
  *
4473
5374
  * <kf-input label="RUT" autocomplete="username" name="rut" inputmode="text" formControlName="rut" />
4474
5375
  * <kf-input type="password" label="Contraseña" toggleable autocomplete="current-password" name="password" formControlName="password" />
5376
+ *
5377
+ * `min`, `max`, `step` y `maxlength` se reenvían al `<input>` nativo (mismo
5378
+ * motivo: un atributo en el host no llega adentro) y sin valor no se
5379
+ * renderiza ningún atributo. `min`/`max`/`step` aceptan número o texto según
5380
+ * el `type` (`number`, `range`, `date`, `time`, `month`…): con `type="date"` el
5381
+ * límite es una fecha ISO. `maxlength` es un entero positivo y aplica a los
5382
+ * tipos de texto (`text`, `search`, `email`, `tel`, `url`, `password`). El
5383
+ * navegador usa los límites para la validación nativa (`:invalid`,
5384
+ * `validity.rangeUnderflow`) y para el selector de fechas; no reemplazan la
5385
+ * validación del formulario ni el `errorMessage`.
5386
+ *
5387
+ * <kf-input type="date" label="Fecha de entrega" min="2026-01-01" max="2026-12-31" />
5388
+ * <kf-input type="number" label="Cantidad" [min]="1" [max]="20" [step]="1" />
5389
+ * <kf-input label="Placa" [maxlength]="7" />
4475
5390
  */
4476
5391
  class Input {
4477
5392
  static nextId = 0;
@@ -4521,6 +5436,14 @@ class Input {
4521
5436
  * el `type`. Sin valor, el atributo no se renderiza. */
4522
5437
  inputmode = input(/* @ts-ignore */
4523
5438
  ...(ngDevMode ? [undefined, { debugName: "inputmode" }] : /* istanbul ignore next */ []));
5439
+ /** Límite inferior del control (`min` nativo): número, o texto para fechas/horas (`'2026-01-01'`). */
5440
+ min = input(null, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: normalizeBound });
5441
+ /** Límite superior del control (`max` nativo). Ver `min`. */
5442
+ max = input(null, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: normalizeBound });
5443
+ /** Paso del control (`step` nativo): número o `'any'`. */
5444
+ step = input(null, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: normalizeBound });
5445
+ /** Largo máximo del texto (`maxlength` nativo), entero positivo. Solo aplica a tipos de texto. */
5446
+ maxlength = input(null, { ...(ngDevMode ? { debugName: "maxlength" } : /* istanbul ignore next */ {}), transform: toMaxLength });
4524
5447
  /** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
4525
5448
  toggleable = input(false, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4526
5449
  /** Nombre accesible del `<input>` cuando no hay `label` visible. Ignorado
@@ -4630,13 +5553,13 @@ class Input {
4630
5553
  this.passwordVisible.update((visible) => !visible);
4631
5554
  }
4632
5555
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Component });
4633
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Input, isStandalone: true, selector: "kf-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared" }, providers: [
5556
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Input, isStandalone: true, selector: "kf-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared" }, providers: [
4634
5557
  {
4635
5558
  provide: NG_VALUE_ACCESSOR,
4636
5559
  useExisting: forwardRef(() => Input),
4637
5560
  multi: true,
4638
5561
  },
4639
- ], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5562
+ ], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.min]=\"min()\"\n [attr.max]=\"max()\"\n [attr.step]=\"step()\"\n [attr.maxlength]=\"maxlength()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4640
5563
  }
4641
5564
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, decorators: [{
4642
5565
  type: Component,
@@ -4646,8 +5569,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4646
5569
  useExisting: forwardRef(() => Input),
4647
5570
  multi: true,
4648
5571
  },
4649
- ], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
4650
- }], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
5572
+ ], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.min]=\"min()\"\n [attr.max]=\"max()\"\n [attr.step]=\"step()\"\n [attr.maxlength]=\"maxlength()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
5573
+ }], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
4651
5574
 
4652
5575
  let nextId$1 = 0;
4653
5576
  /**
@@ -4775,7 +5698,11 @@ class MultiSelect {
4775
5698
  this.searchInputRef()?.nativeElement.focus();
4776
5699
  }
4777
5700
  });
4778
- inject(DestroyRef).onDestroy(() => this.overlay.dispose());
5701
+ const stopEscape = listenEscapeCapture(inject(DOCUMENT), () => this.onEscape());
5702
+ inject(DestroyRef).onDestroy(() => {
5703
+ stopEscape();
5704
+ this.overlay.dispose();
5705
+ });
4779
5706
  }
4780
5707
  isSelected(value) {
4781
5708
  return this.values().includes(value);
@@ -4822,11 +5749,21 @@ class MultiSelect {
4822
5749
  this.toggleOption(option.value);
4823
5750
  }
4824
5751
  }
5752
+ /**
5753
+ * Escape con el panel abierto, en fase de captura sobre `document` (ver
5754
+ * `listenEscapeCapture`): consume la tecla solo si había un panel que
5755
+ * cerrar, así un `kf-drawer`/`kf-modal` contenedor no se cierra con el
5756
+ * mismo Escape. Devuelve si cerró algo.
5757
+ */
4825
5758
  onEscape() {
5759
+ if (!this.isOpen()) {
5760
+ return false;
5761
+ }
4826
5762
  this.isOpen.set(false);
5763
+ return true;
4827
5764
  }
4828
5765
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
4829
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MultiSelect, isStandalone: true, selector: "kf-multi-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valuesChange: "valuesChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.kf-multi-select--width-sm": "width() === 'sm'", "class.kf-multi-select--width-lg": "width() === 'lg'", "class.kf-multi-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-multi-select__trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n (click)=\"toggle()\"\n>\n <span\n class=\"kf-multi-select__value\"\n [class.kf-multi-select__value--placeholder]=\"selectedCount() === 0\"\n >\n {{ summaryText() }}\n </span>\n @if (selectedCount() > 0) {\n <span class=\"kf-multi-select__count\">{{ selectedCount() }}</span>\n }\n <span\n class=\"kf-multi-select__caret kf-caret\"\n [class.kf-multi-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n<ng-template #panelTemplate>\n <div class=\"kf-multi-select__panel\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-multi-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); toggleActive()\"\n />\n }\n <ul\n class=\"kf-multi-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-labelledby]=\"triggerId\"\n >\n @if (options().length > 0) {\n <li\n class=\"kf-multi-select__option\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"allSelected()\"\n (click)=\"toggleAll()\"\n (keydown.enter)=\"toggleAll()\"\n (keydown.space)=\"$event.preventDefault(); toggleAll()\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"allSelected()\"\n [class.kf-multi-select__checkbox--indeterminate]=\"isPartiallySelected()\"\n ></span>\n <span class=\"kf-multi-select__option-label\">Todas</span>\n </li>\n }\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-multi-select__option\"\n [class.kf-multi-select__option--selected]=\"isSelected(option.value)\"\n [class.kf-multi-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(option.value)\"\n (click)=\"toggleOption(option.value)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"toggleOption(option.value)\"\n (keydown.space)=\"$event.preventDefault(); toggleOption(option.value)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"isSelected(option.value)\"\n ></span>\n <span class=\"kf-multi-select__option-label\">{{ option.label }}</span>\n </li>\n } @empty {\n @if (options().length > 0) {\n <li class=\"kf-multi-select__empty\">Sin resultados</li>\n } @else {\n <li class=\"kf-multi-select__empty\">Sin opciones disponibles</li>\n }\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-checkbox-box{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--kf-space-md);height:var(--kf-space-md);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-xs);background:var(--kf-color-white);transition:background .14s,border-color .14s;position:relative}.kf-checkbox-box--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}:host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:var(--kf-select-width-md)}:host(.kf-multi-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-multi-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-multi-select--width-full){width:100%}.kf-multi-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-family:var(--kf-font-body);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-multi-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-multi-select__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-multi-select__trigger:disabled{opacity:.5;cursor:not-allowed}.kf-multi-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}.kf-multi-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-multi-select__count{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 5px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white);font-size:.72rem;font-weight:700}.kf-multi-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-multi-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-multi-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-multi-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-multi-select__list::-webkit-scrollbar{width:6px}.kf-multi-select__list::-webkit-scrollbar-track{background:transparent}.kf-multi-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-multi-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-multi-select__option{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-multi-select__option:hover{background:var(--kf-color-primary-soft)}.kf-multi-select__option--selected{color:var(--kf-color-primary);font-weight:600}.kf-multi-select__option--active{background:var(--kf-color-primary-soft)}.kf-multi-select__checkbox{display:inline-flex}.kf-multi-select__checkbox--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--checked:after{content:\"\";position:absolute;left:4px;top:1px;width:var(--kf-checkbox-mark-width);height:var(--kf-checkbox-mark-height);border:var(--kf-checkbox-mark-border);border-width:var(--kf-checkbox-mark-border-width);transform:rotate(45deg)}.kf-multi-select__checkbox--indeterminate{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:var(--kf-checkbox-dash-width);height:var(--kf-checkbox-dash-height);background:var(--kf-color-white);border:none;transform:none}.kf-multi-select__option-label{overflow:hidden;text-overflow:ellipsis}.kf-multi-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
5766
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MultiSelect, isStandalone: true, selector: "kf-multi-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valuesChange: "valuesChange" }, host: { properties: { "class.kf-multi-select--width-sm": "width() === 'sm'", "class.kf-multi-select--width-lg": "width() === 'lg'", "class.kf-multi-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-multi-select__trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n (click)=\"toggle()\"\n>\n <span\n class=\"kf-multi-select__value\"\n [class.kf-multi-select__value--placeholder]=\"selectedCount() === 0\"\n >\n {{ summaryText() }}\n </span>\n @if (selectedCount() > 0) {\n <span class=\"kf-multi-select__count\">{{ selectedCount() }}</span>\n }\n <span\n class=\"kf-multi-select__caret kf-caret\"\n [class.kf-multi-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n<ng-template #panelTemplate>\n <div class=\"kf-multi-select__panel\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-multi-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); toggleActive()\"\n />\n }\n <ul\n class=\"kf-multi-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-labelledby]=\"triggerId\"\n >\n @if (options().length > 0) {\n <li\n class=\"kf-multi-select__option\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"allSelected()\"\n (click)=\"toggleAll()\"\n (keydown.enter)=\"toggleAll()\"\n (keydown.space)=\"$event.preventDefault(); toggleAll()\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"allSelected()\"\n [class.kf-multi-select__checkbox--indeterminate]=\"isPartiallySelected()\"\n ></span>\n <span class=\"kf-multi-select__option-label\">Todas</span>\n </li>\n }\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-multi-select__option\"\n [class.kf-multi-select__option--selected]=\"isSelected(option.value)\"\n [class.kf-multi-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(option.value)\"\n (click)=\"toggleOption(option.value)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"toggleOption(option.value)\"\n (keydown.space)=\"$event.preventDefault(); toggleOption(option.value)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"isSelected(option.value)\"\n ></span>\n <span class=\"kf-multi-select__option-label\">{{ option.label }}</span>\n </li>\n } @empty {\n @if (options().length > 0) {\n <li class=\"kf-multi-select__empty\">Sin resultados</li>\n } @else {\n <li class=\"kf-multi-select__empty\">Sin opciones disponibles</li>\n }\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-checkbox-box{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--kf-space-md);height:var(--kf-space-md);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-xs);background:var(--kf-color-white);transition:background .14s,border-color .14s;position:relative}.kf-checkbox-box--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}:host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:var(--kf-select-width-md)}:host(.kf-multi-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-multi-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-multi-select--width-full){width:100%}.kf-multi-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-family:var(--kf-font-body);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-multi-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-multi-select__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-multi-select__trigger:disabled{opacity:.5;cursor:not-allowed}.kf-multi-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}.kf-multi-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-multi-select__count{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 5px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white);font-size:.72rem;font-weight:700}.kf-multi-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-multi-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-multi-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-multi-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-multi-select__list::-webkit-scrollbar{width:6px}.kf-multi-select__list::-webkit-scrollbar-track{background:transparent}.kf-multi-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-multi-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-multi-select__option{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-multi-select__option:hover{background:var(--kf-color-primary-soft)}.kf-multi-select__option--selected{color:var(--kf-color-primary);font-weight:600}.kf-multi-select__option--active{background:var(--kf-color-primary-soft)}.kf-multi-select__checkbox{display:inline-flex}.kf-multi-select__checkbox--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--checked:after{content:\"\";position:absolute;left:4px;top:1px;width:var(--kf-checkbox-mark-width);height:var(--kf-checkbox-mark-height);border:var(--kf-checkbox-mark-border);border-width:var(--kf-checkbox-mark-border-width);transform:rotate(45deg)}.kf-multi-select__checkbox--indeterminate{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:var(--kf-checkbox-dash-width);height:var(--kf-checkbox-dash-height);background:var(--kf-color-white);border:none;transform:none}.kf-multi-select__option-label{overflow:hidden;text-overflow:ellipsis}.kf-multi-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
4830
5767
  }
4831
5768
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, decorators: [{
4832
5769
  type: Component,
@@ -4835,10 +5772,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4835
5772
  '[class.kf-multi-select--width-lg]': "width() === 'lg'",
4836
5773
  '[class.kf-multi-select--width-full]': "width() === 'full'",
4837
5774
  }, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-multi-select__trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n (click)=\"toggle()\"\n>\n <span\n class=\"kf-multi-select__value\"\n [class.kf-multi-select__value--placeholder]=\"selectedCount() === 0\"\n >\n {{ summaryText() }}\n </span>\n @if (selectedCount() > 0) {\n <span class=\"kf-multi-select__count\">{{ selectedCount() }}</span>\n }\n <span\n class=\"kf-multi-select__caret kf-caret\"\n [class.kf-multi-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n<ng-template #panelTemplate>\n <div class=\"kf-multi-select__panel\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-multi-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); toggleActive()\"\n />\n }\n <ul\n class=\"kf-multi-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-labelledby]=\"triggerId\"\n >\n @if (options().length > 0) {\n <li\n class=\"kf-multi-select__option\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"allSelected()\"\n (click)=\"toggleAll()\"\n (keydown.enter)=\"toggleAll()\"\n (keydown.space)=\"$event.preventDefault(); toggleAll()\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"allSelected()\"\n [class.kf-multi-select__checkbox--indeterminate]=\"isPartiallySelected()\"\n ></span>\n <span class=\"kf-multi-select__option-label\">Todas</span>\n </li>\n }\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-multi-select__option\"\n [class.kf-multi-select__option--selected]=\"isSelected(option.value)\"\n [class.kf-multi-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(option.value)\"\n (click)=\"toggleOption(option.value)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"toggleOption(option.value)\"\n (keydown.space)=\"$event.preventDefault(); toggleOption(option.value)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"isSelected(option.value)\"\n ></span>\n <span class=\"kf-multi-select__option-label\">{{ option.label }}</span>\n </li>\n } @empty {\n @if (options().length > 0) {\n <li class=\"kf-multi-select__empty\">Sin resultados</li>\n } @else {\n <li class=\"kf-multi-select__empty\">Sin opciones disponibles</li>\n }\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-checkbox-box{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--kf-space-md);height:var(--kf-space-md);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-xs);background:var(--kf-color-white);transition:background .14s,border-color .14s;position:relative}.kf-checkbox-box--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}:host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:var(--kf-select-width-md)}:host(.kf-multi-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-multi-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-multi-select--width-full){width:100%}.kf-multi-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-family:var(--kf-font-body);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-multi-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-multi-select__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-multi-select__trigger:disabled{opacity:.5;cursor:not-allowed}.kf-multi-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}.kf-multi-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-multi-select__count{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 5px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white);font-size:.72rem;font-weight:700}.kf-multi-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-multi-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-multi-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-multi-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-multi-select__list::-webkit-scrollbar{width:6px}.kf-multi-select__list::-webkit-scrollbar-track{background:transparent}.kf-multi-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-multi-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-multi-select__option{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-multi-select__option:hover{background:var(--kf-color-primary-soft)}.kf-multi-select__option--selected{color:var(--kf-color-primary);font-weight:600}.kf-multi-select__option--active{background:var(--kf-color-primary-soft)}.kf-multi-select__checkbox{display:inline-flex}.kf-multi-select__checkbox--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--checked:after{content:\"\";position:absolute;left:4px;top:1px;width:var(--kf-checkbox-mark-width);height:var(--kf-checkbox-mark-height);border:var(--kf-checkbox-mark-border);border-width:var(--kf-checkbox-mark-border-width);transform:rotate(45deg)}.kf-multi-select__checkbox--indeterminate{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:var(--kf-checkbox-dash-width);height:var(--kf-checkbox-dash-height);background:var(--kf-color-white);border:none;transform:none}.kf-multi-select__option-label{overflow:hidden;text-overflow:ellipsis}.kf-multi-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
4838
- }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], valuesChange: [{ type: i0.Output, args: ["valuesChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], onEscape: [{
4839
- type: HostListener,
4840
- args: ['document:keydown.escape']
4841
- }] } });
5775
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], valuesChange: [{ type: i0.Output, args: ["valuesChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
4842
5776
 
4843
5777
  const DEFAULT_FIELD_WIDTH = '240px';
4844
5778
  /**
@@ -4903,7 +5837,7 @@ class Filter {
4903
5837
  this.fieldChange.emit({ key, value });
4904
5838
  }
4905
5839
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, deps: [], target: i0.ɵɵFactoryTarget.Component });
4906
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Filter, isStandalone: true, selector: "kf-filter", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mobileTitle: { classPropertyName: "mobileTitle", publicName: "mobileTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange" }, ngImport: i0, template: "<ng-template #fieldsTpl let-stacked=\"stacked\">\n @for (field of fields(); track field.key) {\n @switch (field.type) {\n @case ('select') {\n <kf-select\n [label]=\"field.label ?? ''\"\n [options]=\"optionsFor(field.key)\"\n [value]=\"singleValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valueChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('multi-select') {\n <kf-multi-select\n [label]=\"field.label ?? ''\"\n [options]=\"multiOptionsFor(field.key)\"\n [values]=\"multiValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valuesChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('search') {\n <kf-input\n [label]=\"field.label ?? ''\"\n [icon]=\"field.icon ?? 'search'\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n [ngModel]=\"singleValueFor(field.key)\"\n (ngModelChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n }\n @if (!stacked && field.showDivider) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n</ng-template>\n\n<div class=\"kf-filter kf-filter--row\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: false }\" />\n</div>\n\n<kf-collapsible class=\"kf-filter__collapsible\" [title]=\"mobileTitle()\">\n <div class=\"kf-filter kf-filter--stack\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: true }\" />\n </div>\n</kf-collapsible>\n", styles: ["@charset \"UTF-8\";:host{display:block}.kf-filter--row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--kf-space-md)}.kf-filter--stack{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-filter__collapsible{display:none}@media(max-width:768px){.kf-filter--row{display:none}.kf-filter__collapsible{display:block}}\n"], dependencies: [{ kind: "component", type: Collapsible, selector: "kf-collapsible", inputs: ["title", "expanded"], outputs: ["expandedChange"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "autocomplete", "name", "inputmode", "toggleable", "ariaLabel", "clearable", "clearLabel"], outputs: ["cleared"] }, { kind: "component", type: MultiSelect, selector: "kf-multi-select", inputs: ["label", "options", "values", "placeholder", "disabled", "width", "searchable", "searchThreshold"], outputs: ["valuesChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold", "ariaLabel", "icon", "panelTitle", "panelWidth"], outputs: ["valueChange"] }] });
5840
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Filter, isStandalone: true, selector: "kf-filter", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mobileTitle: { classPropertyName: "mobileTitle", publicName: "mobileTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange" }, ngImport: i0, template: "<ng-template #fieldsTpl let-stacked=\"stacked\">\n @for (field of fields(); track field.key) {\n @switch (field.type) {\n @case ('select') {\n <kf-select\n [label]=\"field.label ?? ''\"\n [options]=\"optionsFor(field.key)\"\n [value]=\"singleValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valueChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('multi-select') {\n <kf-multi-select\n [label]=\"field.label ?? ''\"\n [options]=\"multiOptionsFor(field.key)\"\n [values]=\"multiValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valuesChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('search') {\n <kf-input\n [label]=\"field.label ?? ''\"\n [icon]=\"field.icon ?? 'search'\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n [ngModel]=\"singleValueFor(field.key)\"\n (ngModelChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n }\n @if (!stacked && field.showDivider) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n</ng-template>\n\n<div class=\"kf-filter kf-filter--row\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: false }\" />\n</div>\n\n<kf-collapsible class=\"kf-filter__collapsible\" [title]=\"mobileTitle()\">\n <div class=\"kf-filter kf-filter--stack\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: true }\" />\n </div>\n</kf-collapsible>\n", styles: ["@charset \"UTF-8\";:host{display:block}.kf-filter--row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--kf-space-md)}.kf-filter--stack{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-filter__collapsible{display:none}@media(max-width:768px){.kf-filter--row{display:none}.kf-filter__collapsible{display:block}}\n"], dependencies: [{ kind: "component", type: Collapsible, selector: "kf-collapsible", inputs: ["title", "expanded"], outputs: ["expandedChange"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "autocomplete", "name", "inputmode", "min", "max", "step", "maxlength", "toggleable", "ariaLabel", "clearable", "clearLabel"], outputs: ["cleared"] }, { kind: "component", type: MultiSelect, selector: "kf-multi-select", inputs: ["label", "options", "values", "placeholder", "disabled", "width", "searchable", "searchThreshold"], outputs: ["valuesChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold", "ariaLabel", "icon", "panelTitle", "panelWidth"], outputs: ["valueChange"] }] });
4907
5841
  }
4908
5842
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, decorators: [{
4909
5843
  type: Component,
@@ -5514,6 +6448,14 @@ function cancelFrame(handle) {
5514
6448
  * <kf-metric-card label="Saldo Pendiente" value="$45,000">
5515
6449
  * <kf-button actions variant="primary" size="sm">Pagar Ahora</kf-button>
5516
6450
  * </kf-metric-card>
6451
+ *
6452
+ * `loading` (default `false`): dibuja el esqueleto de la card — etiqueta,
6453
+ * ícono, valor y `foot` como bloques del alto real de cada línea — dentro del
6454
+ * mismo `kf-card`, y reserva (sin mostrar) el espacio de las acciones, así que
6455
+ * al llegar los datos no hay salto de layout. Pasa los mismos inputs que
6456
+ * usarás cargada. Marca el host con `aria-busy="true"`.
6457
+ *
6458
+ * <kf-metric-card label="Tickets abiertos" value="11" foot="+2 hoy" [loading]="isLoading()" />
5517
6459
  */
5518
6460
  class MetricCard {
5519
6461
  label = input.required(/* @ts-ignore */
@@ -5533,93 +6475,261 @@ class MetricCard {
5533
6475
  /** Badge de esquina opcional, pegado al valor (ej. "+2%"). */
5534
6476
  badge = input(/* @ts-ignore */
5535
6477
  ...(ngDevMode ? [undefined, { debugName: "badge" }] : /* istanbul ignore next */ []));
6478
+ /** Dibuja el esqueleto en lugar del contenido, conservando las dimensiones. */
6479
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5536
6480
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MetricCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
5537
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MetricCard, isStandalone: true, selector: "kf-metric-card", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, foot: { classPropertyName: "foot", publicName: "foot", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n <span class=\"kf-metric-card__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n </div>\n <div class=\"kf-metric-card__value-row\">\n <div class=\"kf-metric-card__value\" [style.color]=\"valueColor()\">\n {{ value() }}\n @if (unit()) {\n <span class=\"kf-metric-card__unit\">{{ unit() }}</span>\n }\n </div>\n @if (badge()) {\n <span class=\"kf-metric-card__badge\">\n <kf-text as=\"span\" variant=\"overline-strong\" tone=\"ok\">{{ badge() }}</kf-text>\n </span>\n }\n </div>\n @if (foot()) {\n <div class=\"kf-metric-card__foot\">{{ foot() }}</div>\n }\n <div class=\"kf-metric-card__actions\"><ng-content select=\"[actions]\" /></div>\n</kf-card>\n", styles: [":host{display:block}.kf-metric-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kf-space-xs)}.kf-metric-card__icon{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-metric-card__value-row{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-metric-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:1.6rem;line-height:1.1;color:var(--kf-color-ink)}.kf-metric-card__unit{font-size:.85rem;font-weight:500;color:var(--kf-color-text-muted);margin-left:var(--kf-space-xs)}.kf-metric-card__badge{padding:2px var(--kf-space-xs);border-radius:var(--kf-radius-pill);background:var(--kf-color-success-soft)}.kf-metric-card__foot{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-metric-card__actions:empty{display:none}.kf-metric-card__actions{display:flex;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm);margin-top:auto}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
6481
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MetricCard, isStandalone: true, selector: "kf-metric-card", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, foot: { classPropertyName: "foot", publicName: "foot", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-busy": "loading() ? 'true' : null" } }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n @if (loading()) {\n <span\n class=\"kf-metric-card__label-bone kf-bone\"\n style=\"--kf-bone-width: 45%\"\n aria-hidden=\"true\"\n >\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n </span>\n @if (icon()) {\n <span class=\"kf-metric-card__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n }\n } @else {\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n <span class=\"kf-metric-card__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n }\n </div>\n <div class=\"kf-metric-card__value-row\">\n @if (loading()) {\n <div class=\"kf-metric-card__value kf-bone\" style=\"--kf-bone-width: 50%\" aria-hidden=\"true\">\n {{ value() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__value\" [style.color]=\"valueColor()\">\n {{ value() }}\n @if (unit()) {\n <span class=\"kf-metric-card__unit\">{{ unit() }}</span>\n }\n </div>\n @if (badge()) {\n <span class=\"kf-metric-card__badge\">\n <kf-text as=\"span\" variant=\"overline-strong\" tone=\"ok\">{{ badge() }}</kf-text>\n </span>\n }\n }\n </div>\n @if (foot()) {\n @if (loading()) {\n <div class=\"kf-metric-card__foot kf-bone\" style=\"--kf-bone-width: 60%\" aria-hidden=\"true\">\n {{ foot() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__foot\">{{ foot() }}</div>\n }\n }\n <div class=\"kf-metric-card__actions\" [class.kf-metric-card__actions--loading]=\"loading()\">\n <ng-content select=\"[actions]\" />\n </div>\n</kf-card>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-metric-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kf-space-xs)}.kf-metric-card__icon{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-metric-card__value-row{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-metric-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:1.6rem;line-height:1.1;color:var(--kf-color-ink)}.kf-metric-card__unit{font-size:.85rem;font-weight:500;color:var(--kf-color-text-muted);margin-left:var(--kf-space-xs)}.kf-metric-card__badge{padding:2px var(--kf-space-xs);border-radius:var(--kf-radius-pill);background:var(--kf-color-success-soft)}.kf-metric-card__foot{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-metric-card__actions:empty{display:none}.kf-metric-card__actions{display:flex;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm);margin-top:auto}.kf-metric-card__actions--loading{visibility:hidden}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap", "loading"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
5538
6482
  }
5539
6483
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MetricCard, decorators: [{
5540
6484
  type: Component,
5541
- args: [{ selector: 'kf-metric-card', imports: [Card, Icon, Text], template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n <span class=\"kf-metric-card__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n </div>\n <div class=\"kf-metric-card__value-row\">\n <div class=\"kf-metric-card__value\" [style.color]=\"valueColor()\">\n {{ value() }}\n @if (unit()) {\n <span class=\"kf-metric-card__unit\">{{ unit() }}</span>\n }\n </div>\n @if (badge()) {\n <span class=\"kf-metric-card__badge\">\n <kf-text as=\"span\" variant=\"overline-strong\" tone=\"ok\">{{ badge() }}</kf-text>\n </span>\n }\n </div>\n @if (foot()) {\n <div class=\"kf-metric-card__foot\">{{ foot() }}</div>\n }\n <div class=\"kf-metric-card__actions\"><ng-content select=\"[actions]\" /></div>\n</kf-card>\n", styles: [":host{display:block}.kf-metric-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kf-space-xs)}.kf-metric-card__icon{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-metric-card__value-row{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-metric-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:1.6rem;line-height:1.1;color:var(--kf-color-ink)}.kf-metric-card__unit{font-size:.85rem;font-weight:500;color:var(--kf-color-text-muted);margin-left:var(--kf-space-xs)}.kf-metric-card__badge{padding:2px var(--kf-space-xs);border-radius:var(--kf-radius-pill);background:var(--kf-color-success-soft)}.kf-metric-card__foot{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-metric-card__actions:empty{display:none}.kf-metric-card__actions{display:flex;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm);margin-top:auto}\n"] }]
5542
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], foot: [{ type: i0.Input, args: [{ isSignal: true, alias: "foot", required: false }] }], valueColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColor", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }] } });
5543
-
5544
- /**
5545
- * `<dialog>` abiertos vía `showModal()` actualmente activos, en orden de
5546
- * apertura. Con diálogos anidados el navegador apila el top layer: el más
5547
- * reciente queda arriba e inertiza a los anteriores (incluidos otros
5548
- * `<dialog>` ya abiertos), así que el ÚLTIMO de esta lista es el único
5549
- * candidato válido para recibir el overlay container — nunca "cualquier
5550
- * `dialog[open]` de la página", que rompería con dos `kf-modal` anidados o
5551
- * con dos independientes donde solo uno está abierto.
5552
- */
5553
- const openModalDialogs = [];
5554
- /** Llamado por `Modal` cuando su `<dialog>` pasa a estar abierto. */
5555
- function registerOpenModalDialog(dialog) {
5556
- if (!openModalDialogs.includes(dialog)) {
5557
- openModalDialogs.push(dialog);
5558
- }
5559
- }
5560
- /** Llamado por `Modal` cuando su `<dialog>` se cierra o el componente se destruye. */
5561
- function unregisterOpenModalDialog(dialog) {
5562
- const index = openModalDialogs.indexOf(dialog);
5563
- if (index !== -1) {
5564
- openModalDialogs.splice(index, 1);
5565
- }
5566
- }
5567
- function topmostOpenModalDialog() {
5568
- return openModalDialogs.at(-1);
5569
- }
6485
+ args: [{ selector: 'kf-metric-card', imports: [Card, Icon, Text], host: {
6486
+ '[attr.aria-busy]': "loading() ? 'true' : null",
6487
+ }, template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n @if (loading()) {\n <span\n class=\"kf-metric-card__label-bone kf-bone\"\n style=\"--kf-bone-width: 45%\"\n aria-hidden=\"true\"\n >\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n </span>\n @if (icon()) {\n <span class=\"kf-metric-card__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n }\n } @else {\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n <span class=\"kf-metric-card__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n }\n </div>\n <div class=\"kf-metric-card__value-row\">\n @if (loading()) {\n <div class=\"kf-metric-card__value kf-bone\" style=\"--kf-bone-width: 50%\" aria-hidden=\"true\">\n {{ value() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__value\" [style.color]=\"valueColor()\">\n {{ value() }}\n @if (unit()) {\n <span class=\"kf-metric-card__unit\">{{ unit() }}</span>\n }\n </div>\n @if (badge()) {\n <span class=\"kf-metric-card__badge\">\n <kf-text as=\"span\" variant=\"overline-strong\" tone=\"ok\">{{ badge() }}</kf-text>\n </span>\n }\n }\n </div>\n @if (foot()) {\n @if (loading()) {\n <div class=\"kf-metric-card__foot kf-bone\" style=\"--kf-bone-width: 60%\" aria-hidden=\"true\">\n {{ foot() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__foot\">{{ foot() }}</div>\n }\n }\n <div class=\"kf-metric-card__actions\" [class.kf-metric-card__actions--loading]=\"loading()\">\n <ng-content select=\"[actions]\" />\n </div>\n</kf-card>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-metric-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kf-space-xs)}.kf-metric-card__icon{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-metric-card__value-row{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-metric-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:1.6rem;line-height:1.1;color:var(--kf-color-ink)}.kf-metric-card__unit{font-size:.85rem;font-weight:500;color:var(--kf-color-text-muted);margin-left:var(--kf-space-xs)}.kf-metric-card__badge{padding:2px var(--kf-space-xs);border-radius:var(--kf-radius-pill);background:var(--kf-color-success-soft)}.kf-metric-card__foot{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-metric-card__actions:empty{display:none}.kf-metric-card__actions{display:flex;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm);margin-top:auto}.kf-metric-card__actions--loading{visibility:hidden}\n"] }]
6488
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], foot: [{ type: i0.Input, args: [{ isSignal: true, alias: "foot", required: false }] }], valueColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColor", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
6489
+
6490
+ const PROGRESS_STATUS = {
6491
+ DONE: 'done',
6492
+ CURRENT: 'current',
6493
+ PENDING: 'pending',
6494
+ };
5570
6495
  /**
5571
- * Instancias de `OverlayContainer` ya parchadas — evita parchar dos veces
5572
- * el mismo objeto (varios `kf-modal` en el árbol llaman a esta función) y,
5573
- * a la vez, no interfiere con instancias nuevas que arma un `TestBed` fresco
5574
- * en cada spec (root providers se recrean por test).
6496
+ * Texto por defecto (español) de cada estado: el estado nunca se comunica solo
6497
+ * con color. Los componentes que lo muestran aceptan sus propios textos.
5575
6498
  */
5576
- const patchedContainers = new WeakSet();
6499
+ const PROGRESS_STATUS_LABEL = {
6500
+ [PROGRESS_STATUS.DONE]: 'Completado',
6501
+ [PROGRESS_STATUS.CURRENT]: 'En curso',
6502
+ [PROGRESS_STATUS.PENDING]: 'Pendiente',
6503
+ };
6504
+
5577
6505
  /**
5578
- * `showModal()` promueve el `<dialog>` al top layer del navegador y vuelve
5579
- * `inert` todo lo que quede fuera de su subárbol — pero Angular CDK
5580
- * (`Overlay`/`OverlayContainer`, usado por `kf-select`, `kf-multi-select`,
5581
- * `kf-dropdown`, `kf-tooltip` y el selector de tamaño de `kf-pagination`)
5582
- * siempre appendea `.cdk-overlay-container` como hijo de `<body>`, que
5583
- * queda fuera del `<dialog>` y por lo tanto inerte: el overlay se ve pero
5584
- * no recibe clicks.
6506
+ * Marca pequeña de estado para leyendas y listas de hitos: círculo relleno
6507
+ * con check (completado), anillo con punto (en curso) o anillo vacío
6508
+ * (pendiente). La forma comunica el estado y el color solo lo refuerza.
6509
+ * `exception` la pinta con borde discontinuo y tono de aviso.
5585
6510
  *
5586
- * No existe una forma de reemplazar, sin que el consumidor configure nada,
5587
- * QUÉ IMPLEMENTACIÓN resuelve Angular DI para el token `OverlayContainer`:
5588
- * es `providedIn: 'root'`, y la instancia se crea una sola vez contra el
5589
- * inyector RAÍZ de la app la primera vez que algo la necesita — incluida
5590
- * la propia `Overlay`, que la inyecta puertas adentro de SU construcción,
5591
- * no en el inyector del componente que gatilló esa construcción. Un
5592
- * `providers` a nivel de componente (`kf-modal`, o incluso `kf-select`)
5593
- * nunca llega a interceptar esa creación; solo un `providers` explícito en
5594
- * el bootstrap de cada app lo haría, y eso es justo la configuración manual
5595
- * que se quiere evitar.
6511
+ * Es decorativa por defecto (`aria-hidden`): el texto del estado va en el
6512
+ * contexto que la usa. Para usarla suelta (ej. una leyenda sin texto al lado),
6513
+ * `label` la expone como imagen con ese nombre.
5596
6514
  *
5597
- * Por eso el fix no reemplaza la clase vía DI: parcha, una sola vez, el
5598
- * MISMO objeto singleton que ya usa toda la app (la instancia real que
5599
- * `Modal` recibe con un `inject(OverlayContainer)` normal — automático
5600
- * apenas se usa `kf-modal`, sin tocar ningún `providers`). `getContainerElement`
5601
- * sigue delegando en el original para crear/cachear el div la primera vez;
5602
- * lo único que se agrega es reubicarlo, en cada llamada, dentro del
5603
- * `<dialog>` abierto más reciente (o de vuelta en `<body>` si no hay
5604
- * ninguno) antes de devolverlo — mover el div se lleva puestos a sus hijos,
5605
- * así que overlays ya adjuntos antes de abrir el modal quedan igual de
5606
- * bien ubicados que los que se abren después.
6515
+ * <kf-status-mark status="done" small />
6516
+ * <kf-status-mark status="current" label="En curso" />
6517
+ *
6518
+ * Tamaño: variables CSS `--kf-status-mark-size` y `--kf-status-mark-size-sm`.
6519
+ */
6520
+ class StatusMark {
6521
+ status = input.required(/* @ts-ignore */
6522
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
6523
+ /** Versión reducida, para listas de hitos. */
6524
+ small = input(false, /* @ts-ignore */
6525
+ ...(ngDevMode ? [{ debugName: "small" }] : /* istanbul ignore next */ []));
6526
+ /** Hito de excepción: borde discontinuo y tono de aviso. */
6527
+ exception = input(false, /* @ts-ignore */
6528
+ ...(ngDevMode ? [{ debugName: "exception" }] : /* istanbul ignore next */ []));
6529
+ /** Nombre accesible; sin él la marca es decorativa. */
6530
+ label = input(null, /* @ts-ignore */
6531
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
6532
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusMark, deps: [], target: i0.ɵɵFactoryTarget.Component });
6533
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: StatusMark, isStandalone: true, selector: "kf-status-mark", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, small: { classPropertyName: "small", publicName: "small", isSignal: true, isRequired: false, transformFunction: null }, exception: { classPropertyName: "exception", publicName: "exception", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-status-mark-host--sm": "small()" } }, ngImport: i0, template: `<span
6534
+ class="kf-status-mark"
6535
+ [class]="'kf-status-mark--' + status()"
6536
+ [class.kf-status-mark--exception]="exception()"
6537
+ [attr.role]="label() ? 'img' : null"
6538
+ [attr.aria-label]="label() || null"
6539
+ [attr.aria-hidden]="label() ? null : 'true'"
6540
+ ></span>`, isInline: true, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-mark-size, 1.375rem)}:host(.kf-status-mark-host--sm){--_size: var(--kf-status-mark-size-sm, 1rem)}.kf-status-mark{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);border-radius:50%;box-sizing:border-box;background:var(--kf-color-white)}.kf-status-mark--done{background:var(--kf-color-success-strong)}.kf-status-mark--done:after{content:\"\";width:calc(var(--_size) * .23);height:calc(var(--_size) * .41);border:solid var(--kf-color-white);border-width:0 2px 2px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-mark--current{border:2px solid var(--kf-color-primary);box-shadow:0 0 0 calc(var(--_size) * .14) var(--kf-color-primary-soft)}.kf-status-mark--current:after{content:\"\";width:calc(var(--_size) * .36);height:calc(var(--_size) * .36);border-radius:50%;background:var(--kf-color-primary)}.kf-status-mark--pending{border:2px solid var(--kf-color-text-muted)}.kf-status-mark--exception{border-style:dashed;border-color:var(--kf-color-warning-strong)}.kf-status-mark--exception.kf-status-mark--done{background:var(--kf-color-warning-strong)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6541
+ }
6542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusMark, decorators: [{
6543
+ type: Component,
6544
+ args: [{ selector: 'kf-status-mark', template: `<span
6545
+ class="kf-status-mark"
6546
+ [class]="'kf-status-mark--' + status()"
6547
+ [class.kf-status-mark--exception]="exception()"
6548
+ [attr.role]="label() ? 'img' : null"
6549
+ [attr.aria-label]="label() || null"
6550
+ [attr.aria-hidden]="label() ? null : 'true'"
6551
+ ></span>`, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.kf-status-mark-host--sm]': 'small()' }, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-mark-size, 1.375rem)}:host(.kf-status-mark-host--sm){--_size: var(--kf-status-mark-size-sm, 1rem)}.kf-status-mark{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);border-radius:50%;box-sizing:border-box;background:var(--kf-color-white)}.kf-status-mark--done{background:var(--kf-color-success-strong)}.kf-status-mark--done:after{content:\"\";width:calc(var(--_size) * .23);height:calc(var(--_size) * .41);border:solid var(--kf-color-white);border-width:0 2px 2px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-mark--current{border:2px solid var(--kf-color-primary);box-shadow:0 0 0 calc(var(--_size) * .14) var(--kf-color-primary-soft)}.kf-status-mark--current:after{content:\"\";width:calc(var(--_size) * .36);height:calc(var(--_size) * .36);border-radius:50%;background:var(--kf-color-primary)}.kf-status-mark--pending{border:2px solid var(--kf-color-text-muted)}.kf-status-mark--exception{border-style:dashed;border-color:var(--kf-color-warning-strong)}.kf-status-mark--exception.kf-status-mark--done{background:var(--kf-color-warning-strong)}\n"] }]
6552
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], small: [{ type: i0.Input, args: [{ isSignal: true, alias: "small", required: false }] }], exception: [{ type: i0.Input, args: [{ isSignal: true, alias: "exception", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
6553
+
6554
+ /**
6555
+ * Nodo circular de una etapa: ícono de la etapa más una insignia de estado.
6556
+ * La forma comunica el estado (relleno con check, anillo con punto, anillo
6557
+ * vacío) y el color solo lo refuerza.
6558
+ *
6559
+ * Es decorativo por defecto (`aria-hidden`): el texto del estado debe estar en
6560
+ * el contexto que lo usa. Para usarlo suelto, `label` lo expone como imagen
6561
+ * con ese nombre.
6562
+ *
6563
+ * <kf-status-node status="current" icon="truck" />
6564
+ * <kf-status-node status="done" icon="check" size="sm" label="Entrega completada" />
6565
+ *
6566
+ * Tamaño: variables CSS `--kf-status-node-size` y `--kf-status-node-size-sm`.
5607
6567
  */
5608
- function installModalAwareOverlayContainer(container) {
5609
- if (patchedContainers.has(container)) {
5610
- return;
6568
+ class StatusNode {
6569
+ status = input.required(/* @ts-ignore */
6570
+ ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
6571
+ icon = input.required(/* @ts-ignore */
6572
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
6573
+ size = input('md', /* @ts-ignore */
6574
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6575
+ /** Nombre accesible; sin él el nodo es decorativo. */
6576
+ label = input(null, /* @ts-ignore */
6577
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
6578
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusNode, deps: [], target: i0.ɵɵFactoryTarget.Component });
6579
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: StatusNode, isStandalone: true, selector: "kf-status-node", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-status-node-host--sm": "size() === 'sm'" } }, ngImport: i0, template: `
6580
+ <span
6581
+ class="kf-status-node"
6582
+ [class]="'kf-status-node--' + status()"
6583
+ [attr.role]="label() ? 'img' : null"
6584
+ [attr.aria-label]="label() || null"
6585
+ [attr.aria-hidden]="label() ? null : 'true'"
6586
+ >
6587
+ <kf-icon [name]="icon()" size="sm" />
6588
+ <span class="kf-status-node__badge"></span>
6589
+ </span>
6590
+ `, isInline: true, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-node-size, 2.125rem);--_halo: calc(var(--_size) * .12);--_badge: calc(var(--_size) * .44)}:host(.kf-status-node-host--sm){--_size: var(--kf-status-node-size-sm, 1.75rem);--_halo: calc(var(--_size) * .11);--_badge: calc(var(--_size) * .46)}.kf-status-node{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);box-sizing:border-box;border-radius:50%;background:var(--kf-color-white);border:2px solid var(--kf-color-text-muted);color:var(--kf-color-text-muted)}.kf-status-node--done{background:var(--kf-color-success-strong);border-color:var(--kf-color-success-strong);color:var(--kf-color-white)}.kf-status-node--current{border-color:var(--kf-color-primary);color:var(--kf-color-primary);box-shadow:0 0 0 var(--_halo) var(--kf-color-primary-soft)}.kf-status-node__badge{position:absolute;right:calc(var(--_badge) * -.33);bottom:calc(var(--_badge) * -.33);display:none;align-items:center;justify-content:center;width:var(--_badge);height:var(--_badge);border:2px solid var(--kf-color-white);border-radius:50%;box-sizing:border-box}.kf-status-node--done .kf-status-node__badge{display:inline-flex;background:var(--kf-color-success-strong)}.kf-status-node--done .kf-status-node__badge:after{content:\"\";width:calc(var(--_badge) * .2);height:calc(var(--_badge) * .4);border:solid var(--kf-color-white);border-width:0 1.5px 1.5px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-node--current .kf-status-node__badge{display:inline-flex;background:var(--kf-color-primary)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6591
+ }
6592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusNode, decorators: [{
6593
+ type: Component,
6594
+ args: [{ selector: 'kf-status-node', imports: [Icon], template: `
6595
+ <span
6596
+ class="kf-status-node"
6597
+ [class]="'kf-status-node--' + status()"
6598
+ [attr.role]="label() ? 'img' : null"
6599
+ [attr.aria-label]="label() || null"
6600
+ [attr.aria-hidden]="label() ? null : 'true'"
6601
+ >
6602
+ <kf-icon [name]="icon()" size="sm" />
6603
+ <span class="kf-status-node__badge"></span>
6604
+ </span>
6605
+ `, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.kf-status-node-host--sm]': "size() === 'sm'" }, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-node-size, 2.125rem);--_halo: calc(var(--_size) * .12);--_badge: calc(var(--_size) * .44)}:host(.kf-status-node-host--sm){--_size: var(--kf-status-node-size-sm, 1.75rem);--_halo: calc(var(--_size) * .11);--_badge: calc(var(--_size) * .46)}.kf-status-node{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);box-sizing:border-box;border-radius:50%;background:var(--kf-color-white);border:2px solid var(--kf-color-text-muted);color:var(--kf-color-text-muted)}.kf-status-node--done{background:var(--kf-color-success-strong);border-color:var(--kf-color-success-strong);color:var(--kf-color-white)}.kf-status-node--current{border-color:var(--kf-color-primary);color:var(--kf-color-primary);box-shadow:0 0 0 var(--_halo) var(--kf-color-primary-soft)}.kf-status-node__badge{position:absolute;right:calc(var(--_badge) * -.33);bottom:calc(var(--_badge) * -.33);display:none;align-items:center;justify-content:center;width:var(--_badge);height:var(--_badge);border:2px solid var(--kf-color-white);border-radius:50%;box-sizing:border-box}.kf-status-node--done .kf-status-node__badge{display:inline-flex;background:var(--kf-color-success-strong)}.kf-status-node--done .kf-status-node__badge:after{content:\"\";width:calc(var(--_badge) * .2);height:calc(var(--_badge) * .4);border:solid var(--kf-color-white);border-width:0 1.5px 1.5px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-node--current .kf-status-node__badge{display:inline-flex;background:var(--kf-color-primary)}\n"] }]
6606
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
6607
+
6608
+ const DEFAULT_MILESTONE_TIMELINE_LABELS = {
6609
+ heading: 'Avance por etapa',
6610
+ expandAll: 'Expandir todo',
6611
+ collapseAll: 'Contraer todo',
6612
+ statusDone: PROGRESS_STATUS_LABEL[PROGRESS_STATUS.DONE],
6613
+ statusPending: PROGRESS_STATUS_LABEL[PROGRESS_STATUS.PENDING],
6614
+ currentMeta: (done, total) => `${PROGRESS_STATUS_LABEL[PROGRESS_STATUS.CURRENT]} · ${done} de ${total}`,
6615
+ next: 'Siguiente',
6616
+ exceptionsTitle: 'Observaciones · no cuentan en el avance',
6617
+ noExceptions: 'Sin observaciones registradas.',
6618
+ itemDone: 'completado',
6619
+ itemPending: 'pendiente',
6620
+ };
6621
+
6622
+ let nextInstanceId = 0;
6623
+ /**
6624
+ * Línea de tiempo de etapas con hitos. Cada etapa es un encabezado expandible
6625
+ * (`aria-expanded`/`aria-controls`) con un panel de hitos y, si la etapa tiene
6626
+ * hitos de excepción, un bloque aparte que no cuenta en el avance. Una etapa
6627
+ * sin hitos es solo informativa (no es un botón).
6628
+ *
6629
+ * El estado nunca se comunica solo con color: cada etapa y cada hito llevan su
6630
+ * estado en texto (visible o solo para lectores de pantalla) y los nodos y
6631
+ * marcas lo dibujan también con la forma. Todos los textos salen de `labels`
6632
+ * (español por defecto) y las fechas llegan ya formateadas.
6633
+ *
6634
+ * El conector entre etapas se deriva del tamaño del nodo
6635
+ * (`--kf-status-node-size`, default `2.125rem`) y de la separación del
6636
+ * encabezado: cambiar el tamaño del nodo mueve la línea con él.
6637
+ *
6638
+ * <kf-milestone-timeline
6639
+ * [groups]="stages()"
6640
+ * [(expandedIds)]="expanded"
6641
+ * nextItemId="stage2-docs"
6642
+ * ariaLabel="Avance de la entrega"
6643
+ * [labels]="{ heading: 'Progress', expandAll: 'Expand all' }"
6644
+ * />
6645
+ */
6646
+ class MilestoneTimeline {
6647
+ host = inject(ElementRef);
6648
+ instanceId = `kf-milestone-timeline-${nextInstanceId++}`;
6649
+ groups = input.required(/* @ts-ignore */
6650
+ ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
6651
+ /** Ids de las etapas expandidas, two-way: `[(expandedIds)]`. El padre controla el estado inicial. */
6652
+ expandedIds = model([], /* @ts-ignore */
6653
+ ...(ngDevMode ? [{ debugName: "expandedIds" }] : /* istanbul ignore next */ []));
6654
+ /** Id del hito que se destaca como el siguiente a completar. */
6655
+ nextItemId = input(undefined, /* @ts-ignore */
6656
+ ...(ngDevMode ? [{ debugName: "nextItemId" }] : /* istanbul ignore next */ []));
6657
+ /** Textos; los que no se pasen usan el español por defecto. */
6658
+ labels = input({}, /* @ts-ignore */
6659
+ ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
6660
+ /** Nombre accesible de la lista de etapas. */
6661
+ ariaLabel = input(undefined, /* @ts-ignore */
6662
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
6663
+ /** Nivel (2-6) del título "Avance por etapa", para respetar el esquema de títulos de la página. */
6664
+ headingLevel = input(3, /* @ts-ignore */
6665
+ ...(ngDevMode ? [{ debugName: "headingLevel" }] : /* istanbul ignore next */ []));
6666
+ text = computed(() => ({
6667
+ ...DEFAULT_MILESTONE_TIMELINE_LABELS,
6668
+ ...this.labels(),
6669
+ }), /* @ts-ignore */
6670
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
6671
+ rows = computed(() => this.groups().map((group) => {
6672
+ const main = group.items.filter((item) => !item.exception);
6673
+ const doneCount = main.filter((item) => item.done).length;
6674
+ const exceptions = group.items.filter((item) => item.exception);
6675
+ return {
6676
+ group,
6677
+ main,
6678
+ exceptions,
6679
+ happened: exceptions.filter((item) => item.done),
6680
+ expandable: group.items.length > 0,
6681
+ meta: this.metaOf(group, doneCount, main.length),
6682
+ };
6683
+ }), /* @ts-ignore */
6684
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
6685
+ expandableIds = computed(() => this.groups()
6686
+ .filter((group) => group.items.length > 0)
6687
+ .map((group) => group.id), /* @ts-ignore */
6688
+ ...(ngDevMode ? [{ debugName: "expandableIds" }] : /* istanbul ignore next */ []));
6689
+ hasExpandable = computed(() => this.expandableIds().length > 0, /* @ts-ignore */
6690
+ ...(ngDevMode ? [{ debugName: "hasExpandable" }] : /* istanbul ignore next */ []));
6691
+ allExpanded = computed(() => this.hasExpandable() && this.expandableIds().every((id) => this.isExpanded(id)), /* @ts-ignore */
6692
+ ...(ngDevMode ? [{ debugName: "allExpanded" }] : /* istanbul ignore next */ []));
6693
+ isExpanded(groupId) {
6694
+ return this.expandedIds().includes(groupId);
5611
6695
  }
5612
- patchedContainers.add(container);
5613
- const originalGetContainerElement = container.getContainerElement.bind(container);
5614
- container.getContainerElement = () => {
5615
- const element = originalGetContainerElement();
5616
- const targetParent = topmostOpenModalDialog() ?? element.ownerDocument.body;
5617
- if (element.parentElement !== targetParent) {
5618
- targetParent.appendChild(element);
5619
- }
5620
- return element;
5621
- };
6696
+ headId(groupId) {
6697
+ return `${this.instanceId}-head-${groupId}`;
6698
+ }
6699
+ panelId(groupId) {
6700
+ return `${this.instanceId}-panel-${groupId}`;
6701
+ }
6702
+ itemStatus(item) {
6703
+ return item.done ? PROGRESS_STATUS.DONE : PROGRESS_STATUS.PENDING;
6704
+ }
6705
+ toggle(groupId) {
6706
+ this.expandedIds.update((ids) => ids.includes(groupId) ? ids.filter((id) => id !== groupId) : [...ids, groupId]);
6707
+ }
6708
+ toggleAll() {
6709
+ this.expandedIds.set(this.allExpanded() ? [] : this.expandableIds());
6710
+ }
6711
+ /** Lleva el foco al encabezado de una etapa expandible; devuelve el elemento, o `null` si no existe o no es expandible. */
6712
+ focusGroup(groupId) {
6713
+ // Solo las etapas expandibles tienen un encabezado enfocable (un botón).
6714
+ const head = this.host.nativeElement.querySelector(`button#${this.headId(groupId)}`);
6715
+ head?.focus({ preventScroll: true });
6716
+ return head;
6717
+ }
6718
+ metaOf(group, done, total) {
6719
+ const text = this.text();
6720
+ if (group.status === PROGRESS_STATUS.DONE)
6721
+ return group.date ?? text.statusDone;
6722
+ if (group.status === PROGRESS_STATUS.CURRENT)
6723
+ return text.currentMeta(done, total);
6724
+ return text.statusPending;
6725
+ }
6726
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MilestoneTimeline, deps: [], target: i0.ɵɵFactoryTarget.Component });
6727
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MilestoneTimeline, isStandalone: true, selector: "kf-milestone-timeline", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, expandedIds: { classPropertyName: "expandedIds", publicName: "expandedIds", isSignal: true, isRequired: false, transformFunction: null }, nextItemId: { classPropertyName: "nextItemId", publicName: "nextItemId", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, headingLevel: { classPropertyName: "headingLevel", publicName: "headingLevel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedIds: "expandedIdsChange" }, ngImport: i0, template: "<div class=\"kf-milestone-timeline\">\n <div class=\"kf-milestone-timeline__toolbar\">\n <p class=\"kf-milestone-timeline__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n {{ text().heading }}\n </p>\n @if (hasExpandable()) {\n <kf-button\n class=\"kf-milestone-timeline__toggle-all\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"toggleAll()\"\n >\n {{ allExpanded() ? text().collapseAll : text().expandAll }}\n </kf-button>\n }\n </div>\n\n <ol class=\"kf-milestone-timeline__list\" [attr.aria-label]=\"ariaLabel() ?? null\">\n @for (row of rows(); track row.group.id) {\n <li\n class=\"kf-milestone-timeline__group\"\n [class]=\"'kf-milestone-timeline__group--' + row.group.status\"\n >\n @if (row.expandable) {\n <button\n type=\"button\"\n class=\"kf-milestone-timeline__head\"\n [id]=\"headId(row.group.id)\"\n [attr.aria-expanded]=\"isExpanded(row.group.id)\"\n [attr.aria-controls]=\"panelId(row.group.id)\"\n (click)=\"toggle(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n <kf-icon class=\"kf-milestone-timeline__chevron\" name=\"chevron-down\" size=\"sm\" />\n </button>\n\n <div\n class=\"kf-milestone-timeline__panel\"\n role=\"group\"\n [id]=\"panelId(row.group.id)\"\n [attr.aria-labelledby]=\"headId(row.group.id)\"\n [hidden]=\"!isExpanded(row.group.id)\"\n >\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.main; track item.id) {\n <li\n class=\"kf-milestone-timeline__item\"\n [class.kf-milestone-timeline__item--done]=\"item.done\"\n [class.kf-milestone-timeline__item--next]=\"item.id === nextItemId()\"\n >\n <kf-status-mark [status]=\"itemStatus(item)\" [small]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\"\n >: {{ item.done ? text().itemDone : text().itemPending }}</span\n ></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n @if (item.id === nextItemId()) {\n <kf-badge tone=\"info\">{{ text().next }}</kf-badge>\n }\n </li>\n }\n </ul>\n\n @if (row.exceptions.length > 0) {\n <div class=\"kf-milestone-timeline__exceptions\">\n <p class=\"kf-milestone-timeline__exceptions-title\">{{ text().exceptionsTitle }}</p>\n @if (row.happened.length > 0) {\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.happened; track item.id) {\n <li class=\"kf-milestone-timeline__item kf-milestone-timeline__item--done\">\n <kf-status-mark status=\"done\" [small]=\"true\" [exception]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\">: {{ text().itemDone }}</span></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n </li>\n }\n </ul>\n } @else {\n <p class=\"kf-milestone-timeline__exceptions-empty\">{{ text().noExceptions }}</p>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-milestone-timeline__head kf-milestone-timeline__head--static\"\n [id]=\"headId(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n </div>\n }\n </li>\n }\n </ol>\n</div>\n\n<!-- Nodo + t\u00EDtulo + estado de una etapa, compartido por el encabezado bot\u00F3n y el est\u00E1tico. -->\n<ng-template #headContent let-row>\n <kf-status-node [status]=\"row.group.status\" [icon]=\"row.group.icon\" />\n <span class=\"kf-milestone-timeline__text\">\n <span class=\"kf-milestone-timeline__title\">{{ row.group.label }}</span>\n <span class=\"kf-milestone-timeline__meta\">\n @if (row.group.status === 'done') {\n <span class=\"kf-sr-only\">{{ text().statusDone }}. </span>\n }\n {{ row.meta }}\n </span>\n </span>\n</ng-template>\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px}.kf-milestone-timeline{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-milestone-timeline__heading{margin:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-milestone-timeline__list{list-style:none;margin:0;padding:0}.kf-milestone-timeline__group{position:relative}.kf-milestone-timeline__group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border-strong)}.kf-milestone-timeline__group--done:not(:last-child):before{background:var(--kf-color-success-strong)}.kf-milestone-timeline__head{position:relative;z-index:1;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;width:100%;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter);background:none;border:0;border-radius:var(--kf-radius-sm);text-align:left;font:inherit;color:inherit}button.kf-milestone-timeline__head{cursor:pointer}button.kf-milestone-timeline__head:hover{background:var(--kf-color-surface)}button.kf-milestone-timeline__head:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px}.kf-milestone-timeline__text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-milestone-timeline__title{font-weight:700;font-size:var(--kf-font-size-body);color:var(--kf-color-ink)}.kf-milestone-timeline__meta{font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__group--current .kf-milestone-timeline__meta{color:var(--kf-color-primary-active);font-weight:600}.kf-milestone-timeline__chevron{flex:none;align-self:center;color:var(--kf-color-text);transition:transform .15s ease}.kf-milestone-timeline__head[aria-expanded=true] .kf-milestone-timeline__chevron{transform:rotate(180deg)}.kf-milestone-timeline__panel{padding-block:var(--kf-space-sm) var(--kf-space-md);padding-inline:calc(var(--_gutter) + var(--_node) + var(--kf-space-md)) var(--_gutter)}.kf-milestone-timeline__panel[hidden]{display:none}.kf-milestone-timeline__items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__item{display:flex;align-items:flex-start;gap:var(--kf-space-sm);font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__item kf-status-mark{margin-top:2px}.kf-milestone-timeline__item--done{color:var(--kf-color-ink)}.kf-milestone-timeline__item--next{color:var(--kf-color-ink);font-weight:700}.kf-milestone-timeline__item-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.kf-milestone-timeline__item-date{font-size:var(--kf-font-size-caption);font-weight:400;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions{margin-top:var(--kf-space-md);padding-top:var(--kf-space-sm);border-top:1px dashed var(--kf-color-border-strong)}.kf-milestone-timeline__exceptions-title{margin:0 0 var(--kf-space-sm);font-size:var(--kf-font-size-caption);font-weight:700;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions-empty{margin:0;font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}@media(max-width:640px),(pointer:coarse){.kf-milestone-timeline__toggle-all{--kf-button-min-height: 44px}}@media(prefers-reduced-motion:reduce){.kf-milestone-timeline__chevron{transition:none}}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot", "truncate"] }, { kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StatusMark, selector: "kf-status-mark", inputs: ["status", "small", "exception", "label"] }, { kind: "component", type: StatusNode, selector: "kf-status-node", inputs: ["status", "icon", "size", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5622
6728
  }
6729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MilestoneTimeline, decorators: [{
6730
+ type: Component,
6731
+ args: [{ selector: 'kf-milestone-timeline', imports: [Badge, Button, Icon, NgTemplateOutlet, StatusMark, StatusNode], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"kf-milestone-timeline\">\n <div class=\"kf-milestone-timeline__toolbar\">\n <p class=\"kf-milestone-timeline__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n {{ text().heading }}\n </p>\n @if (hasExpandable()) {\n <kf-button\n class=\"kf-milestone-timeline__toggle-all\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"toggleAll()\"\n >\n {{ allExpanded() ? text().collapseAll : text().expandAll }}\n </kf-button>\n }\n </div>\n\n <ol class=\"kf-milestone-timeline__list\" [attr.aria-label]=\"ariaLabel() ?? null\">\n @for (row of rows(); track row.group.id) {\n <li\n class=\"kf-milestone-timeline__group\"\n [class]=\"'kf-milestone-timeline__group--' + row.group.status\"\n >\n @if (row.expandable) {\n <button\n type=\"button\"\n class=\"kf-milestone-timeline__head\"\n [id]=\"headId(row.group.id)\"\n [attr.aria-expanded]=\"isExpanded(row.group.id)\"\n [attr.aria-controls]=\"panelId(row.group.id)\"\n (click)=\"toggle(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n <kf-icon class=\"kf-milestone-timeline__chevron\" name=\"chevron-down\" size=\"sm\" />\n </button>\n\n <div\n class=\"kf-milestone-timeline__panel\"\n role=\"group\"\n [id]=\"panelId(row.group.id)\"\n [attr.aria-labelledby]=\"headId(row.group.id)\"\n [hidden]=\"!isExpanded(row.group.id)\"\n >\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.main; track item.id) {\n <li\n class=\"kf-milestone-timeline__item\"\n [class.kf-milestone-timeline__item--done]=\"item.done\"\n [class.kf-milestone-timeline__item--next]=\"item.id === nextItemId()\"\n >\n <kf-status-mark [status]=\"itemStatus(item)\" [small]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\"\n >: {{ item.done ? text().itemDone : text().itemPending }}</span\n ></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n @if (item.id === nextItemId()) {\n <kf-badge tone=\"info\">{{ text().next }}</kf-badge>\n }\n </li>\n }\n </ul>\n\n @if (row.exceptions.length > 0) {\n <div class=\"kf-milestone-timeline__exceptions\">\n <p class=\"kf-milestone-timeline__exceptions-title\">{{ text().exceptionsTitle }}</p>\n @if (row.happened.length > 0) {\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.happened; track item.id) {\n <li class=\"kf-milestone-timeline__item kf-milestone-timeline__item--done\">\n <kf-status-mark status=\"done\" [small]=\"true\" [exception]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\">: {{ text().itemDone }}</span></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n </li>\n }\n </ul>\n } @else {\n <p class=\"kf-milestone-timeline__exceptions-empty\">{{ text().noExceptions }}</p>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-milestone-timeline__head kf-milestone-timeline__head--static\"\n [id]=\"headId(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n </div>\n }\n </li>\n }\n </ol>\n</div>\n\n<!-- Nodo + t\u00EDtulo + estado de una etapa, compartido por el encabezado bot\u00F3n y el est\u00E1tico. -->\n<ng-template #headContent let-row>\n <kf-status-node [status]=\"row.group.status\" [icon]=\"row.group.icon\" />\n <span class=\"kf-milestone-timeline__text\">\n <span class=\"kf-milestone-timeline__title\">{{ row.group.label }}</span>\n <span class=\"kf-milestone-timeline__meta\">\n @if (row.group.status === 'done') {\n <span class=\"kf-sr-only\">{{ text().statusDone }}. </span>\n }\n {{ row.meta }}\n </span>\n </span>\n</ng-template>\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px}.kf-milestone-timeline{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-milestone-timeline__heading{margin:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-milestone-timeline__list{list-style:none;margin:0;padding:0}.kf-milestone-timeline__group{position:relative}.kf-milestone-timeline__group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border-strong)}.kf-milestone-timeline__group--done:not(:last-child):before{background:var(--kf-color-success-strong)}.kf-milestone-timeline__head{position:relative;z-index:1;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;width:100%;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter);background:none;border:0;border-radius:var(--kf-radius-sm);text-align:left;font:inherit;color:inherit}button.kf-milestone-timeline__head{cursor:pointer}button.kf-milestone-timeline__head:hover{background:var(--kf-color-surface)}button.kf-milestone-timeline__head:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px}.kf-milestone-timeline__text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-milestone-timeline__title{font-weight:700;font-size:var(--kf-font-size-body);color:var(--kf-color-ink)}.kf-milestone-timeline__meta{font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__group--current .kf-milestone-timeline__meta{color:var(--kf-color-primary-active);font-weight:600}.kf-milestone-timeline__chevron{flex:none;align-self:center;color:var(--kf-color-text);transition:transform .15s ease}.kf-milestone-timeline__head[aria-expanded=true] .kf-milestone-timeline__chevron{transform:rotate(180deg)}.kf-milestone-timeline__panel{padding-block:var(--kf-space-sm) var(--kf-space-md);padding-inline:calc(var(--_gutter) + var(--_node) + var(--kf-space-md)) var(--_gutter)}.kf-milestone-timeline__panel[hidden]{display:none}.kf-milestone-timeline__items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__item{display:flex;align-items:flex-start;gap:var(--kf-space-sm);font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__item kf-status-mark{margin-top:2px}.kf-milestone-timeline__item--done{color:var(--kf-color-ink)}.kf-milestone-timeline__item--next{color:var(--kf-color-ink);font-weight:700}.kf-milestone-timeline__item-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.kf-milestone-timeline__item-date{font-size:var(--kf-font-size-caption);font-weight:400;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions{margin-top:var(--kf-space-md);padding-top:var(--kf-space-sm);border-top:1px dashed var(--kf-color-border-strong)}.kf-milestone-timeline__exceptions-title{margin:0 0 var(--kf-space-sm);font-size:var(--kf-font-size-caption);font-weight:700;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions-empty{margin:0;font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}@media(max-width:640px),(pointer:coarse){.kf-milestone-timeline__toggle-all{--kf-button-min-height: 44px}}@media(prefers-reduced-motion:reduce){.kf-milestone-timeline__chevron{transition:none}}\n"] }]
6732
+ }], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: true }] }], expandedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIds", required: false }] }, { type: i0.Output, args: ["expandedIdsChange"] }], nextItemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextItemId", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], headingLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingLevel", required: false }] }] } });
5623
6733
 
5624
6734
  let modalId = 0;
5625
6735
  /** Controles que pueden recibir el foco inicial. */
@@ -6021,13 +7131,538 @@ class Panel {
6021
7131
  */
6022
7132
  actionClick = output();
6023
7133
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, deps: [], target: i0.ɵɵFactoryTarget.Component });
6024
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Panel, isStandalone: true, selector: "kf-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
7134
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Panel, isStandalone: true, selector: "kf-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap", "loading"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
6025
7135
  }
6026
7136
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, decorators: [{
6027
7137
  type: Component,
6028
7138
  args: [{ selector: 'kf-panel', imports: [Card, Divider, Link], template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
6029
7139
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
6030
7140
 
7141
+ let nextPopoverId = 0;
7142
+ /** Separación (px) entre el anfitrión y el panel. */
7143
+ const POPOVER_GAP = 8;
7144
+ /** Margen (px) mínimo entre el panel y el borde del viewport. */
7145
+ const POPOVER_VIEWPORT_MARGIN = 8;
7146
+ /**
7147
+ * Ventana (ms) tras abrir en la que un `scroll` no cierra el panel: enfocar el
7148
+ * anfitrión puede desplazar su contenedor para dejarlo a la vista, y ese
7149
+ * `scroll` llega después del `focusin` que abrió el panel.
7150
+ */
7151
+ const SCROLL_GRACE_MS = 150;
7152
+ const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
7153
+ /**
7154
+ * Panel visual del popover — componente interno, no exportado en
7155
+ * `public-api.ts`. `PopoverDirective` es la única forma pública de usarlo.
7156
+ *
7157
+ * `role="tooltip"` cuando es solo informativo (el patrón WAI-ARIA para
7158
+ * contenido que el lector anuncia vía `aria-describedby` del anfitrión) y
7159
+ * `role="dialog"` no modal cuando es `interactive` (puede tener enlaces o
7160
+ * botones adentro, que un `tooltip` no admite).
7161
+ */
7162
+ class PopoverPanel {
7163
+ template = input.required(/* @ts-ignore */
7164
+ ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
7165
+ interactive = input(false, /* @ts-ignore */
7166
+ ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
7167
+ label = input(null, /* @ts-ignore */
7168
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
7169
+ pointerEntered = output();
7170
+ pointerLeft = output();
7171
+ focusEntered = output();
7172
+ focusLeft = output();
7173
+ onPointerEnter(event) {
7174
+ if (event.pointerType !== 'touch') {
7175
+ this.pointerEntered.emit();
7176
+ }
7177
+ }
7178
+ onPointerLeave(event) {
7179
+ if (event.pointerType !== 'touch') {
7180
+ this.pointerLeft.emit();
7181
+ }
7182
+ }
7183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
7184
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: PopoverPanel, isStandalone: true, selector: "kf-popover-panel", inputs: { template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: true, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointerEntered: "pointerEntered", pointerLeft: "pointerLeft", focusEntered: "focusEntered", focusLeft: "focusLeft" }, host: { listeners: { "pointerenter": "onPointerEnter($event)", "pointerleave": "onPointerLeave($event)", "focusin": "focusEntered.emit()", "focusout": "focusLeft.emit($event)" }, properties: { "attr.role": "interactive() ? 'dialog' : 'tooltip'", "attr.aria-label": "label()", "class.kf-popover-panel--interactive": "interactive()" } }, ngImport: i0, template: `<ng-container [ngTemplateOutlet]="template()" />`, isInline: true, styles: [":host{display:block;box-sizing:border-box;max-width:min(22rem,calc(100vw - 2 * var(--kf-space-sm)));max-height:calc(100vh - 2 * var(--kf-space-sm));padding:var(--kf-space-md);overflow:auto;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);color:var(--kf-color-text);font-family:var(--kf-font-body);font-size:var(--kf-font-size-body-sm);line-height:1.45;box-shadow:var(--kf-shadow-lg);animation:kf-popover-enter .12s ease-out}:host(:not(.kf-popover-panel--interactive)){pointer-events:none}@media(prefers-reduced-motion:reduce){:host{animation:none}}@keyframes kf-popover-enter{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7185
+ }
7186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverPanel, decorators: [{
7187
+ type: Component,
7188
+ args: [{ selector: 'kf-popover-panel', imports: [NgTemplateOutlet], template: `<ng-container [ngTemplateOutlet]="template()" />`, changeDetection: ChangeDetectionStrategy.OnPush, host: {
7189
+ '[attr.role]': "interactive() ? 'dialog' : 'tooltip'",
7190
+ '[attr.aria-label]': 'label()',
7191
+ '[class.kf-popover-panel--interactive]': 'interactive()',
7192
+ '(pointerenter)': 'onPointerEnter($event)',
7193
+ '(pointerleave)': 'onPointerLeave($event)',
7194
+ '(focusin)': 'focusEntered.emit()',
7195
+ '(focusout)': 'focusLeft.emit($event)',
7196
+ }, styles: [":host{display:block;box-sizing:border-box;max-width:min(22rem,calc(100vw - 2 * var(--kf-space-sm)));max-height:calc(100vh - 2 * var(--kf-space-sm));padding:var(--kf-space-md);overflow:auto;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);color:var(--kf-color-text);font-family:var(--kf-font-body);font-size:var(--kf-font-size-body-sm);line-height:1.45;box-shadow:var(--kf-shadow-lg);animation:kf-popover-enter .12s ease-out}:host(:not(.kf-popover-panel--interactive)){pointer-events:none}@media(prefers-reduced-motion:reduce){:host{animation:none}}@keyframes kf-popover-enter{0%{opacity:0}to{opacity:1}}\n"] }]
7197
+ }], propDecorators: { template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: true }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], pointerEntered: [{ type: i0.Output, args: ["pointerEntered"] }], pointerLeft: [{ type: i0.Output, args: ["pointerLeft"] }], focusEntered: [{ type: i0.Output, args: ["focusEntered"] }], focusLeft: [{ type: i0.Output, args: ["focusLeft"] }] } });
7198
+ /**
7199
+ * Popover de contenido enriquecido anclado a un elemento — la contraparte de
7200
+ * `kfTooltip` para cuando el texto plano no alcanza (varias líneas, énfasis,
7201
+ * un desglose, enlaces). Mismo mecanismo de `@angular/cdk/overlay`. El host
7202
+ * sigue siendo el elemento que recibe el foco: la directiva no lo hace
7203
+ * focusable, así que va sobre un `<button>`, un `<a>` o un elemento con
7204
+ * `tabindex="0"`.
7205
+ *
7206
+ * <button [kfPopover]="detail" kfPopoverPlacement="top">Etapa</button>
7207
+ * <ng-template #detail><strong>Recepción</strong><p>Completada el 12/05.</p></ng-template>
7208
+ *
7209
+ * Apertura: puntero (hover, tras `kfPopoverShowDelay` ms; NO se abre con puntero
7210
+ * táctil) y foco de teclado (al instante, solo si el foco es visible: un
7211
+ * clic con el mouse no lo abre). Cierre: Escape (descarta el panel hasta que
7212
+ * el puntero y el foco salgan y vuelvan), salida del puntero tras
7213
+ * `kfPopoverHideDelay` ms, pérdida de foco, scroll de cualquier contenedor y
7214
+ * redimensionado de la ventana. El panel se reposiciona dentro del viewport
7215
+ * (invierte el lado si no cabe).
7216
+ *
7217
+ * Con `kfPopoverInteractive`, el puntero puede entrar al panel sin cerrarlo y
7218
+ * el panel es un `dialog` no modal: Tab desde el anfitrión entra al primer
7219
+ * control del panel y Mayús+Tab desde ese control vuelve al anfitrión. Sin
7220
+ * `kfPopoverInteractive` (default), el panel es un `tooltip` que no captura
7221
+ * el puntero y no debe contener controles.
7222
+ *
7223
+ * Si el anfitrión no es interactivo pero vive dentro de un control (un botón,
7224
+ * un enlace, un elemento con `tabindex`), los eventos de puntero, foco y
7225
+ * teclado se escuchan en ese control y el panel se ancla al anfitrión. Es el
7226
+ * caso de un indicador dentro de una celda interactiva de `kf-data-table`:
7227
+ *
7228
+ * <ng-template #stageCell let-row>
7229
+ * <span [kfPopover]="detail"><kf-status-mark [status]="row.status" /></span>
7230
+ * </ng-template>
7231
+ *
7232
+ * Accesibilidad: mientras está abierto, el elemento que recibe el foco
7233
+ * referencia el panel con `aria-describedby` (se conserva cualquier valor
7234
+ * previo).
7235
+ */
7236
+ class PopoverDirective {
7237
+ /** Contenido del popover (un `ng-template`). `null`/`undefined` = no se abre. */
7238
+ kfPopover = input.required(/* @ts-ignore */
7239
+ ...(ngDevMode ? [{ debugName: "kfPopover" }] : /* istanbul ignore next */ []));
7240
+ /** Lado preferido; si no cabe en el viewport, se invierte. Default `bottom`. */
7241
+ kfPopoverPlacement = input('bottom', /* @ts-ignore */
7242
+ ...(ngDevMode ? [{ debugName: "kfPopoverPlacement" }] : /* istanbul ignore next */ []));
7243
+ /** Retardo (ms) antes de abrir con el puntero. El foco de teclado abre sin retardo. */
7244
+ kfPopoverShowDelay = input(150, { ...(ngDevMode ? { debugName: "kfPopoverShowDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
7245
+ /** Retardo (ms) antes de cerrar al salir el puntero. */
7246
+ kfPopoverHideDelay = input(150, { ...(ngDevMode ? { debugName: "kfPopoverHideDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
7247
+ /** Desactiva el popover (y lo cierra si estaba abierto). */
7248
+ kfPopoverDisabled = input(false, { ...(ngDevMode ? { debugName: "kfPopoverDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
7249
+ /** Permite que el puntero entre al panel sin cerrarlo y que el teclado llegue a sus controles. */
7250
+ kfPopoverInteractive = input(false, { ...(ngDevMode ? { debugName: "kfPopoverInteractive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
7251
+ /** Nombre accesible del panel cuando es `interactive` (rol `dialog`). */
7252
+ kfPopoverLabel = input(null, /* @ts-ignore */
7253
+ ...(ngDevMode ? [{ debugName: "kfPopoverLabel" }] : /* istanbul ignore next */ []));
7254
+ /** Emite `true` al abrirse y `false` al cerrarse. */
7255
+ kfPopoverOpenChange = output();
7256
+ host = inject(ElementRef);
7257
+ overlayService = inject(Overlay);
7258
+ viewContainerRef = inject(ViewContainerRef);
7259
+ injector = inject(Injector);
7260
+ document = inject(DOCUMENT);
7261
+ destroyRef = inject(DestroyRef);
7262
+ panelId = `kf-popover-${nextPopoverId++}`;
7263
+ openState = signal(false, /* @ts-ignore */
7264
+ ...(ngDevMode ? [{ debugName: "openState" }] : /* istanbul ignore next */ []));
7265
+ /** `true` mientras el panel está abierto. */
7266
+ isOpen = this.openState.asReadonly();
7267
+ /**
7268
+ * Elemento que recibe los eventos de puntero, foco y teclado: el propio
7269
+ * anfitrión o, si este no es interactivo pero vive dentro de un control
7270
+ * (botón, enlace, `tabindex`), ese control. `aria-describedby` va en él.
7271
+ */
7272
+ trigger = this.host.nativeElement;
7273
+ overlayRef = null;
7274
+ panelElement = null;
7275
+ showTimer = null;
7276
+ hideTimer = null;
7277
+ openedAt = 0;
7278
+ teardownGlobalListeners = null;
7279
+ destroyed = false;
7280
+ pointerOnHost = false;
7281
+ pointerOnPanel = false;
7282
+ focusOnHost = false;
7283
+ focusOnPanel = false;
7284
+ /** Escape lo descartó: no reabre hasta que puntero y foco hayan salido. */
7285
+ dismissed = false;
7286
+ constructor() {
7287
+ effect(() => {
7288
+ if (this.kfPopoverDisabled()) {
7289
+ untracked(() => this.close());
7290
+ }
7291
+ });
7292
+ // El elemento que recibe los eventos se resuelve tras el primer render: en
7293
+ // una vista embebida (ej. dentro de la celda de un `kf-data-table`) el
7294
+ // anfitrión aún no tiene a su ancestro interactivo en el DOM al construirse.
7295
+ afterNextRender(() => {
7296
+ if (this.destroyed) {
7297
+ return;
7298
+ }
7299
+ const target = this.resolveTrigger();
7300
+ this.trigger = target;
7301
+ const listeners = [
7302
+ ['pointerenter', this.onHostPointerEnter],
7303
+ ['pointerleave', this.onHostPointerLeave],
7304
+ ['focusin', this.onHostFocusIn],
7305
+ ['focusout', this.onHostFocusOut],
7306
+ ['keydown', this.onHostKeydown],
7307
+ ];
7308
+ for (const [type, listener] of listeners) {
7309
+ target.addEventListener(type, listener);
7310
+ }
7311
+ this.destroyRef.onDestroy(() => {
7312
+ for (const [type, listener] of listeners) {
7313
+ target.removeEventListener(type, listener);
7314
+ }
7315
+ });
7316
+ });
7317
+ }
7318
+ ngOnDestroy() {
7319
+ this.destroyed = true;
7320
+ this.clearTimers();
7321
+ this.dispose();
7322
+ }
7323
+ /** Cierra el panel de inmediato, sin retardo. */
7324
+ close() {
7325
+ this.clearTimers();
7326
+ this.dispose();
7327
+ }
7328
+ onHostPointerEnter = (event) => {
7329
+ if (event.pointerType === 'touch') {
7330
+ return;
7331
+ }
7332
+ this.pointerOnHost = true;
7333
+ this.reevaluate(this.kfPopoverShowDelay());
7334
+ };
7335
+ onHostPointerLeave = (event) => {
7336
+ if (event.pointerType === 'touch') {
7337
+ return;
7338
+ }
7339
+ this.pointerOnHost = false;
7340
+ this.reevaluate(this.kfPopoverHideDelay());
7341
+ };
7342
+ onHostFocusIn = (event) => {
7343
+ // Solo foco de teclado: un clic o un toque enfocan el botón pero no
7344
+ // deben abrir el panel (el clic tiene su propia acción).
7345
+ if (!this.isKeyboardFocus(event.target)) {
7346
+ return;
7347
+ }
7348
+ this.focusOnHost = true;
7349
+ this.reevaluate(0);
7350
+ };
7351
+ onHostFocusOut = (event) => {
7352
+ const next = event.relatedTarget;
7353
+ if (next && this.trigger.contains(next)) {
7354
+ return;
7355
+ }
7356
+ this.focusOnHost = false;
7357
+ if (next && this.kfPopoverInteractive() && this.panelElement?.contains(next)) {
7358
+ // El foco pasa al panel: su propio `focusin` lo registra.
7359
+ return;
7360
+ }
7361
+ this.reevaluate(0);
7362
+ };
7363
+ onHostKeydown = (event) => {
7364
+ if (event.key !== 'Tab' ||
7365
+ event.shiftKey ||
7366
+ event.defaultPrevented ||
7367
+ !this.panelElement ||
7368
+ !this.kfPopoverInteractive()) {
7369
+ return;
7370
+ }
7371
+ const first = this.panelElement.querySelector(FOCUSABLE_SELECTOR);
7372
+ if (first) {
7373
+ event.preventDefault();
7374
+ first.focus();
7375
+ }
7376
+ };
7377
+ get wanted() {
7378
+ if (this.kfPopoverDisabled()) {
7379
+ return false;
7380
+ }
7381
+ const interactive = this.kfPopoverInteractive();
7382
+ return (this.pointerOnHost ||
7383
+ this.focusOnHost ||
7384
+ (interactive && (this.pointerOnPanel || this.focusOnPanel)));
7385
+ }
7386
+ /** Abre o cierra según los cuatro orígenes (puntero/foco × anfitrión/panel). */
7387
+ reevaluate(delay) {
7388
+ if (this.wanted) {
7389
+ this.clearHideTimer();
7390
+ if (!this.dismissed) {
7391
+ this.scheduleOpen(delay);
7392
+ }
7393
+ return;
7394
+ }
7395
+ this.dismissed = false;
7396
+ this.clearShowTimer();
7397
+ this.scheduleClose(delay);
7398
+ }
7399
+ scheduleOpen(delay) {
7400
+ if (this.overlayRef || this.showTimer !== null) {
7401
+ return;
7402
+ }
7403
+ if (delay <= 0) {
7404
+ this.open();
7405
+ return;
7406
+ }
7407
+ this.showTimer = setTimeout(() => {
7408
+ this.showTimer = null;
7409
+ if (this.wanted && !this.dismissed) {
7410
+ this.open();
7411
+ }
7412
+ }, delay);
7413
+ }
7414
+ scheduleClose(delay) {
7415
+ if (!this.overlayRef || this.hideTimer !== null) {
7416
+ return;
7417
+ }
7418
+ if (delay <= 0) {
7419
+ this.dispose();
7420
+ return;
7421
+ }
7422
+ this.hideTimer = setTimeout(() => {
7423
+ this.hideTimer = null;
7424
+ if (!this.wanted) {
7425
+ this.dispose();
7426
+ }
7427
+ }, delay);
7428
+ }
7429
+ open() {
7430
+ const template = this.kfPopover();
7431
+ if (this.overlayRef || !template || this.kfPopoverDisabled()) {
7432
+ return;
7433
+ }
7434
+ const positionStrategy = this.overlayService
7435
+ .position()
7436
+ .flexibleConnectedTo(this.host)
7437
+ .withPositions(this.positionsFor(this.kfPopoverPlacement()))
7438
+ .withViewportMargin(POPOVER_VIEWPORT_MARGIN)
7439
+ .withFlexibleDimensions(false)
7440
+ .withPush(true);
7441
+ const overlayRef = this.overlayService.create({
7442
+ positionStrategy,
7443
+ // El cierre por scroll lo resuelve `listenGlobal`: el scroll strategy de
7444
+ // CDK solo ve ancestros registrados como `cdkScrollable`.
7445
+ scrollStrategy: this.overlayService.scrollStrategies.noop(),
7446
+ });
7447
+ const portal = new ComponentPortal(PopoverPanel, this.viewContainerRef, this.injector);
7448
+ const panelRef = overlayRef.attach(portal);
7449
+ panelRef.setInput('template', template);
7450
+ panelRef.setInput('interactive', this.kfPopoverInteractive());
7451
+ panelRef.setInput('label', this.kfPopoverInteractive() ? this.kfPopoverLabel() : null);
7452
+ this.panelElement = panelRef.location.nativeElement;
7453
+ this.panelElement.id = this.panelId;
7454
+ panelRef.instance.pointerEntered.subscribe(() => {
7455
+ this.pointerOnPanel = true;
7456
+ this.reevaluate(0);
7457
+ });
7458
+ panelRef.instance.pointerLeft.subscribe(() => {
7459
+ this.pointerOnPanel = false;
7460
+ this.reevaluate(this.kfPopoverHideDelay());
7461
+ });
7462
+ panelRef.instance.focusEntered.subscribe(() => {
7463
+ this.focusOnPanel = true;
7464
+ this.reevaluate(0);
7465
+ });
7466
+ panelRef.instance.focusLeft.subscribe((event) => this.onPanelFocusOut(event));
7467
+ this.panelElement.addEventListener('keydown', this.onPanelKeydown);
7468
+ // Renderiza el contenido antes de medir, para posicionar con el tamaño real.
7469
+ panelRef.changeDetectorRef.detectChanges();
7470
+ overlayRef.updatePosition();
7471
+ this.overlayRef = overlayRef;
7472
+ this.addDescribedBy();
7473
+ this.listenGlobal();
7474
+ this.openedAt = Date.now();
7475
+ this.openState.set(true);
7476
+ this.kfPopoverOpenChange.emit(true);
7477
+ }
7478
+ dispose() {
7479
+ const overlayRef = this.overlayRef;
7480
+ const wasOpen = overlayRef !== null;
7481
+ // Primero se suelta el estado y después se destruye el panel: quitar del DOM
7482
+ // un elemento con foco dispara `focusout` de forma síncrona, y ese manejador
7483
+ // no debe volver a entrar a `dispose()` con el panel a medio destruir.
7484
+ this.overlayRef = null;
7485
+ this.teardownGlobalListeners?.();
7486
+ this.teardownGlobalListeners = null;
7487
+ this.panelElement?.removeEventListener('keydown', this.onPanelKeydown);
7488
+ this.panelElement = null;
7489
+ this.pointerOnPanel = false;
7490
+ this.focusOnPanel = false;
7491
+ overlayRef?.dispose();
7492
+ this.removeDescribedBy();
7493
+ if (wasOpen && !this.destroyed) {
7494
+ this.openState.set(false);
7495
+ this.kfPopoverOpenChange.emit(false);
7496
+ }
7497
+ }
7498
+ onPanelFocusOut(event) {
7499
+ if (!this.overlayRef) {
7500
+ // `focusout` por quitar del DOM el panel que se está cerrando.
7501
+ return;
7502
+ }
7503
+ this.focusOnPanel = false;
7504
+ const next = event.relatedTarget;
7505
+ if (next && (this.panelElement?.contains(next) || this.trigger.contains(next))) {
7506
+ return;
7507
+ }
7508
+ this.reevaluate(0);
7509
+ }
7510
+ /** Mayús+Tab desde el primer control del panel devuelve el foco al anfitrión. */
7511
+ onPanelKeydown = (event) => {
7512
+ if (event.key !== 'Tab' || !event.shiftKey || !this.panelElement) {
7513
+ return;
7514
+ }
7515
+ const first = this.panelElement.querySelector(FOCUSABLE_SELECTOR);
7516
+ if (first && this.document.activeElement === first) {
7517
+ event.preventDefault();
7518
+ this.focusHost();
7519
+ }
7520
+ };
7521
+ focusHost() {
7522
+ const host = this.trigger;
7523
+ const focusable = host.matches(FOCUSABLE_SELECTOR)
7524
+ ? host
7525
+ : host.querySelector(FOCUSABLE_SELECTOR);
7526
+ focusable?.focus({ preventScroll: true });
7527
+ }
7528
+ /**
7529
+ * Escape, scroll y resize mientras el panel está abierto. En fase de captura
7530
+ * y sobre `document`: `scroll` no burbujea (hay que capturarlo para ver el
7531
+ * de cualquier contenedor) y Escape debe cerrar primero el popover sin
7532
+ * llegar al modal o drawer que lo contenga.
7533
+ */
7534
+ listenGlobal() {
7535
+ const view = this.document.defaultView;
7536
+ const onKeydown = (event) => {
7537
+ if (event.key !== 'Escape') {
7538
+ return;
7539
+ }
7540
+ event.stopPropagation();
7541
+ const focusInPanel = this.panelElement?.contains(this.document.activeElement) ?? false;
7542
+ this.dismissed = true;
7543
+ this.close();
7544
+ if (focusInPanel) {
7545
+ this.focusHost();
7546
+ }
7547
+ };
7548
+ const onScroll = (event) => {
7549
+ const target = event.target;
7550
+ if (Date.now() - this.openedAt < SCROLL_GRACE_MS) {
7551
+ return;
7552
+ }
7553
+ if (target && this.panelElement?.contains(target)) {
7554
+ return;
7555
+ }
7556
+ this.close();
7557
+ };
7558
+ const onResize = () => this.close();
7559
+ this.document.addEventListener('keydown', onKeydown, true);
7560
+ this.document.addEventListener('scroll', onScroll, true);
7561
+ view?.addEventListener('resize', onResize);
7562
+ this.teardownGlobalListeners = () => {
7563
+ this.document.removeEventListener('keydown', onKeydown, true);
7564
+ this.document.removeEventListener('scroll', onScroll, true);
7565
+ view?.removeEventListener('resize', onResize);
7566
+ };
7567
+ }
7568
+ resolveTrigger() {
7569
+ const host = this.host.nativeElement;
7570
+ return host.closest(FOCUSABLE_SELECTOR) ?? host;
7571
+ }
7572
+ /** Foco de teclado (`:focus-visible`); sin soporte del selector, se asume teclado. */
7573
+ isKeyboardFocus(target) {
7574
+ if (!(target instanceof Element)) {
7575
+ return true;
7576
+ }
7577
+ try {
7578
+ return target.matches(':focus-visible');
7579
+ }
7580
+ catch {
7581
+ return true;
7582
+ }
7583
+ }
7584
+ addDescribedBy() {
7585
+ const tokens = this.describedByTokens();
7586
+ if (!tokens.includes(this.panelId)) {
7587
+ tokens.push(this.panelId);
7588
+ this.trigger.setAttribute('aria-describedby', tokens.join(' '));
7589
+ }
7590
+ }
7591
+ removeDescribedBy() {
7592
+ const remaining = this.describedByTokens().filter((token) => token !== this.panelId);
7593
+ if (remaining.length > 0) {
7594
+ this.trigger.setAttribute('aria-describedby', remaining.join(' '));
7595
+ }
7596
+ else {
7597
+ this.trigger.removeAttribute('aria-describedby');
7598
+ }
7599
+ }
7600
+ describedByTokens() {
7601
+ return (this.trigger.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean);
7602
+ }
7603
+ clearShowTimer() {
7604
+ if (this.showTimer !== null) {
7605
+ clearTimeout(this.showTimer);
7606
+ this.showTimer = null;
7607
+ }
7608
+ }
7609
+ clearHideTimer() {
7610
+ if (this.hideTimer !== null) {
7611
+ clearTimeout(this.hideTimer);
7612
+ this.hideTimer = null;
7613
+ }
7614
+ }
7615
+ clearTimers() {
7616
+ this.clearShowTimer();
7617
+ this.clearHideTimer();
7618
+ }
7619
+ /**
7620
+ * Posiciones en orden de preferencia: el lado pedido (centrado, luego
7621
+ * alineado al inicio y al final del anfitrión), el lado opuesto con las
7622
+ * mismas tres alineaciones y, por último, los dos lados perpendiculares.
7623
+ * Así un anfitrión pegado a una esquina encuentra una posición que cabe
7624
+ * entera en vez de depender solo del empuje al viewport.
7625
+ */
7626
+ positionsFor(placement) {
7627
+ const opposite = {
7628
+ top: 'bottom',
7629
+ bottom: 'top',
7630
+ left: 'right',
7631
+ right: 'left',
7632
+ };
7633
+ const perpendicular = placement === 'top' || placement === 'bottom' ? ['right', 'left'] : ['bottom', 'top'];
7634
+ return [placement, opposite[placement], ...perpendicular].flatMap((side) => ['center', 'start', 'end'].map((align) => this.positionFor(side, align)));
7635
+ }
7636
+ positionFor(side, align) {
7637
+ if (side === 'top' || side === 'bottom') {
7638
+ return {
7639
+ originX: align,
7640
+ originY: side === 'top' ? 'top' : 'bottom',
7641
+ overlayX: align,
7642
+ overlayY: side === 'top' ? 'bottom' : 'top',
7643
+ offsetY: side === 'top' ? -POPOVER_GAP : POPOVER_GAP,
7644
+ };
7645
+ }
7646
+ const vertical = align === 'center' ? 'center' : align === 'start' ? 'top' : 'bottom';
7647
+ return {
7648
+ originX: side === 'left' ? 'start' : 'end',
7649
+ originY: vertical,
7650
+ overlayX: side === 'left' ? 'end' : 'start',
7651
+ overlayY: vertical,
7652
+ offsetX: side === 'left' ? -POPOVER_GAP : POPOVER_GAP,
7653
+ };
7654
+ }
7655
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7656
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.0", type: PopoverDirective, isStandalone: true, selector: "[kfPopover]", inputs: { kfPopover: { classPropertyName: "kfPopover", publicName: "kfPopover", isSignal: true, isRequired: true, transformFunction: null }, kfPopoverPlacement: { classPropertyName: "kfPopoverPlacement", publicName: "kfPopoverPlacement", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverShowDelay: { classPropertyName: "kfPopoverShowDelay", publicName: "kfPopoverShowDelay", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverHideDelay: { classPropertyName: "kfPopoverHideDelay", publicName: "kfPopoverHideDelay", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverDisabled: { classPropertyName: "kfPopoverDisabled", publicName: "kfPopoverDisabled", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverInteractive: { classPropertyName: "kfPopoverInteractive", publicName: "kfPopoverInteractive", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverLabel: { classPropertyName: "kfPopoverLabel", publicName: "kfPopoverLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { kfPopoverOpenChange: "kfPopoverOpenChange" }, exportAs: ["kfPopover"], ngImport: i0 });
7657
+ }
7658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverDirective, decorators: [{
7659
+ type: Directive,
7660
+ args: [{
7661
+ selector: '[kfPopover]',
7662
+ exportAs: 'kfPopover',
7663
+ }]
7664
+ }], ctorParameters: () => [], propDecorators: { kfPopover: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopover", required: true }] }], kfPopoverPlacement: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverPlacement", required: false }] }], kfPopoverShowDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverShowDelay", required: false }] }], kfPopoverHideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverHideDelay", required: false }] }], kfPopoverDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverDisabled", required: false }] }], kfPopoverInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverInteractive", required: false }] }], kfPopoverLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverLabel", required: false }] }], kfPopoverOpenChange: [{ type: i0.Output, args: ["kfPopoverOpenChange"] }] } });
7665
+
6031
7666
  /**
6032
7667
  * Cabecera completa del portal (página 31) — franja de administrador
6033
7668
  * (acento ámbar, solo admins), barra de marca con logo, navegación y
@@ -6179,36 +7814,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
6179
7814
  args: ['window:resize']
6180
7815
  }] } });
6181
7816
 
6182
- /**
6183
- * Barra de progreso de un solo valor (0-100%) con color por severidad —
6184
- * para un porcentaje simple, a diferencia de `kf-bucket-progress`, que
6185
- * dibuja varias categorías proporcionales en la misma barra.
6186
- *
6187
- * <kf-progress [value]="80" tone="ok" label="80%" />
6188
- * <kf-progress [value]="ratio()" [tone]="severityTone()" />
6189
- *
6190
- * `value` se clampea a [0, 100] — un consumidor que calcule un ratio fuera
6191
- * de rango (ej. tiempo transcurrido sobre un SLA ya vencido) no rompe el
6192
- * ancho de la barra. `label` es texto libre opcional sobre la barra (ej.
6193
- * "80%"); sin él, no se renderiza ningún texto, solo la barra.
6194
- */
6195
- class Progress {
6196
- value = input.required(/* @ts-ignore */
6197
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6198
- tone = input('neutral', /* @ts-ignore */
6199
- ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
6200
- label = input(/* @ts-ignore */
6201
- ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
6202
- clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100), /* @ts-ignore */
6203
- ...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
6204
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, deps: [], target: i0.ɵɵFactoryTarget.Component });
6205
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Progress, isStandalone: true, selector: "kf-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] });
6206
- }
6207
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, decorators: [{
6208
- type: Component,
6209
- args: [{ selector: 'kf-progress', template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] }]
6210
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
6211
-
6212
7817
  /**
6213
7818
  * Punto que emite ondas concéntricas en bucle, para marcar un dato que
6214
7819
  * necesita atención inmediata sin depender de que el operador lo vea por
@@ -6857,47 +8462,16 @@ class SidebarShell {
6857
8462
  this.navItemClick.emit(item);
6858
8463
  }
6859
8464
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
6860
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarShell, isStandalone: true, selector: "kf-sidebar-shell", inputs: { navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", mobileOpen: "mobileOpenChange", navItemClick: "navItemClick" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1);box-shadow:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--mobile-open{transform:translate(0)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw;padding-inline:var(--kf-space-md)}.kf-sidebar-shell__brand{display:grid;grid-template-columns:1fr 2rem;align-items:center}.kf-sidebar-shell__brand-full{grid-column:1;justify-content:flex-start}.kf-sidebar-shell__mobile-close{grid-column:2;display:flex}.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__backdrop{display:block;position:fixed;inset:0;z-index:90;border:none;padding:0;cursor:pointer;background:#0a0e1480}.kf-sidebar-shell__topbar{display:flex;align-items:center;gap:var(--kf-space-md);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-white);border-bottom:1px solid var(--kf-color-border)}.kf-sidebar-shell__menu-toggle{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-ink);cursor:pointer;flex:none}.kf-sidebar-shell__menu-toggle:hover{background:var(--kf-color-surface)}.kf-sidebar-shell__topbar-brand ::ng-deep img{display:block;height:1.25rem}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: SidebarNavItem, selector: "kf-sidebar-nav-item", inputs: ["icon", "label", "href", "active", "collapsed"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
8465
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarShell, isStandalone: true, selector: "kf-sidebar-shell", inputs: { navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", mobileOpen: "mobileOpenChange", navItemClick: "navItemClick" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1),box-shadow .26s cubic-bezier(.4,0,.2,1);box-shadow:none}.kf-sidebar-shell__rail--mobile-open{transform:translate(0);box-shadow:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw;padding-inline:var(--kf-space-md)}.kf-sidebar-shell__brand{display:grid;grid-template-columns:1fr 2rem;align-items:center}.kf-sidebar-shell__brand-full{grid-column:1;justify-content:flex-start}.kf-sidebar-shell__mobile-close{grid-column:2;display:flex}.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__backdrop{display:block;position:fixed;inset:0;z-index:90;border:none;padding:0;cursor:pointer;background:#0a0e1480}.kf-sidebar-shell__topbar{display:flex;align-items:center;gap:var(--kf-space-md);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-white);border-bottom:1px solid var(--kf-color-border)}.kf-sidebar-shell__menu-toggle{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-ink);cursor:pointer;flex:none}.kf-sidebar-shell__menu-toggle:hover{background:var(--kf-color-surface)}.kf-sidebar-shell__topbar-brand ::ng-deep img{display:block;height:1.25rem}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: SidebarNavItem, selector: "kf-sidebar-nav-item", inputs: ["icon", "label", "href", "active", "collapsed"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
6861
8466
  }
6862
8467
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarShell, decorators: [{
6863
8468
  type: Component,
6864
- args: [{ selector: 'kf-sidebar-shell', imports: [Icon, SidebarNavItem, TooltipDirective], template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1);box-shadow:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--mobile-open{transform:translate(0)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw;padding-inline:var(--kf-space-md)}.kf-sidebar-shell__brand{display:grid;grid-template-columns:1fr 2rem;align-items:center}.kf-sidebar-shell__brand-full{grid-column:1;justify-content:flex-start}.kf-sidebar-shell__mobile-close{grid-column:2;display:flex}.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__backdrop{display:block;position:fixed;inset:0;z-index:90;border:none;padding:0;cursor:pointer;background:#0a0e1480}.kf-sidebar-shell__topbar{display:flex;align-items:center;gap:var(--kf-space-md);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-white);border-bottom:1px solid var(--kf-color-border)}.kf-sidebar-shell__menu-toggle{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-ink);cursor:pointer;flex:none}.kf-sidebar-shell__menu-toggle:hover{background:var(--kf-color-surface)}.kf-sidebar-shell__topbar-brand ::ng-deep img{display:block;height:1.25rem}}\n"] }]
8469
+ args: [{ selector: 'kf-sidebar-shell', imports: [Icon, SidebarNavItem, TooltipDirective], template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1),box-shadow .26s cubic-bezier(.4,0,.2,1);box-shadow:none}.kf-sidebar-shell__rail--mobile-open{transform:translate(0);box-shadow:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw;padding-inline:var(--kf-space-md)}.kf-sidebar-shell__brand{display:grid;grid-template-columns:1fr 2rem;align-items:center}.kf-sidebar-shell__brand-full{grid-column:1;justify-content:flex-start}.kf-sidebar-shell__mobile-close{grid-column:2;display:flex}.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__backdrop{display:block;position:fixed;inset:0;z-index:90;border:none;padding:0;cursor:pointer;background:#0a0e1480}.kf-sidebar-shell__topbar{display:flex;align-items:center;gap:var(--kf-space-md);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-white);border-bottom:1px solid var(--kf-color-border)}.kf-sidebar-shell__menu-toggle{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-ink);cursor:pointer;flex:none}.kf-sidebar-shell__menu-toggle:hover{background:var(--kf-color-surface)}.kf-sidebar-shell__topbar-brand ::ng-deep img{display:block;height:1.25rem}}\n"] }]
6865
8470
  }], propDecorators: { navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }, { type: i0.Output, args: ["mobileOpenChange"] }], navItemClick: [{ type: i0.Output, args: ["navItemClick"] }], onEscape: [{
6866
8471
  type: HostListener,
6867
8472
  args: ['document:keydown.escape']
6868
8473
  }] } });
6869
8474
 
6870
- /**
6871
- * Card de estadística horizontal: ícono circular grande a la izquierda,
6872
- * valor + label + nota apilados al centro, chevron opcional al final si es
6873
- * clickeable. Distinta de `kf-metric-card` (layout vertical, label arriba +
6874
- * ícono a la derecha) — este es el patrón real del dashboard de OTD ("Bonos
6875
- * otorgados", "Cortesías otorgadas"), no una variante de la misma card.
6876
- *
6877
- * <kf-stat-card icon="gift" tone="purple" value="4" label="Bonos otorgados" note="$15,000.00 en beneficios" (cardClick)="onNavigate('beneficios')" />
6878
- */
6879
- class StatCard {
6880
- icon = input.required(/* @ts-ignore */
6881
- ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
6882
- tone = input('primary', /* @ts-ignore */
6883
- ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
6884
- value = input.required(/* @ts-ignore */
6885
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6886
- label = input.required(/* @ts-ignore */
6887
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
6888
- note = input(/* @ts-ignore */
6889
- ...(ngDevMode ? [undefined, { debugName: "note" }] : /* istanbul ignore next */ []));
6890
- /** Si se escucha `cardClick`, la card se renderiza como botón clickeable con chevron. */
6891
- clickable = input(false, { ...(ngDevMode ? { debugName: "clickable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6892
- cardClick = output();
6893
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
6894
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: StatCard, isStandalone: true, selector: "kf-stat-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "@if (clickable()) {\n <button type=\"button\" class=\"kf-stat-card kf-stat-card--clickable\" (click)=\"cardClick.emit()\">\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n </span>\n</ng-template>\n", styles: [":host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
6895
- }
6896
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, decorators: [{
6897
- type: Component,
6898
- args: [{ selector: 'kf-stat-card', imports: [Icon, NgTemplateOutlet], template: "@if (clickable()) {\n <button type=\"button\" class=\"kf-stat-card kf-stat-card--clickable\" (click)=\"cardClick.emit()\">\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n </span>\n</ng-template>\n", styles: [":host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}\n"] }]
6899
- }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
6900
-
6901
8475
  /**
6902
8476
  * Indicador numérico compacto: label + valor grande con ícono, para grillas
6903
8477
  * densas de KPIs. Con `cta` en vez de `value` se convierte en un bloque de
@@ -7451,11 +9025,28 @@ const TOAST_ICON = {
7451
9025
  loading: 'bell',
7452
9026
  };
7453
9027
  const DEFAULT_DURATION_MS = 2800;
9028
+ /**
9029
+ * Auto-dismiss time per tone when the caller doesn't pass `duration`. Errors and
9030
+ * warnings stay longer than confirmations: they carry something to read and
9031
+ * act on, while a success only has to be noticed. `success`/`info` keep the
9032
+ * historical 2800ms. `loading` never auto-dismisses.
9033
+ */
9034
+ const TOAST_DEFAULT_DURATION_MS = {
9035
+ success: DEFAULT_DURATION_MS,
9036
+ info: DEFAULT_DURATION_MS,
9037
+ warning: 5000,
9038
+ error: 8000,
9039
+ };
7454
9040
  const EXIT_ANIMATION_MS = 200;
7455
9041
  /**
7456
9042
  * Shows one toast at a time — a new `show()` call replaces whatever is
7457
9043
  * currently up (this mirrors a global status line, not a notification stack).
7458
9044
  *
9045
+ * Duration by tone (when `duration` isn't passed): `success`/`info` 2800ms,
9046
+ * `warning` 5000ms, `error` 8000ms (`TOAST_DEFAULT_DURATION_MS`); an explicit
9047
+ * `duration` always wins. The countdown pauses while the pointer or focus is
9048
+ * on the toast. Error toasts also get a larger, always-opaque close button.
9049
+ *
7459
9050
  * Angular has no global function that can mount UI on its own, so the
7460
9051
  * imperative `showToast(...)` of the reference design system becomes a
7461
9052
  * root-provided service holding signal state: `ToastService.show(...)` sets
@@ -7499,7 +9090,8 @@ class ToastService {
7499
9090
  show(message, options = {}) {
7500
9091
  const id = this.nextId++;
7501
9092
  const tone = options.tone ?? 'info';
7502
- const duration = options.duration ?? DEFAULT_DURATION_MS;
9093
+ const duration = options.duration ??
9094
+ (tone === 'loading' ? DEFAULT_DURATION_MS : TOAST_DEFAULT_DURATION_MS[tone]);
7503
9095
  clearTimeout(this.dismissTimer);
7504
9096
  clearTimeout(this.removeTimer);
7505
9097
  this.dismissTimer = undefined;
@@ -7623,11 +9215,11 @@ class ToastHost {
7623
9215
  this.dismiss(t.id);
7624
9216
  }
7625
9217
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ToastHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
7626
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ToastHost, isStandalone: true, selector: "kf-toast-host", ngImport: i0, template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button type=\"button\" class=\"kf-toast__dismiss\" aria-label=\"Cerrar\" (click)=\"dismiss(t.id)\">\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n <span class=\"kf-toast__bar\" [style.--kf-toast-dur]=\"t.duration + 'ms'\"></span>\n </div>\n}\n", styles: ["@keyframes kf-toast-in{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes kf-toast-bar{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes kf-toast-indeterminate{0%{left:-40%;width:40%}50%{left:30%;width:55%}to{left:100%;width:40%}}.kf-toast{--kf-toast-accent: var(--kf-color-info);position:fixed;inset-block-end:var(--kf-space-lg);inset-inline-end:var(--kf-space-lg);z-index:1100;display:flex;align-items:center;gap:var(--kf-space-sm);max-inline-size:360px;padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-md);border-left:4px solid var(--kf-toast-accent);background:var(--kf-color-ink);color:var(--kf-color-white);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;line-height:1.4;overflow:hidden;animation:kf-toast-in .25s ease forwards;transition:transform .2s ease,opacity .2s ease}.kf-toast--leaving{transform:translateY(20px);opacity:0}.kf-toast--success{--kf-toast-accent: var(--kf-color-success)}.kf-toast--error{--kf-toast-accent: var(--kf-color-error)}.kf-toast--warning{--kf-toast-accent: var(--kf-color-warning)}.kf-toast--info,.kf-toast--loading{--kf-toast-accent: var(--kf-color-info)}.kf-toast__icon{flex:none;color:var(--kf-toast-accent)}.kf-toast__message{flex:1}.kf-toast__action{flex:none;padding:2px var(--kf-space-xs);border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-toast-accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.kf-toast__action:hover{opacity:.85}.kf-toast__dismiss{flex:none;display:grid;place-items:center;inline-size:24px;block-size:24px;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer;opacity:.6}.kf-toast__dismiss:hover{opacity:1}.kf-toast__bar{position:absolute;inset-block-end:0;left:0;block-size:3px;inline-size:100%;background:var(--kf-toast-accent);opacity:.7;transform-origin:left;animation:kf-toast-bar var(--kf-toast-dur, 2.8s) linear forwards}.kf-toast--loading .kf-toast__bar{left:-40%;inline-size:40%;opacity:.6;animation:kf-toast-indeterminate 1.4s ease-in-out infinite}.kf-toast:hover .kf-toast__bar,.kf-toast:focus-within .kf-toast__bar{animation-play-state:paused}@media(prefers-reduced-motion:reduce){.kf-toast,.kf-toast__bar,.kf-toast--loading .kf-toast__bar{animation:none}.kf-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9218
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ToastHost, isStandalone: true, selector: "kf-toast-host", ngImport: i0, template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button\n type=\"button\"\n class=\"kf-toast__dismiss\"\n [class.kf-toast__dismiss--prominent]=\"t.tone === 'error'\"\n [attr.aria-label]=\"t.tone === 'error' ? 'Cerrar mensaje de error' : 'Cerrar'\"\n (click)=\"dismiss(t.id)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n <span class=\"kf-toast__bar\" [style.--kf-toast-dur]=\"t.duration + 'ms'\"></span>\n </div>\n}\n", styles: ["@keyframes kf-toast-in{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes kf-toast-bar{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes kf-toast-indeterminate{0%{left:-40%;width:40%}50%{left:30%;width:55%}to{left:100%;width:40%}}.kf-toast{--kf-toast-accent: var(--kf-color-info);position:fixed;inset-block-end:var(--kf-space-lg);inset-inline-end:var(--kf-space-lg);z-index:1100;display:flex;align-items:center;gap:var(--kf-space-sm);max-inline-size:360px;padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-md);border-left:4px solid var(--kf-toast-accent);background:var(--kf-color-ink);color:var(--kf-color-white);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;line-height:1.4;overflow:hidden;animation:kf-toast-in .25s ease forwards;transition:transform .2s ease,opacity .2s ease}.kf-toast--leaving{transform:translateY(20px);opacity:0}.kf-toast--success{--kf-toast-accent: var(--kf-color-success)}.kf-toast--error{--kf-toast-accent: var(--kf-color-error)}.kf-toast--warning{--kf-toast-accent: var(--kf-color-warning)}.kf-toast--info,.kf-toast--loading{--kf-toast-accent: var(--kf-color-info)}.kf-toast__icon{flex:none;color:var(--kf-toast-accent)}.kf-toast__message{flex:1}.kf-toast__action{flex:none;padding:2px var(--kf-space-xs);border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-toast-accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.kf-toast__action:hover{opacity:.85}.kf-toast__dismiss{flex:none;display:grid;place-items:center;inline-size:24px;block-size:24px;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer;opacity:.6}.kf-toast__dismiss:hover{opacity:1}.kf-toast__dismiss:focus-visible,.kf-toast__action:focus-visible{outline:2px solid var(--kf-color-white);outline-offset:1px;opacity:1}.kf-toast__dismiss--prominent{inline-size:32px;block-size:32px;opacity:1}.kf-toast__bar{position:absolute;inset-block-end:0;left:0;block-size:3px;inline-size:100%;background:var(--kf-toast-accent);opacity:.7;transform-origin:left;animation:kf-toast-bar var(--kf-toast-dur, 2.8s) linear forwards}.kf-toast--loading .kf-toast__bar{left:-40%;inline-size:40%;opacity:.6;animation:kf-toast-indeterminate 1.4s ease-in-out infinite}.kf-toast:hover .kf-toast__bar,.kf-toast:focus-within .kf-toast__bar{animation-play-state:paused}@media(prefers-reduced-motion:reduce){.kf-toast,.kf-toast__bar,.kf-toast--loading .kf-toast__bar{animation:none}.kf-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7627
9219
  }
7628
9220
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ToastHost, decorators: [{
7629
9221
  type: Component,
7630
- args: [{ selector: 'kf-toast-host', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Icon], template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button type=\"button\" class=\"kf-toast__dismiss\" aria-label=\"Cerrar\" (click)=\"dismiss(t.id)\">\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n <span class=\"kf-toast__bar\" [style.--kf-toast-dur]=\"t.duration + 'ms'\"></span>\n </div>\n}\n", styles: ["@keyframes kf-toast-in{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes kf-toast-bar{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes kf-toast-indeterminate{0%{left:-40%;width:40%}50%{left:30%;width:55%}to{left:100%;width:40%}}.kf-toast{--kf-toast-accent: var(--kf-color-info);position:fixed;inset-block-end:var(--kf-space-lg);inset-inline-end:var(--kf-space-lg);z-index:1100;display:flex;align-items:center;gap:var(--kf-space-sm);max-inline-size:360px;padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-md);border-left:4px solid var(--kf-toast-accent);background:var(--kf-color-ink);color:var(--kf-color-white);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;line-height:1.4;overflow:hidden;animation:kf-toast-in .25s ease forwards;transition:transform .2s ease,opacity .2s ease}.kf-toast--leaving{transform:translateY(20px);opacity:0}.kf-toast--success{--kf-toast-accent: var(--kf-color-success)}.kf-toast--error{--kf-toast-accent: var(--kf-color-error)}.kf-toast--warning{--kf-toast-accent: var(--kf-color-warning)}.kf-toast--info,.kf-toast--loading{--kf-toast-accent: var(--kf-color-info)}.kf-toast__icon{flex:none;color:var(--kf-toast-accent)}.kf-toast__message{flex:1}.kf-toast__action{flex:none;padding:2px var(--kf-space-xs);border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-toast-accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.kf-toast__action:hover{opacity:.85}.kf-toast__dismiss{flex:none;display:grid;place-items:center;inline-size:24px;block-size:24px;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer;opacity:.6}.kf-toast__dismiss:hover{opacity:1}.kf-toast__bar{position:absolute;inset-block-end:0;left:0;block-size:3px;inline-size:100%;background:var(--kf-toast-accent);opacity:.7;transform-origin:left;animation:kf-toast-bar var(--kf-toast-dur, 2.8s) linear forwards}.kf-toast--loading .kf-toast__bar{left:-40%;inline-size:40%;opacity:.6;animation:kf-toast-indeterminate 1.4s ease-in-out infinite}.kf-toast:hover .kf-toast__bar,.kf-toast:focus-within .kf-toast__bar{animation-play-state:paused}@media(prefers-reduced-motion:reduce){.kf-toast,.kf-toast__bar,.kf-toast--loading .kf-toast__bar{animation:none}.kf-toast{transition:none}}\n"] }]
9222
+ args: [{ selector: 'kf-toast-host', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Icon], template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button\n type=\"button\"\n class=\"kf-toast__dismiss\"\n [class.kf-toast__dismiss--prominent]=\"t.tone === 'error'\"\n [attr.aria-label]=\"t.tone === 'error' ? 'Cerrar mensaje de error' : 'Cerrar'\"\n (click)=\"dismiss(t.id)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n <span class=\"kf-toast__bar\" [style.--kf-toast-dur]=\"t.duration + 'ms'\"></span>\n </div>\n}\n", styles: ["@keyframes kf-toast-in{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes kf-toast-bar{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes kf-toast-indeterminate{0%{left:-40%;width:40%}50%{left:30%;width:55%}to{left:100%;width:40%}}.kf-toast{--kf-toast-accent: var(--kf-color-info);position:fixed;inset-block-end:var(--kf-space-lg);inset-inline-end:var(--kf-space-lg);z-index:1100;display:flex;align-items:center;gap:var(--kf-space-sm);max-inline-size:360px;padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-md);border-left:4px solid var(--kf-toast-accent);background:var(--kf-color-ink);color:var(--kf-color-white);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;line-height:1.4;overflow:hidden;animation:kf-toast-in .25s ease forwards;transition:transform .2s ease,opacity .2s ease}.kf-toast--leaving{transform:translateY(20px);opacity:0}.kf-toast--success{--kf-toast-accent: var(--kf-color-success)}.kf-toast--error{--kf-toast-accent: var(--kf-color-error)}.kf-toast--warning{--kf-toast-accent: var(--kf-color-warning)}.kf-toast--info,.kf-toast--loading{--kf-toast-accent: var(--kf-color-info)}.kf-toast__icon{flex:none;color:var(--kf-toast-accent)}.kf-toast__message{flex:1}.kf-toast__action{flex:none;padding:2px var(--kf-space-xs);border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-toast-accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.kf-toast__action:hover{opacity:.85}.kf-toast__dismiss{flex:none;display:grid;place-items:center;inline-size:24px;block-size:24px;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer;opacity:.6}.kf-toast__dismiss:hover{opacity:1}.kf-toast__dismiss:focus-visible,.kf-toast__action:focus-visible{outline:2px solid var(--kf-color-white);outline-offset:1px;opacity:1}.kf-toast__dismiss--prominent{inline-size:32px;block-size:32px;opacity:1}.kf-toast__bar{position:absolute;inset-block-end:0;left:0;block-size:3px;inline-size:100%;background:var(--kf-toast-accent);opacity:.7;transform-origin:left;animation:kf-toast-bar var(--kf-toast-dur, 2.8s) linear forwards}.kf-toast--loading .kf-toast__bar{left:-40%;inline-size:40%;opacity:.6;animation:kf-toast-indeterminate 1.4s ease-in-out infinite}.kf-toast:hover .kf-toast__bar,.kf-toast:focus-within .kf-toast__bar{animation-play-state:paused}@media(prefers-reduced-motion:reduce){.kf-toast,.kf-toast__bar,.kf-toast--loading .kf-toast__bar{animation:none}.kf-toast{transition:none}}\n"] }]
7631
9223
  }] });
7632
9224
 
7633
9225
  /*
@@ -7651,5 +9243,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
7651
9243
  * Generated bundle index. Do not edit.
7652
9244
  */
7653
9245
 
7654
- export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentViewer, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, LIST_TOOLBAR_HYSTERESIS, Link, ListToolbar, MetricCard, Modal, MultiSelect, NoteBlock, OptionList, PageHeader, Pagination, Panel, PortalHeader, Progress, PulseDot, RowActions, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, Spinner, StatCard, StatTile, StatusPill, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal, resolveListToolbarMode };
9246
+ export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentStateLoading, ContentViewer, DATA_TABLE_MAX_SKELETON_ROWS, DEFAULT_MILESTONE_TIMELINE_LABELS, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, FileInput, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, LIST_TOOLBAR_HYSTERESIS, Link, ListToolbar, MetricCard, MilestoneTimeline, Modal, MultiSelect, NoteBlock, OptionList, PROGRESS_STATUS, PROGRESS_STATUS_LABEL, PageHeader, Pagination, Panel, PopoverDirective, PortalHeader, Progress, PulseDot, RowActions, SKELETON_PRESET_MAX_COUNT, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, SkeletonPreset, Spinner, StatCard, StatTile, StatusMark, StatusNode, StatusPill, TOAST_DEFAULT_DURATION_MS, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal, formatFileSize, resolveListToolbarMode };
7655
9247
  //# sourceMappingURL=kaufmann-lab-ui-core.mjs.map