@kaufmann-lab/ui 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, booleanAttribute, inject, computed, effect, Component, model, output, ElementRef, Injector, signal, afterNextRender, ChangeDetectionStrategy, numberAttribute, contentChildren, DestroyRef, viewChild, forwardRef, TemplateRef, contentChild, ViewContainerRef, HostListener, afterRenderEffect, 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, HostListener, afterRenderEffect, untracked, isDevMode, viewChildren, Input as Input$1, Directive, 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';
@@ -363,6 +363,16 @@ const inbox = {
363
363
  `,
364
364
  };
365
365
 
366
+ /** Símbolo de trazo "info" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
367
+ const info = {
368
+ viewBox: '0 0 24 24',
369
+ markup: `
370
+ <circle cx="12" cy="12" r="10" />
371
+ <path d="M12 16v-4" />
372
+ <path d="M12 8h.01" />
373
+ `,
374
+ };
375
+
366
376
  /** Símbolo de trazo "layers" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
367
377
  const layers = {
368
378
  viewBox: '0 0 24 24',
@@ -373,6 +383,17 @@ const layers = {
373
383
  `,
374
384
  };
375
385
 
386
+ /** Símbolo de trazo "layout-grid" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
387
+ const layoutGrid = {
388
+ viewBox: '0 0 24 24',
389
+ markup: `
390
+ <rect width="7" height="7" x="3" y="3" rx="1" />
391
+ <rect width="7" height="7" x="14" y="3" rx="1" />
392
+ <rect width="7" height="7" x="14" y="14" rx="1" />
393
+ <rect width="7" height="7" x="3" y="14" rx="1" />
394
+ `,
395
+ };
396
+
376
397
  /** Símbolo de trazo "list" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
377
398
  const list = {
378
399
  viewBox: '0 0 24 24',
@@ -566,6 +587,17 @@ const plus = {
566
587
  `,
567
588
  };
568
589
 
590
+ /** Símbolo de trazo "refresh" (grilla 24×24; `refresh-cw` de Lucide) — ver `icons/index.ts` para el registro completo. */
591
+ const refresh = {
592
+ viewBox: '0 0 24 24',
593
+ markup: `
594
+ <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
595
+ <path d="M21 3v5h-5" />
596
+ <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
597
+ <path d="M8 16H3v5" />
598
+ `,
599
+ };
600
+
569
601
  /** Símbolo de trazo "search" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
570
602
  const search = {
571
603
  viewBox: '0 0 24 24',
@@ -757,7 +789,9 @@ const ICON_DEFINITIONS = {
757
789
  history: history,
758
790
  home: home,
759
791
  inbox: inbox,
792
+ info: info,
760
793
  layers: layers,
794
+ 'layout-grid': layoutGrid,
761
795
  list: list,
762
796
  'log-out': logOut,
763
797
  mail: mail,
@@ -778,6 +812,7 @@ const ICON_DEFINITIONS = {
778
812
  pin: pin,
779
813
  play: play,
780
814
  plus: plus,
815
+ refresh: refresh,
781
816
  search: search,
782
817
  settings: settings,
783
818
  'shield-check': shieldCheck,
@@ -796,7 +831,7 @@ const ICON_DEFINITIONS = {
796
831
  };
797
832
 
798
833
  /**
799
- * Sprite SVG con los 71 íconos de trazo del design system (página 5:
834
+ * Sprite SVG con los 74 íconos de trazo del design system (página 5:
800
835
  * "Set de 59 iconos de trazo (1.9px, grilla 24×24)", más las extensiones
801
836
  * propias documentadas en `icon-name.ts` — `eye-off`, `log-out`, los 5 de
802
837
  * `kf-map` y los 4 del selector de canal/fijar de `apps/operations`).
@@ -1081,7 +1116,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
1081
1116
  * Envuelve un `<button>` nativo: mantiene teclado, foco y submit de formulario.
1082
1117
  * Como es un elemento personalizado, ningún atributo ARIA puesto en el host
1083
1118
  * llega al `<button>` interno — por eso `disabled`, `type`, `ariaLabel`,
1084
- * `ariaExpanded` y `ariaPressed` se reenvían a mano.
1119
+ * `ariaExpanded`, `ariaPressed` y `ariaHaspopup` se reenvían a mano.
1085
1120
  *
1086
1121
  * Seis variantes: `primary`, `secondary`, `ghost`, `outline`, `danger`,
1087
1122
  * `surface`. Tres tamaños (`sm`/`md`/`lg`). `icon` agrega un glifo antes del
@@ -1100,6 +1135,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
1100
1135
  *
1101
1136
  * `(click)` no se reenvía: burbujea del botón interno al host solo.
1102
1137
  *
1138
+ * Objetivo táctil: el alto y el ancho mínimos del `<button>` interno salen
1139
+ * de `--kf-button-min-height` y `--kf-button-min-width` (default `auto`,
1140
+ * sin cambio). Un contenedor los sube (ej. a 44px en pantallas táctiles,
1141
+ * sobre todo para un botón solo-ícono) declarando las variables en su
1142
+ * propio CSS — heredan a través del límite de encapsulación.
1143
+ *
1144
+ * `focus()` enfoca el `<button>` interno: el consumidor no necesita
1145
+ * buscarlo con `querySelector` en el DOM del componente.
1146
+ *
1103
1147
  * `loading` reemplaza el contenido proyectado por `kf-spinner` y fuerza
1104
1148
  * `disabled`.
1105
1149
  *
@@ -1147,6 +1191,16 @@ class Button {
1147
1191
  * acordarse de pedirlo aparte. */
1148
1192
  ariaPressed = input(/* @ts-ignore */
1149
1193
  ...(ngDevMode ? [undefined, { debugName: "ariaPressed" }] : /* istanbul ignore next */ []));
1194
+ /** Tipo de elemento emergente que abre el botón (ej. `'dialog'` para un
1195
+ * panel de filtros, `'listbox'`) — mismo motivo de reenvío manual. */
1196
+ ariaHaspopup = input(/* @ts-ignore */
1197
+ ...(ngDevMode ? [undefined, { debugName: "ariaHaspopup" }] : /* istanbul ignore next */ []));
1198
+ nativeButton = viewChild.required('nativeButton', /* @ts-ignore */
1199
+ ...(ngDevMode ? [{ debugName: "nativeButton" }] : /* istanbul ignore next */ []));
1200
+ /** Enfoca el `<button>` interno (mismas opciones que `HTMLElement.focus`). */
1201
+ focus(options) {
1202
+ this.nativeButton().nativeElement.focus(options);
1203
+ }
1150
1204
  /** `ghost`/`outline`/`surface` no tienen fondo oscuro sólido — el anillo
1151
1205
  * del spinner por defecto ya se lee bien ahí sin variante propia
1152
1206
  * (`surface` es fondo claro con texto oscuro, mismo motivo que `outline`). */
@@ -1156,14 +1210,14 @@ class Button {
1156
1210
  }, /* @ts-ignore */
1157
1211
  ...(ngDevMode ? [{ debugName: "hasSolidFill" }] : /* istanbul ignore next */ []));
1158
1212
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Button, deps: [], target: i0.ɵɵFactoryTarget.Component });
1159
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Button, isStandalone: true, selector: "kf-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, block: { classPropertyName: "block", publicName: "block", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaExpanded: { classPropertyName: "ariaExpanded", publicName: "ariaExpanded", isSignal: true, isRequired: false, transformFunction: null }, ariaPressed: { classPropertyName: "ariaPressed", publicName: "ariaPressed", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.block": "block() ? '' : null" } }, ngImport: i0, template: "<button\n [type]=\"type()\"\n [disabled]=\"disabled() || loading()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-pressed]=\"ariaPressed() === undefined ? null : ariaPressed()\"\n [class.kf-button--secondary]=\"variant() === 'secondary'\"\n [class.kf-button--ghost]=\"variant() === 'ghost'\"\n [class.kf-button--outline]=\"variant() === 'outline'\"\n [class.kf-button--danger]=\"variant() === 'danger'\"\n [class.kf-button--surface]=\"variant() === 'surface'\"\n [class.kf-button--circle]=\"shape() === 'circle'\"\n [class.kf-button--sm]=\"size() === 'sm'\"\n [class.kf-button--lg]=\"size() === 'lg'\"\n>\n @if (loading()) {\n <kf-spinner size=\"sm\" [onDark]=\"hasSolidFill()\" />\n } @else {\n @if (icon(); as iconName) {\n <kf-icon\n class=\"kf-button__icon\"\n [name]=\"iconName\"\n [size]=\"size() === 'lg' ? 'md' : 'sm'\"\n [filled]=\"ariaPressed() === true\"\n />\n }\n <ng-content />\n }\n</button>\n", styles: [":host{display:inline-block}:host([block]){display:block}button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);width:100%;font:inherit;font-weight:600;padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-primary);color:var(--kf-color-white);cursor:pointer;transition:background .14s,border-color .14s,color .14s}button:hover:not(:disabled){background:var(--kf-color-primary-hover);border-color:var(--kf-color-primary-hover)}button:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}button:disabled{opacity:.5;cursor:not-allowed}button.kf-button--secondary{border-color:var(--kf-color-secondary);background:var(--kf-color-secondary)}button.kf-button--secondary:hover:not(:disabled){background:var(--kf-color-secondary-hover);border-color:var(--kf-color-secondary-hover)}button.kf-button--ghost{background:transparent;border-color:transparent;color:var(--kf-color-primary)}button.kf-button--ghost:hover:not(:disabled){background:var(--kf-color-primary-soft)}button.kf-button--ghost[aria-pressed=true]{color:var(--kf-color-secondary)}.kf-button__icon{transition:transform .14s ease}button[aria-pressed=true] .kf-button__icon{transform:scale(1.12)}@media(prefers-reduced-motion:reduce){.kf-button__icon{transition:none}}button.kf-button--outline{background:transparent;border-color:var(--kf-color-border-strong);color:var(--kf-color-text)}button.kf-button--outline:hover:not(:disabled){background:var(--kf-color-surface);border-color:var(--kf-color-text-muted)}button.kf-button--danger{border-color:var(--kf-color-error);background:var(--kf-color-error)}button.kf-button--danger:hover:not(:disabled){background:var(--kf-color-error-hover);border-color:var(--kf-color-error-hover)}button.kf-button--surface{border-color:var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);box-shadow:var(--kf-shadow-md)}button.kf-button--surface:hover:not(:disabled){background:var(--kf-color-white);border-color:var(--kf-color-border-strong)}button.kf-button--circle{aspect-ratio:1;padding:var(--kf-space-sm);border-radius:var(--kf-radius-pill)}button.kf-button--circle.kf-button--sm{padding:6px}button.kf-button--circle.kf-button--lg{padding:var(--kf-space-md)}button.kf-button--sm{padding:6px var(--kf-space-sm);font-size:.85rem}button.kf-button--lg{padding:var(--kf-space-sm) var(--kf-space-lg);font-size:1.05rem}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
1213
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Button, isStandalone: true, selector: "kf-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, block: { classPropertyName: "block", publicName: "block", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaExpanded: { classPropertyName: "ariaExpanded", publicName: "ariaExpanded", isSignal: true, isRequired: false, transformFunction: null }, ariaPressed: { classPropertyName: "ariaPressed", publicName: "ariaPressed", isSignal: true, isRequired: false, transformFunction: null }, ariaHaspopup: { classPropertyName: "ariaHaspopup", publicName: "ariaHaspopup", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.block": "block() ? '' : null" } }, viewQueries: [{ propertyName: "nativeButton", first: true, predicate: ["nativeButton"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #nativeButton\n [type]=\"type()\"\n [disabled]=\"disabled() || loading()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-pressed]=\"ariaPressed() === undefined ? null : ariaPressed()\"\n [attr.aria-haspopup]=\"ariaHaspopup() ?? null\"\n [class.kf-button--secondary]=\"variant() === 'secondary'\"\n [class.kf-button--ghost]=\"variant() === 'ghost'\"\n [class.kf-button--outline]=\"variant() === 'outline'\"\n [class.kf-button--danger]=\"variant() === 'danger'\"\n [class.kf-button--surface]=\"variant() === 'surface'\"\n [class.kf-button--circle]=\"shape() === 'circle'\"\n [class.kf-button--sm]=\"size() === 'sm'\"\n [class.kf-button--lg]=\"size() === 'lg'\"\n>\n @if (loading()) {\n <kf-spinner size=\"sm\" [onDark]=\"hasSolidFill()\" />\n } @else {\n @if (icon(); as iconName) {\n <kf-icon\n class=\"kf-button__icon\"\n [name]=\"iconName\"\n [size]=\"size() === 'lg' ? 'md' : 'sm'\"\n [filled]=\"ariaPressed() === true\"\n />\n }\n <ng-content />\n }\n</button>\n", styles: [":host{display:inline-block}:host([block]){display:block}button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);width:100%;min-height:var(--kf-button-min-height, auto);min-width:var(--kf-button-min-width, auto);font:inherit;font-weight:600;padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-primary);color:var(--kf-color-white);cursor:pointer;transition:background .14s,border-color .14s,color .14s}button:hover:not(:disabled){background:var(--kf-color-primary-hover);border-color:var(--kf-color-primary-hover)}button:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}button:disabled{opacity:.5;cursor:not-allowed}button.kf-button--secondary{border-color:var(--kf-color-secondary);background:var(--kf-color-secondary)}button.kf-button--secondary:hover:not(:disabled){background:var(--kf-color-secondary-hover);border-color:var(--kf-color-secondary-hover)}button.kf-button--ghost{background:transparent;border-color:transparent;color:var(--kf-color-primary)}button.kf-button--ghost:hover:not(:disabled){background:var(--kf-color-primary-soft)}button.kf-button--ghost[aria-pressed=true]{color:var(--kf-color-secondary)}.kf-button__icon{transition:transform .14s ease}button[aria-pressed=true] .kf-button__icon{transform:scale(1.12)}@media(prefers-reduced-motion:reduce){.kf-button__icon{transition:none}}button.kf-button--outline{background:transparent;border-color:var(--kf-color-border-strong);color:var(--kf-color-text)}button.kf-button--outline:hover:not(:disabled){background:var(--kf-color-surface);border-color:var(--kf-color-text-muted)}button.kf-button--danger{border-color:var(--kf-color-error);background:var(--kf-color-error)}button.kf-button--danger:hover:not(:disabled){background:var(--kf-color-error-hover);border-color:var(--kf-color-error-hover)}button.kf-button--surface{border-color:var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);box-shadow:var(--kf-shadow-md)}button.kf-button--surface:hover:not(:disabled){background:var(--kf-color-white);border-color:var(--kf-color-border-strong)}button.kf-button--circle{aspect-ratio:1;padding:var(--kf-space-sm);border-radius:var(--kf-radius-pill)}button.kf-button--circle.kf-button--sm{padding:6px}button.kf-button--circle.kf-button--lg{padding:var(--kf-space-md)}button.kf-button--sm{padding:6px var(--kf-space-sm);font-size:.85rem}button.kf-button--lg{padding:var(--kf-space-sm) var(--kf-space-lg);font-size:1.05rem}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
1160
1214
  }
1161
1215
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Button, decorators: [{
1162
1216
  type: Component,
1163
1217
  args: [{ selector: 'kf-button', imports: [Icon, Spinner], host: {
1164
1218
  '[attr.block]': "block() ? '' : null",
1165
- }, template: "<button\n [type]=\"type()\"\n [disabled]=\"disabled() || loading()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-pressed]=\"ariaPressed() === undefined ? null : ariaPressed()\"\n [class.kf-button--secondary]=\"variant() === 'secondary'\"\n [class.kf-button--ghost]=\"variant() === 'ghost'\"\n [class.kf-button--outline]=\"variant() === 'outline'\"\n [class.kf-button--danger]=\"variant() === 'danger'\"\n [class.kf-button--surface]=\"variant() === 'surface'\"\n [class.kf-button--circle]=\"shape() === 'circle'\"\n [class.kf-button--sm]=\"size() === 'sm'\"\n [class.kf-button--lg]=\"size() === 'lg'\"\n>\n @if (loading()) {\n <kf-spinner size=\"sm\" [onDark]=\"hasSolidFill()\" />\n } @else {\n @if (icon(); as iconName) {\n <kf-icon\n class=\"kf-button__icon\"\n [name]=\"iconName\"\n [size]=\"size() === 'lg' ? 'md' : 'sm'\"\n [filled]=\"ariaPressed() === true\"\n />\n }\n <ng-content />\n }\n</button>\n", styles: [":host{display:inline-block}:host([block]){display:block}button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);width:100%;font:inherit;font-weight:600;padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-primary);color:var(--kf-color-white);cursor:pointer;transition:background .14s,border-color .14s,color .14s}button:hover:not(:disabled){background:var(--kf-color-primary-hover);border-color:var(--kf-color-primary-hover)}button:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}button:disabled{opacity:.5;cursor:not-allowed}button.kf-button--secondary{border-color:var(--kf-color-secondary);background:var(--kf-color-secondary)}button.kf-button--secondary:hover:not(:disabled){background:var(--kf-color-secondary-hover);border-color:var(--kf-color-secondary-hover)}button.kf-button--ghost{background:transparent;border-color:transparent;color:var(--kf-color-primary)}button.kf-button--ghost:hover:not(:disabled){background:var(--kf-color-primary-soft)}button.kf-button--ghost[aria-pressed=true]{color:var(--kf-color-secondary)}.kf-button__icon{transition:transform .14s ease}button[aria-pressed=true] .kf-button__icon{transform:scale(1.12)}@media(prefers-reduced-motion:reduce){.kf-button__icon{transition:none}}button.kf-button--outline{background:transparent;border-color:var(--kf-color-border-strong);color:var(--kf-color-text)}button.kf-button--outline:hover:not(:disabled){background:var(--kf-color-surface);border-color:var(--kf-color-text-muted)}button.kf-button--danger{border-color:var(--kf-color-error);background:var(--kf-color-error)}button.kf-button--danger:hover:not(:disabled){background:var(--kf-color-error-hover);border-color:var(--kf-color-error-hover)}button.kf-button--surface{border-color:var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);box-shadow:var(--kf-shadow-md)}button.kf-button--surface:hover:not(:disabled){background:var(--kf-color-white);border-color:var(--kf-color-border-strong)}button.kf-button--circle{aspect-ratio:1;padding:var(--kf-space-sm);border-radius:var(--kf-radius-pill)}button.kf-button--circle.kf-button--sm{padding:6px}button.kf-button--circle.kf-button--lg{padding:var(--kf-space-md)}button.kf-button--sm{padding:6px var(--kf-space-sm);font-size:.85rem}button.kf-button--lg{padding:var(--kf-space-sm) var(--kf-space-lg);font-size:1.05rem}\n"] }]
1166
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], block: [{ type: i0.Input, args: [{ isSignal: true, alias: "block", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaExpanded", required: false }] }], ariaPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaPressed", required: false }] }] } });
1219
+ }, template: "<button\n #nativeButton\n [type]=\"type()\"\n [disabled]=\"disabled() || loading()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-pressed]=\"ariaPressed() === undefined ? null : ariaPressed()\"\n [attr.aria-haspopup]=\"ariaHaspopup() ?? null\"\n [class.kf-button--secondary]=\"variant() === 'secondary'\"\n [class.kf-button--ghost]=\"variant() === 'ghost'\"\n [class.kf-button--outline]=\"variant() === 'outline'\"\n [class.kf-button--danger]=\"variant() === 'danger'\"\n [class.kf-button--surface]=\"variant() === 'surface'\"\n [class.kf-button--circle]=\"shape() === 'circle'\"\n [class.kf-button--sm]=\"size() === 'sm'\"\n [class.kf-button--lg]=\"size() === 'lg'\"\n>\n @if (loading()) {\n <kf-spinner size=\"sm\" [onDark]=\"hasSolidFill()\" />\n } @else {\n @if (icon(); as iconName) {\n <kf-icon\n class=\"kf-button__icon\"\n [name]=\"iconName\"\n [size]=\"size() === 'lg' ? 'md' : 'sm'\"\n [filled]=\"ariaPressed() === true\"\n />\n }\n <ng-content />\n }\n</button>\n", styles: [":host{display:inline-block}:host([block]){display:block}button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);width:100%;min-height:var(--kf-button-min-height, auto);min-width:var(--kf-button-min-width, auto);font:inherit;font-weight:600;padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-primary);color:var(--kf-color-white);cursor:pointer;transition:background .14s,border-color .14s,color .14s}button:hover:not(:disabled){background:var(--kf-color-primary-hover);border-color:var(--kf-color-primary-hover)}button:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}button:disabled{opacity:.5;cursor:not-allowed}button.kf-button--secondary{border-color:var(--kf-color-secondary);background:var(--kf-color-secondary)}button.kf-button--secondary:hover:not(:disabled){background:var(--kf-color-secondary-hover);border-color:var(--kf-color-secondary-hover)}button.kf-button--ghost{background:transparent;border-color:transparent;color:var(--kf-color-primary)}button.kf-button--ghost:hover:not(:disabled){background:var(--kf-color-primary-soft)}button.kf-button--ghost[aria-pressed=true]{color:var(--kf-color-secondary)}.kf-button__icon{transition:transform .14s ease}button[aria-pressed=true] .kf-button__icon{transform:scale(1.12)}@media(prefers-reduced-motion:reduce){.kf-button__icon{transition:none}}button.kf-button--outline{background:transparent;border-color:var(--kf-color-border-strong);color:var(--kf-color-text)}button.kf-button--outline:hover:not(:disabled){background:var(--kf-color-surface);border-color:var(--kf-color-text-muted)}button.kf-button--danger{border-color:var(--kf-color-error);background:var(--kf-color-error)}button.kf-button--danger:hover:not(:disabled){background:var(--kf-color-error-hover);border-color:var(--kf-color-error-hover)}button.kf-button--surface{border-color:var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);box-shadow:var(--kf-shadow-md)}button.kf-button--surface:hover:not(:disabled){background:var(--kf-color-white);border-color:var(--kf-color-border-strong)}button.kf-button--circle{aspect-ratio:1;padding:var(--kf-space-sm);border-radius:var(--kf-radius-pill)}button.kf-button--circle.kf-button--sm{padding:6px}button.kf-button--circle.kf-button--lg{padding:var(--kf-space-md)}button.kf-button--sm{padding:6px var(--kf-space-sm);font-size:.85rem}button.kf-button--lg{padding:var(--kf-space-sm) var(--kf-space-lg);font-size:1.05rem}\n"] }]
1220
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], block: [{ type: i0.Input, args: [{ isSignal: true, alias: "block", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaExpanded", required: false }] }], ariaPressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaPressed", required: false }] }], ariaHaspopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaHaspopup", required: false }] }], nativeButton: [{ type: i0.ViewChild, args: ['nativeButton', { isSignal: true }] }] } });
1167
1221
 
1168
1222
  const TONE_ICON = {
1169
1223
  info: 'alert-circle',
@@ -1278,6 +1332,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
1278
1332
  * punto (`dot`) usa el color BASE de cada tono, no `-strong` — ese último
1279
1333
  * está pensado para texto legible sobre el fondo pastel de `soft` y se ve
1280
1334
  * apagado sobre una superficie translúcida/oscura.
1335
+ *
1336
+ * `truncate` corta un texto largo con elipsis (ej. un estado de SAP como
1337
+ * "WF Aprobación Política descuento") en vez de ensanchar la fila que lo
1338
+ * contiene: el badge se limita al ancho de su contenedor (`max-width: 100%`,
1339
+ * `min-width: 0`) y el punto no se recorta. El texto completo lo pone el
1340
+ * consumidor en `title` (el badge no puede leer su propio contenido
1341
+ * proyectado para generarlo):
1342
+ *
1343
+ * <kf-badge tone="warn" dot truncate [title]="q.statusLabel">{{ q.statusLabel }}</kf-badge>
1281
1344
  */
1282
1345
  class Badge {
1283
1346
  tone = input('neutral', /* @ts-ignore */
@@ -1285,13 +1348,17 @@ class Badge {
1285
1348
  variant = input('soft', /* @ts-ignore */
1286
1349
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
1287
1350
  dot = input(false, { ...(ngDevMode ? { debugName: "dot" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1351
+ /** Corta el texto con elipsis al ancho del contenedor. Ver JSDoc de la clase. */
1352
+ truncate = input(false, { ...(ngDevMode ? { debugName: "truncate" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1288
1353
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Badge, deps: [], target: i0.ɵɵFactoryTarget.Component });
1289
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Badge, isStandalone: true, selector: "kf-badge", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<span\n class=\"kf-badge\"\n [class]=\"'kf-badge--' + tone()\"\n [class.kf-badge--glass]=\"variant() === 'glass'\"\n>\n @if (dot()) {\n <span class=\"kf-badge__dot\"></span>\n }\n <ng-content />\n</span>\n", styles: [":host{display:inline-block}.kf-badge{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);font:inherit;font-size:var(--kf-font-size-caption);font-weight:600;line-height:1;white-space:nowrap}.kf-badge__dot{inline-size:6px;block-size:6px;border-radius:var(--kf-radius-pill);flex:none;background:var(--kf-badge-accent, currentColor)}.kf-badge--neutral{background:var(--kf-color-gray-200);color:var(--kf-color-text);--kf-badge-accent: var(--kf-color-text)}.kf-badge--ok{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong);--kf-badge-accent: var(--kf-color-success-strong)}.kf-badge--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong);--kf-badge-accent: var(--kf-color-warning-strong)}.kf-badge--error{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong);--kf-badge-accent: var(--kf-color-error-strong)}.kf-badge--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong);--kf-badge-accent: var(--kf-color-info-strong)}.kf-badge--service{background:var(--kf-color-secondary-soft);color:var(--kf-color-secondary-strong);--kf-badge-accent: var(--kf-color-secondary-strong)}.kf-badge--glass{background:#ffffff24;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:var(--kf-color-white)}.kf-badge--ok.kf-badge--glass{--kf-badge-accent: var(--kf-color-success)}.kf-badge--warn.kf-badge--glass{--kf-badge-accent: var(--kf-color-warning)}.kf-badge--error.kf-badge--glass{--kf-badge-accent: var(--kf-color-error)}.kf-badge--info.kf-badge--glass{--kf-badge-accent: var(--kf-color-info)}.kf-badge--service.kf-badge--glass{--kf-badge-accent: var(--kf-color-secondary)}\n"] });
1354
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Badge, isStandalone: true, selector: "kf-badge", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dot: { classPropertyName: "dot", publicName: "dot", isSignal: true, isRequired: false, transformFunction: null }, truncate: { classPropertyName: "truncate", publicName: "truncate", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-badge-host--truncate": "truncate()" } }, ngImport: i0, template: "<span\n class=\"kf-badge\"\n [class]=\"'kf-badge--' + tone()\"\n [class.kf-badge--glass]=\"variant() === 'glass'\"\n>\n @if (dot()) {\n <span class=\"kf-badge__dot\"></span>\n }\n <!-- `display: contents` salvo con `truncate`: sin truncar, el contenido\n proyectado sigue siendo hijo directo del flex (mismo `gap` de siempre). -->\n <span class=\"kf-badge__label\"><ng-content /></span>\n</span>\n", styles: [":host{display:inline-block}.kf-badge{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);font:inherit;font-size:var(--kf-font-size-caption);font-weight:600;line-height:1;white-space:nowrap}.kf-badge__label{display:contents}:host(.kf-badge-host--truncate){display:inline-flex;max-width:100%;min-width:0;vertical-align:middle}:host(.kf-badge-host--truncate) .kf-badge{box-sizing:border-box;max-width:100%;min-width:0}:host(.kf-badge-host--truncate) .kf-badge__label{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.25;margin-block:-.125em}.kf-badge__dot{inline-size:6px;block-size:6px;border-radius:var(--kf-radius-pill);flex:none;background:var(--kf-badge-accent, currentColor)}.kf-badge--neutral{background:var(--kf-color-gray-200);color:var(--kf-color-text);--kf-badge-accent: var(--kf-color-text)}.kf-badge--ok{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong);--kf-badge-accent: var(--kf-color-success-strong)}.kf-badge--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong);--kf-badge-accent: var(--kf-color-warning-strong)}.kf-badge--error{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong);--kf-badge-accent: var(--kf-color-error-strong)}.kf-badge--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong);--kf-badge-accent: var(--kf-color-info-strong)}.kf-badge--service{background:var(--kf-color-secondary-soft);color:var(--kf-color-secondary-strong);--kf-badge-accent: var(--kf-color-secondary-strong)}.kf-badge--glass{background:#ffffff24;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:var(--kf-color-white)}.kf-badge--ok.kf-badge--glass{--kf-badge-accent: var(--kf-color-success)}.kf-badge--warn.kf-badge--glass{--kf-badge-accent: var(--kf-color-warning)}.kf-badge--error.kf-badge--glass{--kf-badge-accent: var(--kf-color-error)}.kf-badge--info.kf-badge--glass{--kf-badge-accent: var(--kf-color-info)}.kf-badge--service.kf-badge--glass{--kf-badge-accent: var(--kf-color-secondary)}\n"] });
1290
1355
  }
1291
1356
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Badge, decorators: [{
1292
1357
  type: Component,
1293
- args: [{ selector: 'kf-badge', template: "<span\n class=\"kf-badge\"\n [class]=\"'kf-badge--' + tone()\"\n [class.kf-badge--glass]=\"variant() === 'glass'\"\n>\n @if (dot()) {\n <span class=\"kf-badge__dot\"></span>\n }\n <ng-content />\n</span>\n", styles: [":host{display:inline-block}.kf-badge{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);font:inherit;font-size:var(--kf-font-size-caption);font-weight:600;line-height:1;white-space:nowrap}.kf-badge__dot{inline-size:6px;block-size:6px;border-radius:var(--kf-radius-pill);flex:none;background:var(--kf-badge-accent, currentColor)}.kf-badge--neutral{background:var(--kf-color-gray-200);color:var(--kf-color-text);--kf-badge-accent: var(--kf-color-text)}.kf-badge--ok{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong);--kf-badge-accent: var(--kf-color-success-strong)}.kf-badge--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong);--kf-badge-accent: var(--kf-color-warning-strong)}.kf-badge--error{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong);--kf-badge-accent: var(--kf-color-error-strong)}.kf-badge--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong);--kf-badge-accent: var(--kf-color-info-strong)}.kf-badge--service{background:var(--kf-color-secondary-soft);color:var(--kf-color-secondary-strong);--kf-badge-accent: var(--kf-color-secondary-strong)}.kf-badge--glass{background:#ffffff24;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:var(--kf-color-white)}.kf-badge--ok.kf-badge--glass{--kf-badge-accent: var(--kf-color-success)}.kf-badge--warn.kf-badge--glass{--kf-badge-accent: var(--kf-color-warning)}.kf-badge--error.kf-badge--glass{--kf-badge-accent: var(--kf-color-error)}.kf-badge--info.kf-badge--glass{--kf-badge-accent: var(--kf-color-info)}.kf-badge--service.kf-badge--glass{--kf-badge-accent: var(--kf-color-secondary)}\n"] }]
1294
- }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
1358
+ args: [{ selector: 'kf-badge', host: {
1359
+ '[class.kf-badge-host--truncate]': 'truncate()',
1360
+ }, template: "<span\n class=\"kf-badge\"\n [class]=\"'kf-badge--' + tone()\"\n [class.kf-badge--glass]=\"variant() === 'glass'\"\n>\n @if (dot()) {\n <span class=\"kf-badge__dot\"></span>\n }\n <!-- `display: contents` salvo con `truncate`: sin truncar, el contenido\n proyectado sigue siendo hijo directo del flex (mismo `gap` de siempre). -->\n <span class=\"kf-badge__label\"><ng-content /></span>\n</span>\n", styles: [":host{display:inline-block}.kf-badge{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);font:inherit;font-size:var(--kf-font-size-caption);font-weight:600;line-height:1;white-space:nowrap}.kf-badge__label{display:contents}:host(.kf-badge-host--truncate){display:inline-flex;max-width:100%;min-width:0;vertical-align:middle}:host(.kf-badge-host--truncate) .kf-badge{box-sizing:border-box;max-width:100%;min-width:0}:host(.kf-badge-host--truncate) .kf-badge__label{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.25;margin-block:-.125em}.kf-badge__dot{inline-size:6px;block-size:6px;border-radius:var(--kf-radius-pill);flex:none;background:var(--kf-badge-accent, currentColor)}.kf-badge--neutral{background:var(--kf-color-gray-200);color:var(--kf-color-text);--kf-badge-accent: var(--kf-color-text)}.kf-badge--ok{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong);--kf-badge-accent: var(--kf-color-success-strong)}.kf-badge--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong);--kf-badge-accent: var(--kf-color-warning-strong)}.kf-badge--error{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong);--kf-badge-accent: var(--kf-color-error-strong)}.kf-badge--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong);--kf-badge-accent: var(--kf-color-info-strong)}.kf-badge--service{background:var(--kf-color-secondary-soft);color:var(--kf-color-secondary-strong);--kf-badge-accent: var(--kf-color-secondary-strong)}.kf-badge--glass{background:#ffffff24;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);color:var(--kf-color-white)}.kf-badge--ok.kf-badge--glass{--kf-badge-accent: var(--kf-color-success)}.kf-badge--warn.kf-badge--glass{--kf-badge-accent: var(--kf-color-warning)}.kf-badge--error.kf-badge--glass{--kf-badge-accent: var(--kf-color-error)}.kf-badge--info.kf-badge--glass{--kf-badge-accent: var(--kf-color-info)}.kf-badge--service.kf-badge--glass{--kf-badge-accent: var(--kf-color-secondary)}\n"] }]
1361
+ }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }], truncate: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncate", required: false }] }] } });
1295
1362
 
1296
1363
  /**
1297
1364
  * Ruta de navegación de las vistas internas. El último nivel es la página
@@ -1620,7 +1687,7 @@ class Calendar {
1620
1687
  };
1621
1688
  }
1622
1689
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Calendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
1623
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Calendar, isStandalone: true, selector: "kf-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, month: { classPropertyName: "month", publicName: "month", 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 }, availableDates: { classPropertyName: "availableDates", publicName: "availableDates", isSignal: true, isRequired: false, transformFunction: null }, markedDates: { classPropertyName: "markedDates", publicName: "markedDates", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, monthNames: { classPropertyName: "monthNames", publicName: "monthNames", isSignal: true, isRequired: false, transformFunction: null }, weekdayLabels: { classPropertyName: "weekdayLabels", publicName: "weekdayLabels", isSignal: true, isRequired: false, transformFunction: null }, weekdayNames: { classPropertyName: "weekdayNames", publicName: "weekdayNames", isSignal: true, isRequired: false, transformFunction: null }, markedLabel: { classPropertyName: "markedLabel", publicName: "markedLabel", isSignal: true, isRequired: false, transformFunction: null }, previousMonthLabel: { classPropertyName: "previousMonthLabel", publicName: "previousMonthLabel", isSignal: true, isRequired: false, transformFunction: null }, nextMonthLabel: { classPropertyName: "nextMonthLabel", publicName: "nextMonthLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", month: "monthChange" }, ngImport: i0, template: "<div class=\"kf-calendar\" role=\"group\" [attr.aria-label]=\"ariaLabel()\">\n <div class=\"kf-calendar__header\">\n <kf-button\n variant=\"ghost\"\n shape=\"circle\"\n size=\"lg\"\n type=\"button\"\n icon=\"chevron-left\"\n [ariaLabel]=\"previousMonthLabel()\"\n [disabled]=\"!canGoPrevious()\"\n (click)=\"previousMonth()\"\n />\n <kf-text\n class=\"kf-calendar__title\"\n as=\"span\"\n variant=\"title-sm\"\n aria-live=\"polite\"\n [attr.id]=\"titleId\"\n >{{ title() }}</kf-text\n >\n <kf-button\n variant=\"ghost\"\n shape=\"circle\"\n size=\"lg\"\n type=\"button\"\n icon=\"chevron-right\"\n [ariaLabel]=\"nextMonthLabel()\"\n [disabled]=\"!canGoNext()\"\n (click)=\"nextMonth()\"\n />\n </div>\n\n <div\n class=\"kf-calendar__grid\"\n role=\"grid\"\n [class.kf-calendar__grid--loading]=\"loading()\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-busy]=\"loading() || null\"\n >\n <div class=\"kf-calendar__row\" role=\"row\">\n @for (label of weekdayLabels(); track $index) {\n <span\n class=\"kf-calendar__weekday kf-overline-text\"\n role=\"columnheader\"\n [attr.aria-label]=\"weekdayNames()[$index]\"\n >{{ label }}</span\n >\n }\n </div>\n\n @for (week of weeks(); track week[0].iso) {\n <div class=\"kf-calendar__row\" role=\"row\">\n @for (day of week; track day.iso) {\n @if (day.inMonth) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"kf-calendar__day\"\n [class.kf-calendar__day--selectable]=\"day.selectable\"\n [class.kf-calendar__day--selected]=\"day.selected\"\n [class.kf-calendar__day--today]=\"day.today\"\n [class.kf-calendar__day--marked]=\"day.marked\"\n [attr.data-date]=\"day.iso\"\n [attr.aria-label]=\"day.ariaLabel\"\n [attr.aria-selected]=\"day.selected\"\n [attr.aria-disabled]=\"!day.selectable\"\n [attr.aria-current]=\"day.today ? 'date' : null\"\n [attr.tabindex]=\"day.iso === activeDate() ? 0 : -1\"\n (click)=\"selectDay(day)\"\n (focus)=\"onDayFocus(day.iso)\"\n (keydown)=\"onDayKeydown($event, day.iso)\"\n >\n {{ day.day }}\n </button>\n } @else {\n <span role=\"gridcell\" class=\"kf-calendar__day kf-calendar__day--outside\">\n <span aria-hidden=\"true\">{{ day.day }}</span>\n </span>\n }\n }\n </div>\n }\n </div>\n\n <ng-content select=\"[footer]\" />\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)}:host{display:block}.kf-calendar{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-calendar__header{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-calendar__title{flex:1;text-align:center}.kf-calendar__grid{display:flex;flex-direction:column;gap:2px;transition:opacity .2s ease}.kf-calendar__grid--loading{opacity:.5;cursor:progress}.kf-calendar__row{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:2px}.kf-calendar__weekday{display:flex;align-items:center;justify-content:center;min-height:32px}.kf-calendar__day{position:relative;display:flex;align-items:center;justify-content:center;min-width:0;min-height:44px;padding:0;box-sizing:border-box;border:none;border-radius:var(--kf-radius-sm);background:transparent;font:inherit;font-size:var(--kf-font-size-body-sm);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted);cursor:default;transition:background-color .14s,color .14s}.kf-calendar__day--selectable{font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink);cursor:pointer}.kf-calendar__grid--loading .kf-calendar__day--selectable{cursor:progress}.kf-calendar__day--selectable:hover:not(.kf-calendar__day--selected){background:var(--kf-color-primary-soft)}.kf-calendar__day:focus-visible{z-index:1;outline:2px solid var(--kf-color-primary);outline-offset:1px}.kf-calendar__day--today{box-shadow:inset 0 0 0 1.5px var(--kf-color-primary)}.kf-calendar__day--selected{background:var(--kf-color-primary);color:var(--kf-color-white)}.kf-calendar__day--selected:hover{background:var(--kf-color-primary-hover)}.kf-calendar__day--outside{color:var(--kf-color-gray-400)}.kf-calendar__day--marked:after{content:\"\";position:absolute;bottom:6px;left:50%;width:4px;height:4px;margin-left:-2px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary)}.kf-calendar__day--selected.kf-calendar__day--marked:after{background:var(--kf-color-white)}@media(prefers-reduced-motion:reduce){.kf-calendar__grid,.kf-calendar__day{transition:none}}\n"], dependencies: [{ kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1690
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Calendar, isStandalone: true, selector: "kf-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, month: { classPropertyName: "month", publicName: "month", 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 }, availableDates: { classPropertyName: "availableDates", publicName: "availableDates", isSignal: true, isRequired: false, transformFunction: null }, markedDates: { classPropertyName: "markedDates", publicName: "markedDates", isSignal: true, isRequired: false, transformFunction: null }, today: { classPropertyName: "today", publicName: "today", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, monthNames: { classPropertyName: "monthNames", publicName: "monthNames", isSignal: true, isRequired: false, transformFunction: null }, weekdayLabels: { classPropertyName: "weekdayLabels", publicName: "weekdayLabels", isSignal: true, isRequired: false, transformFunction: null }, weekdayNames: { classPropertyName: "weekdayNames", publicName: "weekdayNames", isSignal: true, isRequired: false, transformFunction: null }, markedLabel: { classPropertyName: "markedLabel", publicName: "markedLabel", isSignal: true, isRequired: false, transformFunction: null }, previousMonthLabel: { classPropertyName: "previousMonthLabel", publicName: "previousMonthLabel", isSignal: true, isRequired: false, transformFunction: null }, nextMonthLabel: { classPropertyName: "nextMonthLabel", publicName: "nextMonthLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", month: "monthChange" }, ngImport: i0, template: "<div class=\"kf-calendar\" role=\"group\" [attr.aria-label]=\"ariaLabel()\">\n <div class=\"kf-calendar__header\">\n <kf-button\n variant=\"ghost\"\n shape=\"circle\"\n size=\"lg\"\n type=\"button\"\n icon=\"chevron-left\"\n [ariaLabel]=\"previousMonthLabel()\"\n [disabled]=\"!canGoPrevious()\"\n (click)=\"previousMonth()\"\n />\n <kf-text\n class=\"kf-calendar__title\"\n as=\"span\"\n variant=\"title-sm\"\n aria-live=\"polite\"\n [attr.id]=\"titleId\"\n >{{ title() }}</kf-text\n >\n <kf-button\n variant=\"ghost\"\n shape=\"circle\"\n size=\"lg\"\n type=\"button\"\n icon=\"chevron-right\"\n [ariaLabel]=\"nextMonthLabel()\"\n [disabled]=\"!canGoNext()\"\n (click)=\"nextMonth()\"\n />\n </div>\n\n <div\n class=\"kf-calendar__grid\"\n role=\"grid\"\n [class.kf-calendar__grid--loading]=\"loading()\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-busy]=\"loading() || null\"\n >\n <div class=\"kf-calendar__row\" role=\"row\">\n @for (label of weekdayLabels(); track $index) {\n <span\n class=\"kf-calendar__weekday kf-overline-text\"\n role=\"columnheader\"\n [attr.aria-label]=\"weekdayNames()[$index]\"\n >{{ label }}</span\n >\n }\n </div>\n\n @for (week of weeks(); track week[0].iso) {\n <div class=\"kf-calendar__row\" role=\"row\">\n @for (day of week; track day.iso) {\n @if (day.inMonth) {\n <button\n type=\"button\"\n role=\"gridcell\"\n class=\"kf-calendar__day\"\n [class.kf-calendar__day--selectable]=\"day.selectable\"\n [class.kf-calendar__day--selected]=\"day.selected\"\n [class.kf-calendar__day--today]=\"day.today\"\n [class.kf-calendar__day--marked]=\"day.marked\"\n [attr.data-date]=\"day.iso\"\n [attr.aria-label]=\"day.ariaLabel\"\n [attr.aria-selected]=\"day.selected\"\n [attr.aria-disabled]=\"!day.selectable\"\n [attr.aria-current]=\"day.today ? 'date' : null\"\n [attr.tabindex]=\"day.iso === activeDate() ? 0 : -1\"\n (click)=\"selectDay(day)\"\n (focus)=\"onDayFocus(day.iso)\"\n (keydown)=\"onDayKeydown($event, day.iso)\"\n >\n {{ day.day }}\n </button>\n } @else {\n <span role=\"gridcell\" class=\"kf-calendar__day kf-calendar__day--outside\">\n <span aria-hidden=\"true\">{{ day.day }}</span>\n </span>\n }\n }\n </div>\n }\n </div>\n\n <ng-content select=\"[footer]\" />\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)}:host{display:block}.kf-calendar{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-calendar__header{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-calendar__title{flex:1;text-align:center}.kf-calendar__grid{display:flex;flex-direction:column;gap:2px;transition:opacity .2s ease}.kf-calendar__grid--loading{opacity:.5;cursor:progress}.kf-calendar__row{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:2px}.kf-calendar__weekday{display:flex;align-items:center;justify-content:center;min-height:32px}.kf-calendar__day{position:relative;display:flex;align-items:center;justify-content:center;min-width:0;min-height:44px;padding:0;box-sizing:border-box;border:none;border-radius:var(--kf-radius-sm);background:transparent;font:inherit;font-size:var(--kf-font-size-body-sm);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted);cursor:default;transition:background-color .14s,color .14s}.kf-calendar__day--selectable{font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink);cursor:pointer}.kf-calendar__grid--loading .kf-calendar__day--selectable{cursor:progress}.kf-calendar__day--selectable:hover:not(.kf-calendar__day--selected){background:var(--kf-color-primary-soft)}.kf-calendar__day:focus-visible{z-index:1;outline:2px solid var(--kf-color-primary);outline-offset:1px}.kf-calendar__day--today{box-shadow:inset 0 0 0 1.5px var(--kf-color-primary)}.kf-calendar__day--selected{background:var(--kf-color-primary);color:var(--kf-color-white)}.kf-calendar__day--selected:hover{background:var(--kf-color-primary-hover)}.kf-calendar__day--outside{color:var(--kf-color-gray-400)}.kf-calendar__day--marked:after{content:\"\";position:absolute;bottom:6px;left:50%;width:4px;height:4px;margin-left:-2px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary)}.kf-calendar__day--selected.kf-calendar__day--marked:after{background:var(--kf-color-white)}@media(prefers-reduced-motion:reduce){.kf-calendar__grid,.kf-calendar__day{transition:none}}\n"], dependencies: [{ kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1624
1691
  }
1625
1692
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Calendar, decorators: [{
1626
1693
  type: Component,
@@ -1962,7 +2029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
1962
2029
  }, 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"] }]
1963
2030
  }], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
1964
2031
 
1965
- let nextId$2 = 0;
2032
+ let nextId$4 = 0;
1966
2033
  /**
1967
2034
  * Bloque colapsable genérico: un header clickeable (título + chevron) que
1968
2035
  * expande/colapsa contenido proyectado libre, sin saber nada de lo que hay
@@ -1989,7 +2056,7 @@ class Collapsible {
1989
2056
  ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1990
2057
  expanded = model(false, /* @ts-ignore */
1991
2058
  ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
1992
- instanceId = `kf-collapsible-${nextId$2++}`;
2059
+ instanceId = `kf-collapsible-${nextId$4++}`;
1993
2060
  triggerId = `${this.instanceId}-trigger`;
1994
2061
  panelId = `${this.instanceId}-panel`;
1995
2062
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Collapsible, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2286,22 +2353,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
2286
2353
  * No pudimos cargar tus vehículos
2287
2354
  * <kf-button (click)="retry()">Reintentar</kf-button>
2288
2355
  * </kf-empty-state>
2356
+ *
2357
+ * ## Variante `card`
2358
+ * Tarjeta centrada (fondo blanco, borde, sombra, ancho máximo 480px) para
2359
+ * un estado que REEMPLAZA a un contenido grande — ej. un iframe que no
2360
+ * cargó o un documento que aún no existe. Ícono en un círculo tonal de
2361
+ * 56px según `tone` (`warn`, `info`, `error` o `neutral`), `title` como
2362
+ * título, el contenido proyectado como texto (puede incluir una lista de
2363
+ * pasos) y `[kf-empty-state-actions]` para los botones. Los roles
2364
+ * (`role="alert"` para un error) los pone el consumidor en el host: no
2365
+ * todo estado de esta forma debe interrumpir al lector de pantalla.
2366
+ *
2367
+ * <kf-empty-state variant="card" tone="warn" icon="alert-triangle"
2368
+ * title="No pudimos cargar la propuesta" role="alert">
2369
+ * <p>Este contenido solo está disponible dentro de la red de la empresa.</p>
2370
+ * <ol>…pasos…</ol>
2371
+ * <div kf-empty-state-actions>
2372
+ * <kf-button (click)="retry()">Reintentar</kf-button>
2373
+ * </div>
2374
+ * </kf-empty-state>
2375
+ *
2376
+ * `[kf-empty-state-footer]` va DESPUÉS de las acciones, para pistas y
2377
+ * metadatos debajo de los botones ("¿Ya estás conectado? …", "Intento 2 ·
2378
+ * 10:42"). Vacío, no ocupa lugar.
2379
+ *
2380
+ * `title` se dibuja como texto destacado, no como encabezado (`<h*>`): el
2381
+ * nivel de encabezado depende de la página que lo contiene, y un estado
2382
+ * vacío no debe alterar su estructura.
2289
2383
  */
2290
2384
  class EmptyState {
2291
2385
  icon = input(undefined, /* @ts-ignore */
2292
2386
  ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
2293
- /** `neutral` (default): cero resultados. `error`: fallo real de carga. */
2387
+ /** `neutral` (default): cero resultados. `error`: fallo real de carga. `warn`/`info`: tiñen el chip del ícono. */
2294
2388
  tone = input('neutral', /* @ts-ignore */
2295
2389
  ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
2390
+ /** `inline` (default, histórico) o `card` (tarjeta centrada). Ver JSDoc de la clase. */
2391
+ variant = input('inline', /* @ts-ignore */
2392
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
2393
+ /** Título destacado sobre el contenido. Opcional en ambas variantes. */
2394
+ title = input(/* @ts-ignore */
2395
+ ...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
2396
+ iconSize = computed(() => this.variant() === 'card' || this.tone() === 'error' ? 'lg' : 'md', /* @ts-ignore */
2397
+ ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
2296
2398
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: EmptyState, deps: [], target: i0.ɵɵFactoryTarget.Component });
2297
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: EmptyState, isStandalone: true, selector: "kf-empty-state", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-empty-state--error": "tone() === 'error'" } }, ngImport: i0, template: "@if (icon(); as iconName) {\n <span class=\"kf-empty-state__icon\">\n <kf-icon [name]=\"iconName\" [size]=\"tone() === 'error' ? 'lg' : 'md'\" />\n </span>\n}\n<div class=\"kf-empty-state__content\">\n <ng-content />\n</div>\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-sm);padding:var(--kf-space-xl);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-surface);color:var(--kf-color-text-muted);font:inherit;text-align:center}:host(.kf-empty-state--error){border:none;background:var(--kf-color-error-soft)}.kf-empty-state__content{color:var(--kf-color-text-muted);font-family:var(--kf-font-body)}:host(.kf-empty-state--error) .kf-empty-state__content{color:var(--kf-color-error-strong)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
2399
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: EmptyState, isStandalone: true, selector: "kf-empty-state", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, 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 } }, host: { properties: { "class.kf-empty-state--error": "tone() === 'error'", "class.kf-empty-state--warn": "tone() === 'warn'", "class.kf-empty-state--info": "tone() === 'info'", "class.kf-empty-state--card": "variant() === 'card'", "attr.title": "null" } }, ngImport: i0, template: "@if (icon(); as iconName) {\n <span class=\"kf-empty-state__icon\">\n <kf-icon [name]=\"iconName\" [size]=\"iconSize()\" />\n </span>\n}\n@if (title(); as titleText) {\n <p class=\"kf-empty-state__title\">{{ titleText }}</p>\n}\n<div class=\"kf-empty-state__content\">\n <ng-content />\n</div>\n<div class=\"kf-empty-state__actions\"><ng-content select=\"[kf-empty-state-actions]\" /></div>\n<div class=\"kf-empty-state__footer\"><ng-content select=\"[kf-empty-state-footer]\" /></div>\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-sm);padding:var(--kf-space-xl);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-surface);color:var(--kf-color-text-muted);font:inherit;text-align:center}:host(.kf-empty-state--error){border:none;background:var(--kf-color-error-soft)}.kf-empty-state__content{color:var(--kf-color-text-muted);font-family:var(--kf-font-body)}:host(.kf-empty-state--error) .kf-empty-state__content{color:var(--kf-color-error-strong)}.kf-empty-state__title{margin:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);line-height:1.25;color:var(--kf-color-ink)}.kf-empty-state__actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-sm)}.kf-empty-state__actions>::ng-deep [kf-empty-state-actions]{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-sm)}.kf-empty-state__actions:empty,.kf-empty-state__footer:empty{display:none}.kf-empty-state__footer{display:flex;flex-direction:column;align-items:center;gap:var(--kf-space-xs);font-size:var(--kf-font-size-caption);color:var(--kf-color-text)}:host(.kf-empty-state--warn) .kf-empty-state__icon{color:var(--kf-color-warning-strong)}:host(.kf-empty-state--info) .kf-empty-state__icon{color:var(--kf-color-info-strong)}:host(.kf-empty-state--card){box-sizing:border-box;width:min(480px,100%);gap:var(--kf-space-md);padding:var(--kf-space-xl) var(--kf-space-lg);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-lg);background:var(--kf-color-white);box-shadow:var(--kf-shadow-md);color:var(--kf-color-text)}:host(.kf-empty-state--card) .kf-empty-state__title+.kf-empty-state__content{margin-top:calc(var(--kf-space-sm) - var(--kf-space-md))}:host(.kf-empty-state--card) .kf-empty-state__content>::ng-deep :is(p,ol,ul),:host(.kf-empty-state--card) .kf-empty-state__footer>::ng-deep [kf-empty-state-footer] :is(p),:host(.kf-empty-state--card) .kf-empty-state__footer>::ng-deep p[kf-empty-state-footer]{margin:0}:host(.kf-empty-state--card) .kf-empty-state__content:empty{display:none}:host(.kf-empty-state--card) .kf-empty-state__content{display:flex;flex-direction:column;align-items:stretch;gap:var(--kf-space-md);align-self:stretch;color:var(--kf-color-text);font-size:var(--kf-font-size-body-sm);line-height:1.5}:host(.kf-empty-state--card.kf-empty-state--error) .kf-empty-state__content{color:var(--kf-color-text)}:host(.kf-empty-state--card) .kf-empty-state__icon{display:flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:50%;background:var(--kf-color-surface);color:var(--kf-color-text-muted)}:host(.kf-empty-state--card.kf-empty-state--warn) .kf-empty-state__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-empty-state--card.kf-empty-state--info) .kf-empty-state__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-empty-state--card.kf-empty-state--error) .kf-empty-state__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
2298
2400
  }
2299
2401
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: EmptyState, decorators: [{
2300
2402
  type: Component,
2301
2403
  args: [{ selector: 'kf-empty-state', imports: [Icon], host: {
2302
2404
  '[class.kf-empty-state--error]': "tone() === 'error'",
2303
- }, template: "@if (icon(); as iconName) {\n <span class=\"kf-empty-state__icon\">\n <kf-icon [name]=\"iconName\" [size]=\"tone() === 'error' ? 'lg' : 'md'\" />\n </span>\n}\n<div class=\"kf-empty-state__content\">\n <ng-content />\n</div>\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-sm);padding:var(--kf-space-xl);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-surface);color:var(--kf-color-text-muted);font:inherit;text-align:center}:host(.kf-empty-state--error){border:none;background:var(--kf-color-error-soft)}.kf-empty-state__content{color:var(--kf-color-text-muted);font-family:var(--kf-font-body)}:host(.kf-empty-state--error) .kf-empty-state__content{color:var(--kf-color-error-strong)}\n"] }]
2304
- }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
2405
+ '[class.kf-empty-state--warn]': "tone() === 'warn'",
2406
+ '[class.kf-empty-state--info]': "tone() === 'info'",
2407
+ '[class.kf-empty-state--card]': "variant() === 'card'",
2408
+ // `title` es un input con nombre de atributo nativo: escrito estático,
2409
+ // Angular también lo dejaría en el host como tooltip sobre todo el bloque.
2410
+ '[attr.title]': 'null',
2411
+ }, template: "@if (icon(); as iconName) {\n <span class=\"kf-empty-state__icon\">\n <kf-icon [name]=\"iconName\" [size]=\"iconSize()\" />\n </span>\n}\n@if (title(); as titleText) {\n <p class=\"kf-empty-state__title\">{{ titleText }}</p>\n}\n<div class=\"kf-empty-state__content\">\n <ng-content />\n</div>\n<div class=\"kf-empty-state__actions\"><ng-content select=\"[kf-empty-state-actions]\" /></div>\n<div class=\"kf-empty-state__footer\"><ng-content select=\"[kf-empty-state-footer]\" /></div>\n", styles: [":host{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-sm);padding:var(--kf-space-xl);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-surface);color:var(--kf-color-text-muted);font:inherit;text-align:center}:host(.kf-empty-state--error){border:none;background:var(--kf-color-error-soft)}.kf-empty-state__content{color:var(--kf-color-text-muted);font-family:var(--kf-font-body)}:host(.kf-empty-state--error) .kf-empty-state__content{color:var(--kf-color-error-strong)}.kf-empty-state__title{margin:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);line-height:1.25;color:var(--kf-color-ink)}.kf-empty-state__actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-sm)}.kf-empty-state__actions>::ng-deep [kf-empty-state-actions]{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-sm)}.kf-empty-state__actions:empty,.kf-empty-state__footer:empty{display:none}.kf-empty-state__footer{display:flex;flex-direction:column;align-items:center;gap:var(--kf-space-xs);font-size:var(--kf-font-size-caption);color:var(--kf-color-text)}:host(.kf-empty-state--warn) .kf-empty-state__icon{color:var(--kf-color-warning-strong)}:host(.kf-empty-state--info) .kf-empty-state__icon{color:var(--kf-color-info-strong)}:host(.kf-empty-state--card){box-sizing:border-box;width:min(480px,100%);gap:var(--kf-space-md);padding:var(--kf-space-xl) var(--kf-space-lg);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-lg);background:var(--kf-color-white);box-shadow:var(--kf-shadow-md);color:var(--kf-color-text)}:host(.kf-empty-state--card) .kf-empty-state__title+.kf-empty-state__content{margin-top:calc(var(--kf-space-sm) - var(--kf-space-md))}:host(.kf-empty-state--card) .kf-empty-state__content>::ng-deep :is(p,ol,ul),:host(.kf-empty-state--card) .kf-empty-state__footer>::ng-deep [kf-empty-state-footer] :is(p),:host(.kf-empty-state--card) .kf-empty-state__footer>::ng-deep p[kf-empty-state-footer]{margin:0}:host(.kf-empty-state--card) .kf-empty-state__content:empty{display:none}:host(.kf-empty-state--card) .kf-empty-state__content{display:flex;flex-direction:column;align-items:stretch;gap:var(--kf-space-md);align-self:stretch;color:var(--kf-color-text);font-size:var(--kf-font-size-body-sm);line-height:1.5}:host(.kf-empty-state--card.kf-empty-state--error) .kf-empty-state__content{color:var(--kf-color-text)}:host(.kf-empty-state--card) .kf-empty-state__icon{display:flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:50%;background:var(--kf-color-surface);color:var(--kf-color-text-muted)}:host(.kf-empty-state--card.kf-empty-state--warn) .kf-empty-state__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-empty-state--card.kf-empty-state--info) .kf-empty-state__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-empty-state--card.kf-empty-state--error) .kf-empty-state__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}\n"] }]
2412
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], 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 }] }] } });
2305
2413
 
2306
2414
  /**
2307
2415
  * Bloque placeholder con shimmer para contenido que todavía carga: una línea
@@ -2368,7 +2476,7 @@ class ContentState {
2368
2476
  loadingLabel = input('Cargando…', /* @ts-ignore */
2369
2477
  ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
2370
2478
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, deps: [], target: i0.ɵɵFactoryTarget.Component });
2371
- 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"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
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"] }] });
2372
2480
  }
2373
2481
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, decorators: [{
2374
2482
  type: Component,
@@ -2557,6 +2665,7 @@ class SelectOverlay {
2557
2665
  viewContainerRef;
2558
2666
  triggerRef;
2559
2667
  minWidth;
2668
+ sizing;
2560
2669
  overlayRef = null;
2561
2670
  constructor(overlayService, viewContainerRef, triggerRef,
2562
2671
  /**
@@ -2567,18 +2676,28 @@ class SelectOverlay {
2567
2676
  * trigger normal (240px fijo) ya es más ancho que cualquier
2568
2677
  * `minWidth` razonable, así que este parámetro no le cambia nada.
2569
2678
  */
2570
- minWidth = () => 0) {
2679
+ minWidth = () => 0,
2680
+ /**
2681
+ * `'match'` (default): el panel mide exactamente el ancho del trigger.
2682
+ * `'content'`: el trigger es solo el piso y el panel crece hasta el
2683
+ * ancho natural de sus opciones (con techo `maxWidth: 90vw`) — para un
2684
+ * trigger compacto de barra de herramientas ("Más recientes") cuyas
2685
+ * opciones son más largas ("Más recientes primero") y no deben
2686
+ * truncarse.
2687
+ */
2688
+ sizing = () => 'match') {
2571
2689
  this.overlayService = overlayService;
2572
2690
  this.viewContainerRef = viewContainerRef;
2573
2691
  this.triggerRef = triggerRef;
2574
2692
  this.minWidth = minWidth;
2693
+ this.sizing = sizing;
2575
2694
  }
2576
2695
  attach(panelTemplate, onClose) {
2577
2696
  if (this.overlayRef?.hasAttached()) {
2578
2697
  return;
2579
2698
  }
2580
2699
  const overlayRef = this.getOrCreate();
2581
- overlayRef.updateSize({ width: this.resolveWidth() });
2700
+ overlayRef.updateSize(this.resolveSize());
2582
2701
  const portal = new TemplatePortal(panelTemplate, this.viewContainerRef);
2583
2702
  overlayRef.attach(portal);
2584
2703
  overlayRef.outsidePointerEvents().subscribe((event) => {
@@ -2618,17 +2737,25 @@ class SelectOverlay {
2618
2737
  this.overlayRef = this.overlayService.create({
2619
2738
  positionStrategy,
2620
2739
  scrollStrategy: this.overlayService.scrollStrategies.reposition(),
2621
- width: this.resolveWidth(),
2740
+ ...this.resolveSize(),
2622
2741
  maxWidth: '90vw',
2623
2742
  });
2624
2743
  return this.overlayRef;
2625
2744
  }
2745
+ resolveSize() {
2746
+ const width = this.resolveWidth();
2747
+ // `undefined` (no omitir la clave): `updateSize` mezcla con la config
2748
+ // previa, y solo una clave presente con `undefined` limpia el estilo.
2749
+ return this.sizing() === 'content'
2750
+ ? { width: undefined, minWidth: width }
2751
+ : { width, minWidth: undefined };
2752
+ }
2626
2753
  resolveWidth() {
2627
2754
  return Math.max(this.triggerRef().nativeElement.getBoundingClientRect().width, this.minWidth());
2628
2755
  }
2629
2756
  }
2630
2757
 
2631
- let nextId$1 = 0;
2758
+ let nextId$3 = 0;
2632
2759
  /** Ancho mínimo del panel flotante en `variant="compact"` — el trigger de
2633
2760
  * ese modo puede medir menos de 45px (ej. "10" + caret), y sin este piso
2634
2761
  * la opción quedaría apretada contra ese mismo ancho angosto. 72px es lo
@@ -2649,6 +2776,7 @@ const COMPACT_PANEL_MIN_WIDTH = 72;
2649
2776
  *
2650
2777
  * <kf-select label="Sede" [options]="['Lima', 'Arequipa', 'Trujillo']" placeholder="Selecciona…" />
2651
2778
  * <kf-select [options]="[{ value: 'pe', label: 'Perú' }]" (valueChange)="onCountry($event)" />
2779
+ * <kf-select [options]="[{ value: '1002099', label: 'BBVA', tag: 'Empresa', detail: 'RUC 20100130204' }]" />
2652
2780
  *
2653
2781
  * `variant="compact"` cambia el ancho de `240px` fijo a intrínseco con un
2654
2782
  * techo de `160px` (se ajusta al contenido de la opción seleccionada hasta
@@ -2665,6 +2793,25 @@ const COMPACT_PANEL_MIN_WIDTH = 72;
2665
2793
  * layouts flex/grid donde el ancho fijo de `md` no encaja.
2666
2794
  *
2667
2795
  * <kf-select width="full" [options]="branches" [value]="branch()" (valueChange)="..." />
2796
+ *
2797
+ * Control de orden de una barra de herramientas (ícono + valor + caret, sin
2798
+ * label visible): `icon` agrega un glifo `sm` a la izquierda del valor,
2799
+ * `ariaLabel` da el nombre accesible (el trigger se anuncia como
2800
+ * `"<ariaLabel>: <valor actual>"`), `panelTitle` agrega un título chico en
2801
+ * mayúsculas dentro del panel (decorativo, `aria-hidden`) y
2802
+ * `panelWidth="content"` deja que el panel crezca hasta el ancho de sus
2803
+ * opciones en vez de truncarlas al ancho del trigger compacto.
2804
+ *
2805
+ * <kf-select variant="compact" icon="sort" ariaLabel="Ordenar por"
2806
+ * panelTitle="Ordenar por" panelWidth="content"
2807
+ * [options]="sortOptions" [value]="sort()" (valueChange)="sort.set($event)" />
2808
+ *
2809
+ * Teclado (patrón listbox de WAI-ARIA): ArrowDown/ArrowUp sobre el trigger
2810
+ * cerrado lo abren; al abrir, el foco pasa a la opción seleccionada (o al
2811
+ * buscador, si está visible). En el panel, flechas y Home/End mueven el
2812
+ * foco, Enter/Espacio eligen, Escape cierra y devuelve el foco al trigger,
2813
+ * y Tab cierra el panel y continúa la tabulación desde el trigger. Las
2814
+ * opciones nunca son paradas de Tab (`tabindex="-1"`).
2668
2815
  */
2669
2816
  class Select {
2670
2817
  label = input(/* @ts-ignore */
@@ -2701,8 +2848,32 @@ class Select {
2701
2848
  ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
2702
2849
  searchThreshold = input(7, /* @ts-ignore */
2703
2850
  ...(ngDevMode ? [{ debugName: "searchThreshold" }] : /* istanbul ignore next */ []));
2851
+ /**
2852
+ * Nombre accesible del control cuando no hay `label` visible (ej. "Ordenar
2853
+ * por"). El trigger se anuncia como `"<ariaLabel>: <valor visible>"` (el
2854
+ * mismo texto que muestra el trigger, `shortLabel` si la opción lo trae),
2855
+ * así el nombre contiene el texto visible (WCAG 2.5.3) y además dice qué se
2856
+ * está eligiendo. Ignorado si hay `label`: un `aria-label` pisaría al
2857
+ * `<label for>` visible.
2858
+ */
2859
+ ariaLabel = input(/* @ts-ignore */
2860
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2861
+ /** Ícono `sm` a la izquierda del valor en el trigger (ej. `'sort'`). */
2862
+ icon = input(/* @ts-ignore */
2863
+ ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
2864
+ /** Título chico en mayúsculas dentro del panel, sobre las opciones. Decorativo (`aria-hidden`): el nombre del listbox ya lo da `ariaLabel`/`label`. */
2865
+ panelTitle = input(/* @ts-ignore */
2866
+ ...(ngDevMode ? [undefined, { debugName: "panelTitle" }] : /* istanbul ignore next */ []));
2867
+ /**
2868
+ * `'trigger'` (default): el panel mide lo mismo que el trigger.
2869
+ * `'content'`: el trigger es el ancho mínimo y el panel crece hasta el
2870
+ * ancho de la opción más larga — para triggers compactos cuyas opciones
2871
+ * son más largas que el valor resumido.
2872
+ */
2873
+ panelWidth = input('trigger', /* @ts-ignore */
2874
+ ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
2704
2875
  valueChange = output();
2705
- instanceId = `kf-select-${nextId$1++}`;
2876
+ instanceId = `kf-select-${nextId$3++}`;
2706
2877
  triggerId = `${this.instanceId}-trigger`;
2707
2878
  listboxId = `${this.instanceId}-listbox`;
2708
2879
  errorId = `${this.instanceId}-error`;
@@ -2712,11 +2883,30 @@ class Select {
2712
2883
  ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
2713
2884
  activeIndex = signal(0, /* @ts-ignore */
2714
2885
  ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
2715
- /** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
2886
+ /** Resolves `string | SelectOption` entries into a single shape for the template. */
2716
2887
  normalizedOptions = computed(() => this.options().map((option) => typeof option === 'string' ? { value: option, label: option } : option), /* @ts-ignore */
2717
2888
  ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
2718
2889
  selectedOption = computed(() => this.normalizedOptions().find((option) => option.value === this.value()), /* @ts-ignore */
2719
2890
  ...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
2891
+ /** Texto que muestra el trigger: `shortLabel` o `label` de la opción elegida, o el placeholder. */
2892
+ triggerText = computed(() => {
2893
+ const option = this.selectedOption();
2894
+ return option?.shortLabel ?? option?.label ?? this.placeholder();
2895
+ }, /* @ts-ignore */
2896
+ ...(ngDevMode ? [{ debugName: "triggerText" }] : /* istanbul ignore next */ []));
2897
+ /**
2898
+ * `aria-label` del trigger: solo con `ariaLabel` y SIN `label` visible. Un
2899
+ * `aria-label` tiene prioridad sobre el `<label for>` en el cálculo del
2900
+ * nombre accesible, así que con label visible no se emite.
2901
+ */
2902
+ triggerAriaLabel = computed(() => {
2903
+ const name = this.ariaLabel();
2904
+ if (!name || this.label()) {
2905
+ return null;
2906
+ }
2907
+ return `${name}: ${this.triggerText()}`;
2908
+ }, /* @ts-ignore */
2909
+ ...(ngDevMode ? [{ debugName: "triggerAriaLabel" }] : /* istanbul ignore next */ []));
2720
2910
  showSearch = computed(() => this.searchable() ?? this.normalizedOptions().length > this.searchThreshold(), /* @ts-ignore */
2721
2911
  ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
2722
2912
  /** Opciones visibles tras aplicar el filtro de búsqueda (case/acento-insensitive). */
@@ -2725,7 +2915,7 @@ class Select {
2725
2915
  if (!query) {
2726
2916
  return this.normalizedOptions();
2727
2917
  }
2728
- return this.normalizedOptions().filter((option) => normalizeForSearch(option.label).includes(query));
2918
+ return this.normalizedOptions().filter((option) => [option.label, option.tag, option.detail].some((text) => !!text && normalizeForSearch(text).includes(query)));
2729
2919
  }, /* @ts-ignore */
2730
2920
  ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
2731
2921
  triggerRef = viewChild.required('trigger', /* @ts-ignore */
@@ -2734,11 +2924,14 @@ class Select {
2734
2924
  ...(ngDevMode ? [{ debugName: "panelTemplate" }] : /* istanbul ignore next */ []));
2735
2925
  searchInputRef = viewChild('searchInput', /* @ts-ignore */
2736
2926
  ...(ngDevMode ? [{ debugName: "searchInputRef" }] : /* istanbul ignore next */ []));
2737
- overlay = new SelectOverlay(inject(Overlay), inject(ViewContainerRef), () => this.triggerRef(), () => (this.variant() === 'compact' ? COMPACT_PANEL_MIN_WIDTH : 0));
2927
+ panelRef = viewChild('panel', /* @ts-ignore */
2928
+ ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
2929
+ document = inject(DOCUMENT);
2930
+ overlay = new SelectOverlay(inject(Overlay), inject(ViewContainerRef), () => this.triggerRef(), () => (this.variant() === 'compact' ? COMPACT_PANEL_MIN_WIDTH : 0), () => (this.panelWidth() === 'content' ? 'content' : 'match'));
2738
2931
  constructor() {
2739
2932
  effect(() => {
2740
2933
  if (this.isOpen()) {
2741
- this.overlay.attach(this.panelTemplate(), () => this.isOpen.set(false));
2934
+ this.overlay.attach(this.panelTemplate(), () => this.close());
2742
2935
  }
2743
2936
  else {
2744
2937
  this.overlay.detach();
@@ -2750,9 +2943,20 @@ class Select {
2750
2943
  // dentro de un effect() (NG0203: pierde el contexto de inyección) — hay
2751
2944
  // que usar afterRenderEffect(), que sí es reactivo a signals y corre
2752
2945
  // después de que Angular actualizó el DOM del panel recién adjuntado.
2946
+ //
2947
+ // Sin buscador, el foco pasa a la opción activa (la seleccionada, ver
2948
+ // `open()`): `activeIndex` se lee `untracked` para que mover el índice
2949
+ // con el mouse (`mouseenter`) no robe el foco en cada render.
2753
2950
  afterRenderEffect(() => {
2754
- if (this.isOpen() && this.showSearch()) {
2951
+ if (!this.isOpen()) {
2952
+ return;
2953
+ }
2954
+ if (this.showSearch()) {
2755
2955
  this.searchInputRef()?.nativeElement.focus();
2956
+ return;
2957
+ }
2958
+ if (this.panelRef()) {
2959
+ untracked(() => this.focusOption(this.activeIndex()));
2756
2960
  }
2757
2961
  });
2758
2962
  inject(DestroyRef).onDestroy(() => this.overlay.dispose());
@@ -2764,16 +2968,135 @@ class Select {
2764
2968
  * panel está abierto, `keydownEvents()` nunca dispara.
2765
2969
  */
2766
2970
  onDocumentEscape() {
2767
- this.isOpen.set(false);
2971
+ this.close();
2768
2972
  }
2769
2973
  toggle() {
2770
2974
  if (this.disabled()) {
2771
2975
  return;
2772
2976
  }
2773
- this.isOpen.update((open) => !open);
2977
+ if (this.isOpen()) {
2978
+ this.close();
2979
+ }
2980
+ else {
2981
+ this.open();
2982
+ }
2774
2983
  }
2775
- select(option) {
2984
+ /** Abre el panel con la opción seleccionada como activa (o la primera). */
2985
+ open() {
2986
+ if (this.disabled() || this.isOpen()) {
2987
+ return;
2988
+ }
2989
+ const selectedIndex = this.filteredOptions().findIndex((option) => option.value === this.value());
2990
+ this.activeIndex.set(Math.max(selectedIndex, 0));
2991
+ this.isOpen.set(true);
2992
+ }
2993
+ /**
2994
+ * Cierra el panel. `restoreFocus` (default: solo si el foco está dentro
2995
+ * del panel) devuelve el foco al trigger ANTES de desmontar el panel —
2996
+ * si no, el elemento enfocado desaparece y el foco cae al `<body>`. Un
2997
+ * click afuera no lo restaura: el foco ya se fue a donde se hizo click.
2998
+ */
2999
+ close(restoreFocus = this.isFocusInPanel()) {
3000
+ if (!this.isOpen()) {
3001
+ return;
3002
+ }
3003
+ if (restoreFocus) {
3004
+ this.triggerRef().nativeElement.focus();
3005
+ }
2776
3006
  this.isOpen.set(false);
3007
+ }
3008
+ /** ArrowDown/ArrowUp con el panel cerrado lo abren (patrón listbox). */
3009
+ onTriggerKeydown(event) {
3010
+ if (this.isOpen() || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) {
3011
+ return;
3012
+ }
3013
+ event.preventDefault();
3014
+ this.open();
3015
+ }
3016
+ /**
3017
+ * Teclado con el foco en una opción del panel. Las flechas parten SIEMPRE
3018
+ * de la opción que recibió el evento (`index`), no de `activeIndex`: el
3019
+ * `mouseenter` mueve `activeIndex` sin mover el foco, y sin esto, con el
3020
+ * foco en la opción 1 y el mouse sobre la 4, ArrowDown saltaría a la 5.
3021
+ */
3022
+ onOptionKeydown(event, option, index) {
3023
+ this.activeIndex.set(index);
3024
+ switch (event.key) {
3025
+ case 'ArrowDown':
3026
+ event.preventDefault();
3027
+ this.moveActive(1);
3028
+ this.focusOption(this.activeIndex());
3029
+ break;
3030
+ case 'ArrowUp':
3031
+ event.preventDefault();
3032
+ this.moveActive(-1);
3033
+ this.focusOption(this.activeIndex());
3034
+ break;
3035
+ case 'Home':
3036
+ event.preventDefault();
3037
+ this.activeIndex.set(0);
3038
+ this.focusOption(0);
3039
+ this.scrollActiveIntoView();
3040
+ break;
3041
+ case 'End': {
3042
+ event.preventDefault();
3043
+ const last = this.filteredOptions().length - 1;
3044
+ this.activeIndex.set(last);
3045
+ this.focusOption(last);
3046
+ this.scrollActiveIntoView();
3047
+ break;
3048
+ }
3049
+ case 'Enter':
3050
+ case ' ':
3051
+ event.preventDefault();
3052
+ this.select(option);
3053
+ break;
3054
+ default:
3055
+ this.handleExitKeys(event);
3056
+ }
3057
+ }
3058
+ /** Teclas de salida del buscador: Escape y Tab, igual que desde una opción. */
3059
+ onSearchKeydown(event) {
3060
+ this.handleExitKeys(event);
3061
+ }
3062
+ /**
3063
+ * Escape cierra y devuelve el foco al trigger; `preventDefault` le avisa a
3064
+ * un contenedor que también escucha Escape (ej. `kf-drawer`) que la tecla
3065
+ * ya se consumió. Tab cierra y mueve el foco al trigger SIN
3066
+ * `preventDefault`: la tabulación nativa continúa desde el trigger, así
3067
+ * Tab/Shift+Tab salen del panel hacia el control siguiente/anterior.
3068
+ */
3069
+ handleExitKeys(event) {
3070
+ if (event.key === 'Escape') {
3071
+ event.preventDefault();
3072
+ this.close(true);
3073
+ }
3074
+ else if (event.key === 'Tab') {
3075
+ this.close(true);
3076
+ }
3077
+ }
3078
+ focusOption(index) {
3079
+ const options = this.panelRef()?.nativeElement.querySelectorAll('.kf-select__option');
3080
+ options?.[index]?.focus();
3081
+ }
3082
+ isFocusInPanel() {
3083
+ const active = this.document.activeElement;
3084
+ const panel = this.panelRef()?.nativeElement;
3085
+ return !!active && !!panel && panel.contains(active);
3086
+ }
3087
+ /** Id de cada opción, para `aria-activedescendant` del buscador. */
3088
+ optionId(index) {
3089
+ return `${this.listboxId}-option-${index}`;
3090
+ }
3091
+ /** Nombre accesible explícito solo para opciones con `tag`/`detail`, para separar las partes al leerlas. */
3092
+ optionAriaLabel(option) {
3093
+ if (!option.tag && !option.detail) {
3094
+ return null;
3095
+ }
3096
+ return [option.label, option.tag, option.detail].filter(Boolean).join(', ');
3097
+ }
3098
+ select(option) {
3099
+ this.close(true);
2777
3100
  if (option.value !== this.value()) {
2778
3101
  this.valueChange.emit(option.value);
2779
3102
  }
@@ -2795,6 +3118,17 @@ class Select {
2795
3118
  return;
2796
3119
  }
2797
3120
  this.activeIndex.update((current) => Math.min(Math.max(current + delta, 0), count - 1));
3121
+ this.scrollActiveIntoView();
3122
+ }
3123
+ /**
3124
+ * Con el foco en el buscador, la opción activa se marca con
3125
+ * `aria-activedescendant` pero no recibe foco, así que el navegador no la
3126
+ * desplaza a la vista: se hace a mano. (`scrollIntoView` puede no existir
3127
+ * en entornos sin layout.)
3128
+ */
3129
+ scrollActiveIntoView() {
3130
+ const option = this.panelRef()?.nativeElement.querySelectorAll('.kf-select__option')[this.activeIndex()];
3131
+ option?.scrollIntoView?.({ block: 'nearest' });
2798
3132
  }
2799
3133
  selectActive() {
2800
3134
  const option = this.filteredOptions()[this.activeIndex()];
@@ -2803,17 +3137,17 @@ class Select {
2803
3137
  }
2804
3138
  }
2805
3139
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
2806
- 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 } }, 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 }], 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 [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\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 class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-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(); selectActive()\"\n />\n }\n <ul class=\"kf-select__list\" [id]=\"listboxId\" role=\"listbox\" [attr.aria-labelledby]=\"triggerId\">\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"option.value === value()\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"select(option)\"\n (keydown.space)=\"$event.preventDefault(); select(option)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n {{ option.label }}\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)}: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__value--placeholder{color:var(--kf-color-text-muted)}.kf-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-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);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.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__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"] }] });
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"] }] });
2807
3141
  }
2808
3142
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, decorators: [{
2809
3143
  type: Component,
2810
- args: [{ selector: 'kf-select', imports: [FieldLabel], host: {
3144
+ args: [{ selector: 'kf-select', imports: [Badge, FieldLabel, Icon], host: {
2811
3145
  '[class.kf-select--compact]': "variant() === 'compact'",
2812
3146
  '[class.kf-select--width-sm]': "width() === 'sm'",
2813
3147
  '[class.kf-select--width-lg]': "width() === 'lg'",
2814
3148
  '[class.kf-select--width-full]': "width() === 'full'",
2815
- }, 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 [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\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 class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-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(); selectActive()\"\n />\n }\n <ul class=\"kf-select__list\" [id]=\"listboxId\" role=\"listbox\" [attr.aria-labelledby]=\"triggerId\">\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"option.value === value()\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"select(option)\"\n (keydown.space)=\"$event.preventDefault(); select(option)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n {{ option.label }}\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)}: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__value--placeholder{color:var(--kf-color-text-muted)}.kf-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-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);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.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__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
2816
- }], 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 }] }], 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 }] }], onDocumentEscape: [{
3149
+ }, 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: [{
2817
3151
  type: HostListener,
2818
3152
  args: ['document:keydown.escape']
2819
3153
  }] } });
@@ -2890,11 +3224,11 @@ class Pagination {
2890
3224
  this.pageChange.emit(clamped);
2891
3225
  }
2892
3226
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Pagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
2893
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Pagination, isStandalone: true, selector: "kf-pagination", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, pages: { classPropertyName: "pages", publicName: "pages", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSize: "pageSizeChange" }, ngImport: i0, template: "<div class=\"kf-pagination\">\n <div class=\"kf-pagination__info kf-caption-text\">\n <ng-content />\n </div>\n\n <div class=\"kf-pagination__controls\">\n @if (pageSizeOptions().length > 0) {\n <div class=\"kf-pagination__size\">\n <span class=\"kf-pagination__size-label kf-caption-text\">Filas</span>\n <kf-select\n variant=\"compact\"\n [options]=\"pageSizeSelectOptions()\"\n [value]=\"pageSizeValue()\"\n (valueChange)=\"setPageSize($event)\"\n />\n </div>\n @if (hasNav()) {\n <div class=\"kf-pagination__divider\" aria-hidden=\"true\"></div>\n }\n }\n\n @if (hasNav()) {\n <nav class=\"kf-pagination__nav\" aria-label=\"Paginaci\u00F3n\">\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() <= 1\"\n aria-label=\"P\u00E1gina anterior\"\n (click)=\"goTo(page() - 1)\"\n >\n \u2039\n </button>\n\n @for (item of items(); track $index) {\n @if (item.type === 'page') {\n <button\n type=\"button\"\n class=\"kf-pagination__page\"\n [class.kf-pagination__page--active]=\"item.page === page()\"\n [attr.aria-current]=\"item.page === page() ? 'page' : null\"\n [attr.aria-label]=\"'Ir a la p\u00E1gina ' + item.page\"\n (click)=\"goTo(item.page)\"\n >\n {{ item.page }}\n </button>\n } @else {\n <span class=\"kf-pagination__ellipsis\" aria-hidden=\"true\">\u2026</span>\n }\n }\n\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() >= pages()\"\n aria-label=\"P\u00E1gina siguiente\"\n (click)=\"goTo(page() + 1)\"\n >\n \u203A\n </button>\n </nav>\n }\n </div>\n</div>\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:block;font-family:var(--kf-font-body)}.kf-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);flex-wrap:wrap}.kf-pagination__info:empty{display:none}.kf-pagination__controls{display:flex;align-items:center;gap:var(--kf-space-md)}.kf-pagination__size{display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-pagination__size-label{white-space:nowrap}.kf-pagination__divider{width:1px;height:1.25rem;background:var(--kf-color-border)}.kf-pagination__nav{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-pagination__control,.kf-pagination__page{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;padding:0 var(--kf-space-xs);font:inherit;font-size:.85rem;color:var(--kf-color-text);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-pagination__control:hover:not(:disabled),.kf-pagination__page:hover:not(.kf-pagination__page--active){background:var(--kf-color-surface);border-color:var(--kf-color-border-strong)}.kf-pagination__control:focus-visible,.kf-pagination__page:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-pagination__control:disabled{opacity:.5;cursor:not-allowed}.kf-pagination__page--active{color:var(--kf-color-white);background:var(--kf-color-primary);border-color:var(--kf-color-primary);cursor:default}.kf-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold"], outputs: ["valueChange"] }] });
3227
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Pagination, isStandalone: true, selector: "kf-pagination", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, pages: { classPropertyName: "pages", publicName: "pages", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageChange: "pageChange", pageSize: "pageSizeChange" }, ngImport: i0, template: "<div class=\"kf-pagination\">\n <div class=\"kf-pagination__info kf-caption-text\" aria-live=\"polite\">\n <ng-content />\n </div>\n\n <div class=\"kf-pagination__controls\">\n @if (pageSizeOptions().length > 0) {\n <div class=\"kf-pagination__size\">\n <span class=\"kf-pagination__size-label kf-caption-text\">Filas</span>\n <kf-select\n variant=\"compact\"\n ariaLabel=\"Filas por p\u00E1gina\"\n [options]=\"pageSizeSelectOptions()\"\n [value]=\"pageSizeValue()\"\n (valueChange)=\"setPageSize($event)\"\n />\n </div>\n @if (hasNav()) {\n <div class=\"kf-pagination__divider\" aria-hidden=\"true\"></div>\n }\n }\n\n @if (hasNav()) {\n <nav class=\"kf-pagination__nav\" aria-label=\"Paginaci\u00F3n\">\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() <= 1\"\n aria-label=\"P\u00E1gina anterior\"\n (click)=\"goTo(page() - 1)\"\n >\n \u2039\n </button>\n\n @for (item of items(); track $index) {\n @if (item.type === 'page') {\n <button\n type=\"button\"\n class=\"kf-pagination__page\"\n [class.kf-pagination__page--active]=\"item.page === page()\"\n [attr.aria-current]=\"item.page === page() ? 'page' : null\"\n [attr.aria-label]=\"'Ir a la p\u00E1gina ' + item.page\"\n (click)=\"goTo(item.page)\"\n >\n {{ item.page }}\n </button>\n } @else {\n <span class=\"kf-pagination__ellipsis\" aria-hidden=\"true\">\u2026</span>\n }\n }\n\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() >= pages()\"\n aria-label=\"P\u00E1gina siguiente\"\n (click)=\"goTo(page() + 1)\"\n >\n \u203A\n </button>\n </nav>\n }\n </div>\n</div>\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:block;font-family:var(--kf-font-body)}.kf-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);flex-wrap:wrap}.kf-pagination__info:empty{display:none}.kf-pagination__controls{display:flex;flex-wrap:wrap;align-items:center;gap:var(--kf-space-sm) var(--kf-space-md)}.kf-pagination__size{display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-pagination__size-label{white-space:nowrap}.kf-pagination__divider{width:1px;height:1.25rem;background:var(--kf-color-border)}.kf-pagination__nav{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-pagination__control,.kf-pagination__page{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;padding:0 var(--kf-space-xs);font:inherit;font-size:.85rem;color:var(--kf-color-text);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-pagination__control:hover:not(:disabled),.kf-pagination__page:hover:not(.kf-pagination__page--active){background:var(--kf-color-surface);border-color:var(--kf-color-border-strong)}.kf-pagination__control:focus-visible,.kf-pagination__page:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-pagination__control:disabled{opacity:.5;cursor:not-allowed}.kf-pagination__page--active{color:var(--kf-color-white);background:var(--kf-color-primary);border-color:var(--kf-color-primary);cursor:default}.kf-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;color:var(--kf-color-text-muted)}@media(max-width:480px){.kf-pagination__controls{width:100%}.kf-pagination__divider{display:none}}\n"], dependencies: [{ 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"] }] });
2894
3228
  }
2895
3229
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Pagination, decorators: [{
2896
3230
  type: Component,
2897
- args: [{ selector: 'kf-pagination', imports: [Select], template: "<div class=\"kf-pagination\">\n <div class=\"kf-pagination__info kf-caption-text\">\n <ng-content />\n </div>\n\n <div class=\"kf-pagination__controls\">\n @if (pageSizeOptions().length > 0) {\n <div class=\"kf-pagination__size\">\n <span class=\"kf-pagination__size-label kf-caption-text\">Filas</span>\n <kf-select\n variant=\"compact\"\n [options]=\"pageSizeSelectOptions()\"\n [value]=\"pageSizeValue()\"\n (valueChange)=\"setPageSize($event)\"\n />\n </div>\n @if (hasNav()) {\n <div class=\"kf-pagination__divider\" aria-hidden=\"true\"></div>\n }\n }\n\n @if (hasNav()) {\n <nav class=\"kf-pagination__nav\" aria-label=\"Paginaci\u00F3n\">\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() <= 1\"\n aria-label=\"P\u00E1gina anterior\"\n (click)=\"goTo(page() - 1)\"\n >\n \u2039\n </button>\n\n @for (item of items(); track $index) {\n @if (item.type === 'page') {\n <button\n type=\"button\"\n class=\"kf-pagination__page\"\n [class.kf-pagination__page--active]=\"item.page === page()\"\n [attr.aria-current]=\"item.page === page() ? 'page' : null\"\n [attr.aria-label]=\"'Ir a la p\u00E1gina ' + item.page\"\n (click)=\"goTo(item.page)\"\n >\n {{ item.page }}\n </button>\n } @else {\n <span class=\"kf-pagination__ellipsis\" aria-hidden=\"true\">\u2026</span>\n }\n }\n\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() >= pages()\"\n aria-label=\"P\u00E1gina siguiente\"\n (click)=\"goTo(page() + 1)\"\n >\n \u203A\n </button>\n </nav>\n }\n </div>\n</div>\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:block;font-family:var(--kf-font-body)}.kf-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);flex-wrap:wrap}.kf-pagination__info:empty{display:none}.kf-pagination__controls{display:flex;align-items:center;gap:var(--kf-space-md)}.kf-pagination__size{display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-pagination__size-label{white-space:nowrap}.kf-pagination__divider{width:1px;height:1.25rem;background:var(--kf-color-border)}.kf-pagination__nav{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-pagination__control,.kf-pagination__page{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;padding:0 var(--kf-space-xs);font:inherit;font-size:.85rem;color:var(--kf-color-text);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-pagination__control:hover:not(:disabled),.kf-pagination__page:hover:not(.kf-pagination__page--active){background:var(--kf-color-surface);border-color:var(--kf-color-border-strong)}.kf-pagination__control:focus-visible,.kf-pagination__page:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-pagination__control:disabled{opacity:.5;cursor:not-allowed}.kf-pagination__page--active{color:var(--kf-color-white);background:var(--kf-color-primary);border-color:var(--kf-color-primary);cursor:default}.kf-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;color:var(--kf-color-text-muted)}\n"] }]
3231
+ args: [{ selector: 'kf-pagination', imports: [Select], template: "<div class=\"kf-pagination\">\n <div class=\"kf-pagination__info kf-caption-text\" aria-live=\"polite\">\n <ng-content />\n </div>\n\n <div class=\"kf-pagination__controls\">\n @if (pageSizeOptions().length > 0) {\n <div class=\"kf-pagination__size\">\n <span class=\"kf-pagination__size-label kf-caption-text\">Filas</span>\n <kf-select\n variant=\"compact\"\n ariaLabel=\"Filas por p\u00E1gina\"\n [options]=\"pageSizeSelectOptions()\"\n [value]=\"pageSizeValue()\"\n (valueChange)=\"setPageSize($event)\"\n />\n </div>\n @if (hasNav()) {\n <div class=\"kf-pagination__divider\" aria-hidden=\"true\"></div>\n }\n }\n\n @if (hasNav()) {\n <nav class=\"kf-pagination__nav\" aria-label=\"Paginaci\u00F3n\">\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() <= 1\"\n aria-label=\"P\u00E1gina anterior\"\n (click)=\"goTo(page() - 1)\"\n >\n \u2039\n </button>\n\n @for (item of items(); track $index) {\n @if (item.type === 'page') {\n <button\n type=\"button\"\n class=\"kf-pagination__page\"\n [class.kf-pagination__page--active]=\"item.page === page()\"\n [attr.aria-current]=\"item.page === page() ? 'page' : null\"\n [attr.aria-label]=\"'Ir a la p\u00E1gina ' + item.page\"\n (click)=\"goTo(item.page)\"\n >\n {{ item.page }}\n </button>\n } @else {\n <span class=\"kf-pagination__ellipsis\" aria-hidden=\"true\">\u2026</span>\n }\n }\n\n <button\n type=\"button\"\n class=\"kf-pagination__control\"\n [disabled]=\"page() >= pages()\"\n aria-label=\"P\u00E1gina siguiente\"\n (click)=\"goTo(page() + 1)\"\n >\n \u203A\n </button>\n </nav>\n }\n </div>\n</div>\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:block;font-family:var(--kf-font-body)}.kf-pagination{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);flex-wrap:wrap}.kf-pagination__info:empty{display:none}.kf-pagination__controls{display:flex;flex-wrap:wrap;align-items:center;gap:var(--kf-space-sm) var(--kf-space-md)}.kf-pagination__size{display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-pagination__size-label{white-space:nowrap}.kf-pagination__divider{width:1px;height:1.25rem;background:var(--kf-color-border)}.kf-pagination__nav{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-pagination__control,.kf-pagination__page{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;padding:0 var(--kf-space-xs);font:inherit;font-size:.85rem;color:var(--kf-color-text);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-pagination__control:hover:not(:disabled),.kf-pagination__page:hover:not(.kf-pagination__page--active){background:var(--kf-color-surface);border-color:var(--kf-color-border-strong)}.kf-pagination__control:focus-visible,.kf-pagination__page:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-pagination__control:disabled{opacity:.5;cursor:not-allowed}.kf-pagination__page--active{color:var(--kf-color-white);background:var(--kf-color-primary);border-color:var(--kf-color-primary);cursor:default}.kf-pagination__ellipsis{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;height:2rem;color:var(--kf-color-text-muted)}@media(max-width:480px){.kf-pagination__controls{width:100%}.kf-pagination__divider{display:none}}\n"] }]
2898
3232
  }], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }], pages: [{ type: i0.Input, args: [{ isSignal: true, alias: "pages", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }] } });
2899
3233
  /**
2900
3234
  * Construye la lista de items visibles con el algoritmo de elipsis estándar:
@@ -2939,11 +3273,28 @@ function buildPaginationItems(current, total) {
2939
3273
  * navegación con flechas izquierda/derecha, igual que un <input type="radio">
2940
3274
  * nativo agrupado.
2941
3275
  *
3276
+ * Con `iconOnly` cada opción muestra solo su ícono (`list` / `grid`); el
3277
+ * nombre accesible y el tooltip salen de `tableLabel` / `cardsLabel`. El
3278
+ * objetivo táctil mide 32px en escritorio y 44px en pantallas táctiles o de
3279
+ * hasta 640px.
3280
+ *
2942
3281
  * <kf-view-toggle [view]="view" (viewChange)="view = $event" />
3282
+ * <kf-view-toggle iconOnly [view]="view" (viewChange)="view = $event" />
2943
3283
  */
2944
3284
  class ViewToggle {
2945
3285
  view = input.required(/* @ts-ignore */
2946
3286
  ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
3287
+ /** Solo íconos (el rótulo queda como nombre accesible y `title`). */
3288
+ iconOnly = input(false, { ...(ngDevMode ? { debugName: "iconOnly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3289
+ /** Rótulo de la opción de tabla. */
3290
+ tableLabel = input('Tabla', /* @ts-ignore */
3291
+ ...(ngDevMode ? [{ debugName: "tableLabel" }] : /* istanbul ignore next */ []));
3292
+ /** Rótulo de la opción de tarjetas. */
3293
+ cardsLabel = input('Tarjetas', /* @ts-ignore */
3294
+ ...(ngDevMode ? [{ debugName: "cardsLabel" }] : /* istanbul ignore next */ []));
3295
+ /** Nombre accesible del grupo. */
3296
+ ariaLabel = input('Alternar tipo de vista', /* @ts-ignore */
3297
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
2947
3298
  viewChange = output();
2948
3299
  select(next) {
2949
3300
  if (next === this.view()) {
@@ -2965,13 +3316,17 @@ class ViewToggle {
2965
3316
  nextButton?.focus();
2966
3317
  }
2967
3318
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ViewToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
2968
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: ViewToggle, isStandalone: true, selector: "kf-view-toggle", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { viewChange: "viewChange" }, ngImport: i0, template: "<div class=\"kf-view-toggle\" role=\"radiogroup\" aria-label=\"Alternar tipo de vista\">\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--active]=\"view() === 'table'\"\n [attr.aria-checked]=\"view() === 'table'\"\n [attr.tabindex]=\"view() === 'table' ? 0 : -1\"\n (click)=\"select('table')\"\n (keydown)=\"onKeydown($event)\"\n >\n Tabla\n </button>\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--active]=\"view() === 'cards'\"\n [attr.aria-checked]=\"view() === 'cards'\"\n [attr.tabindex]=\"view() === 'cards' ? 0 : -1\"\n (click)=\"select('cards')\"\n (keydown)=\"onKeydown($event)\"\n >\n Tarjetas\n </button>\n</div>\n", styles: [":host{display:inline-block;font-family:var(--kf-font-body)}.kf-view-toggle{display:inline-flex;align-items:stretch;gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-view-toggle__option{font:inherit;font-size:.85rem;line-height:1;color:var(--kf-color-text-muted);background:transparent;border:none;border-radius:var(--kf-radius-xs);padding:var(--kf-space-xs) var(--kf-space-sm);cursor:pointer;transition:background .14s,color .14s,box-shadow .14s}.kf-view-toggle__option:hover:not(.kf-view-toggle__option--active){color:var(--kf-color-text)}.kf-view-toggle__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-view-toggle__option--active{color:var(--kf-color-ink);background:var(--kf-color-white);box-shadow:var(--kf-shadow-xs)}\n"] });
3319
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ViewToggle, isStandalone: true, selector: "kf-view-toggle", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: true, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null }, tableLabel: { classPropertyName: "tableLabel", publicName: "tableLabel", isSignal: true, isRequired: false, transformFunction: null }, cardsLabel: { classPropertyName: "cardsLabel", publicName: "cardsLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { viewChange: "viewChange" }, ngImport: i0, template: "<div class=\"kf-view-toggle\" role=\"radiogroup\" [attr.aria-label]=\"ariaLabel()\">\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--icon]=\"iconOnly()\"\n [class.kf-view-toggle__option--active]=\"view() === 'table'\"\n [attr.aria-checked]=\"view() === 'table'\"\n [attr.aria-label]=\"iconOnly() ? tableLabel() : null\"\n [attr.title]=\"iconOnly() ? tableLabel() : null\"\n [attr.tabindex]=\"view() === 'table' ? 0 : -1\"\n (click)=\"select('table')\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (iconOnly()) {\n <kf-icon name=\"list\" size=\"md\" />\n } @else {\n {{ tableLabel() }}\n }\n </button>\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--icon]=\"iconOnly()\"\n [class.kf-view-toggle__option--active]=\"view() === 'cards'\"\n [attr.aria-checked]=\"view() === 'cards'\"\n [attr.aria-label]=\"iconOnly() ? cardsLabel() : null\"\n [attr.title]=\"iconOnly() ? cardsLabel() : null\"\n [attr.tabindex]=\"view() === 'cards' ? 0 : -1\"\n (click)=\"select('cards')\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (iconOnly()) {\n <kf-icon name=\"layout-grid\" size=\"md\" />\n } @else {\n {{ cardsLabel() }}\n }\n </button>\n</div>\n", styles: [":host{display:inline-block;font-family:var(--kf-font-body)}.kf-view-toggle{display:inline-flex;align-items:stretch;gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-view-toggle__option{font:inherit;font-size:.85rem;line-height:1;color:var(--kf-color-text-muted);background:transparent;border:none;border-radius:var(--kf-radius-xs);padding:var(--kf-space-xs) var(--kf-space-sm);cursor:pointer;transition:background .14s,color .14s,box-shadow .14s}.kf-view-toggle__option:hover:not(.kf-view-toggle__option--active){color:var(--kf-color-text)}.kf-view-toggle__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-view-toggle__option--active{color:var(--kf-color-ink);background:var(--kf-color-white);box-shadow:var(--kf-shadow-xs)}.kf-view-toggle__option--icon{display:inline-flex;align-items:center;justify-content:center;min-width:32px;min-height:32px;padding:var(--kf-space-xs)}@media(max-width:640px),(pointer:coarse){.kf-view-toggle__option--icon{min-width:44px;min-height:44px}}@media(prefers-reduced-motion:reduce){.kf-view-toggle__option{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
2969
3320
  }
2970
3321
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ViewToggle, decorators: [{
2971
3322
  type: Component,
2972
- args: [{ selector: 'kf-view-toggle', template: "<div class=\"kf-view-toggle\" role=\"radiogroup\" aria-label=\"Alternar tipo de vista\">\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--active]=\"view() === 'table'\"\n [attr.aria-checked]=\"view() === 'table'\"\n [attr.tabindex]=\"view() === 'table' ? 0 : -1\"\n (click)=\"select('table')\"\n (keydown)=\"onKeydown($event)\"\n >\n Tabla\n </button>\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--active]=\"view() === 'cards'\"\n [attr.aria-checked]=\"view() === 'cards'\"\n [attr.tabindex]=\"view() === 'cards' ? 0 : -1\"\n (click)=\"select('cards')\"\n (keydown)=\"onKeydown($event)\"\n >\n Tarjetas\n </button>\n</div>\n", styles: [":host{display:inline-block;font-family:var(--kf-font-body)}.kf-view-toggle{display:inline-flex;align-items:stretch;gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-view-toggle__option{font:inherit;font-size:.85rem;line-height:1;color:var(--kf-color-text-muted);background:transparent;border:none;border-radius:var(--kf-radius-xs);padding:var(--kf-space-xs) var(--kf-space-sm);cursor:pointer;transition:background .14s,color .14s,box-shadow .14s}.kf-view-toggle__option:hover:not(.kf-view-toggle__option--active){color:var(--kf-color-text)}.kf-view-toggle__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-view-toggle__option--active{color:var(--kf-color-ink);background:var(--kf-color-white);box-shadow:var(--kf-shadow-xs)}\n"] }]
2973
- }], propDecorators: { view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: true }] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }] } });
3323
+ args: [{ selector: 'kf-view-toggle', imports: [Icon], template: "<div class=\"kf-view-toggle\" role=\"radiogroup\" [attr.aria-label]=\"ariaLabel()\">\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--icon]=\"iconOnly()\"\n [class.kf-view-toggle__option--active]=\"view() === 'table'\"\n [attr.aria-checked]=\"view() === 'table'\"\n [attr.aria-label]=\"iconOnly() ? tableLabel() : null\"\n [attr.title]=\"iconOnly() ? tableLabel() : null\"\n [attr.tabindex]=\"view() === 'table' ? 0 : -1\"\n (click)=\"select('table')\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (iconOnly()) {\n <kf-icon name=\"list\" size=\"md\" />\n } @else {\n {{ tableLabel() }}\n }\n </button>\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-view-toggle__option\"\n [class.kf-view-toggle__option--icon]=\"iconOnly()\"\n [class.kf-view-toggle__option--active]=\"view() === 'cards'\"\n [attr.aria-checked]=\"view() === 'cards'\"\n [attr.aria-label]=\"iconOnly() ? cardsLabel() : null\"\n [attr.title]=\"iconOnly() ? cardsLabel() : null\"\n [attr.tabindex]=\"view() === 'cards' ? 0 : -1\"\n (click)=\"select('cards')\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (iconOnly()) {\n <kf-icon name=\"layout-grid\" size=\"md\" />\n } @else {\n {{ cardsLabel() }}\n }\n </button>\n</div>\n", styles: [":host{display:inline-block;font-family:var(--kf-font-body)}.kf-view-toggle{display:inline-flex;align-items:stretch;gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-view-toggle__option{font:inherit;font-size:.85rem;line-height:1;color:var(--kf-color-text-muted);background:transparent;border:none;border-radius:var(--kf-radius-xs);padding:var(--kf-space-xs) var(--kf-space-sm);cursor:pointer;transition:background .14s,color .14s,box-shadow .14s}.kf-view-toggle__option:hover:not(.kf-view-toggle__option--active){color:var(--kf-color-text)}.kf-view-toggle__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-view-toggle__option--active{color:var(--kf-color-ink);background:var(--kf-color-white);box-shadow:var(--kf-shadow-xs)}.kf-view-toggle__option--icon{display:inline-flex;align-items:center;justify-content:center;min-width:32px;min-height:32px;padding:var(--kf-space-xs)}@media(max-width:640px),(pointer:coarse){.kf-view-toggle__option--icon{min-width:44px;min-height:44px}}@media(prefers-reduced-motion:reduce){.kf-view-toggle__option{transition:none}}\n"] }]
3324
+ }], propDecorators: { view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: true }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], tableLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableLabel", required: false }] }], cardsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardsLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }] } });
2974
3325
 
3326
+ /** Controles cuyo clic/Enter NO debe activar además la fila o tarjeta que los contiene. */
3327
+ 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
+ /** Piso de ancho de una columna `grow` que no declara `minWidth` ni `width`. */
3329
+ const GROW_MIN_WIDTH = '8rem';
2975
3330
  /** Opciones de "filas por página" estándar del portal — 10 como default
2976
3331
  * cubre bien tanto un dataset chico (ej. Beneficios) como uno con volumen
2977
3332
  * real (ej. 30+ cuotas en Billing), sin sentirse vacío ni insuficiente; 25
@@ -3040,6 +3395,12 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
3040
3395
  * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" itemLabel="beneficios" />
3041
3396
  * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" [pageSizeOptions]="[]" />
3042
3397
  *
3398
+ * La página es `[(page)]` (el consumidor puede llevarla a la URL) y se
3399
+ * corrige sola si queda fuera de rango. Por defecto un cambio de `rows` la
3400
+ * devuelve a 1; con `keepPageOnRowsChange` la conserva (el consumidor decide
3401
+ * cuándo volver a 1). Un `pageSize` nuevo del consumidor pisa el elegido en
3402
+ * el selector, y `pageSizeChange` avisa cuando el usuario lo cambia.
3403
+ *
3043
3404
  * Ordenamiento por columna: cada `DataTableColumn` con `sortable: true`
3044
3405
  * vuelve su header clickeable, con el mismo formato `key:direction` que
3045
3406
  * usa `sort` de `kf-quote-picker` (two-way manual, no `model()` — el
@@ -3053,6 +3414,55 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
3053
3414
  *
3054
3415
  * Densidad: `density="compact"` reduce el padding vertical de cada fila,
3055
3416
  * para priorizar cantidad de filas visibles sobre aire entre ellas.
3417
+ *
3418
+ * Listados largos con textos largos (ej. cotizaciones con el modelo del
3419
+ * vehículo): la columna de texto largo lleva `truncate` (elipsis + `title`),
3420
+ * las de dato corto `width`/`align: 'end'`, y la de acciones solo ícono
3421
+ * `width` angosto + `hideLabel` + `sticky: 'right'`. Así la acción queda
3422
+ * siempre a la vista sin scroll horizontal:
3423
+ *
3424
+ * columns = [
3425
+ * { key: 'id', label: 'Cotización', width: '9rem' },
3426
+ * { key: 'model', label: 'Vehículo', truncate: true, minWidth: '10rem' },
3427
+ * { key: 'amount', label: 'Monto', align: 'end', width: '8rem' },
3428
+ * { key: 'open', label: 'Abrir', hideLabel: true, width: '56px', sticky: 'right' },
3429
+ * ];
3430
+ * <kf-data-table [columns]="columns" [rows]="quotes" [rowClickable]="true"
3431
+ * (rowClick)="open($event.row)" [cellTemplates]="{ open: openCell }" />
3432
+ *
3433
+ * Reparto de anchos: con `grow` en alguna columna, esa(s) se reparten TODO el
3434
+ * espacio sobrante según su peso, las de `width` lo mantienen y las que no
3435
+ * traen `width` ni `grow` se ajustan a su contenido (con `maxWidth` como
3436
+ * techo). Así una columna de dato corto (estado, precio, fecha) no acapara
3437
+ * espacio y el texto largo (el vehículo) lo aprovecha:
3438
+ *
3439
+ * columns = [
3440
+ * { key: 'id', label: 'Cotización', width: '8.5rem', mono: true },
3441
+ * { key: 'model', label: 'Vehículo', grow: 1, truncate: true },
3442
+ * { key: 'issuedAt', label: 'Emisión' },
3443
+ * { key: 'price', label: 'Precio', align: 'end', emphasis: true },
3444
+ * { key: 'status', label: 'Estado', truncate: true, maxWidth: '14rem' },
3445
+ * ];
3446
+ *
3447
+ * Listado completo con vista tarjetas (ej. cotizaciones): la vista es
3448
+ * two-way (`[(view)]`) y el toggle interno se oculta con
3449
+ * `[showViewToggle]="false"` cuando el consumidor lo pone en su barra. En
3450
+ * tarjetas: título (`mono`) y estado en una fila, el campo principal a
3451
+ * ancho completo en 2 líneas (`cardClamp: 2`), y fecha/monto en una fila
3452
+ * inferior (`cardRole: 'meta'`, el monto con `emphasis`). Las tarjetas de
3453
+ * una fila miden lo mismo; `cardMinWidth` fija el ancho mínimo. Con
3454
+ * `rowClickable` la tarjeta entera es un botón (Enter/Espacio):
3455
+ *
3456
+ * columns = [
3457
+ * { key: 'id', label: 'Cotización', cardRole: 'title', mono: true },
3458
+ * { key: 'status', label: 'Estado', cardRole: 'status' },
3459
+ * { key: 'model', label: 'Vehículo', truncate: true, cardClamp: 2 },
3460
+ * { key: 'date', label: 'Fecha', cardRole: 'meta', width: '7rem' },
3461
+ * { key: 'amount', label: 'Monto', cardRole: 'meta', align: 'end', emphasis: true },
3462
+ * ];
3463
+ * <kf-data-table [columns]="columns" [rows]="quotes" cardable [(view)]="view"
3464
+ * [showViewToggle]="false" cardMinWidth="300px" [rowClickable]="true"
3465
+ * (rowClick)="open($event.row)" />
3056
3466
  */
3057
3467
  class DataTable {
3058
3468
  columns = input.required(/* @ts-ignore */
@@ -3078,6 +3488,14 @@ class DataTable {
3078
3488
  ...(ngDevMode ? [{ debugName: "canScrollLeft" }] : /* istanbul ignore next */ []));
3079
3489
  canScrollRight = signal(false, /* @ts-ignore */
3080
3490
  ...(ngDevMode ? [{ debugName: "canScrollRight" }] : /* istanbul ignore next */ []));
3491
+ /**
3492
+ * Hay contenido oculto por scroll horizontal. Solo entonces el contenedor
3493
+ * es una región nombrada y enfocable (`tabindex="0"`, `role="region"`):
3494
+ * sin foco, quien navega con teclado no puede desplazarlo (WCAG 2.1.1).
3495
+ * Una tabla que entra completa no suma una parada de Tab vacía.
3496
+ */
3497
+ hasOverflow = signal(false, /* @ts-ignore */
3498
+ ...(ngDevMode ? [{ debugName: "hasOverflow" }] : /* istanbul ignore next */ []));
3081
3499
  constructor() {
3082
3500
  afterNextRender(() => {
3083
3501
  const el = this.scrollEl()?.nativeElement;
@@ -3103,15 +3521,45 @@ class DataTable {
3103
3521
  this.measureStickyOffsets();
3104
3522
  });
3105
3523
  // `rows()` cambia de identidad cuando el consumidor filtra el dataset
3106
- // base (ej. cambiar de tab) — sin esto, `page` queda en un número que
3107
- // puede no existir más para el nuevo conjunto (ej. estar en la página 2
3108
- // de "Todos" y pasar a "Bonos", que solo tiene 1 página), dejando la
3109
- // tabla en blanco porque `displayedRows` corta un rango vacío. Reacciona
3110
- // solo a `rows()`, no a `page()`/`sort()`: cambiar de página o de orden
3111
- // NUNCA debe resetear la página por sí mismo.
3524
+ // base (ej. cambiar de tab): la página vuelve a 1, salvo con
3525
+ // `keepPageOnRowsChange`, donde el consumidor lleva la página (ej. en la
3526
+ // URL) y solo se corrige si queda fuera de rango (ver abajo). La primera
3527
+ // ejecución no cuenta: respeta la página inicial de `[(page)]`. Reacciona
3528
+ // solo a `rows()` y a la bandera, no a `page()`/`sort()`.
3529
+ let firstRowsRun = true;
3112
3530
  effect(() => {
3113
3531
  this.rows();
3114
- this.page.set(1);
3532
+ const keep = this.keepPageOnRowsChange();
3533
+ if (firstRowsRun) {
3534
+ firstRowsRun = false;
3535
+ return;
3536
+ }
3537
+ if (!keep) {
3538
+ this.page.set(1);
3539
+ }
3540
+ });
3541
+ // Una página fuera de rango (ej. `?page=9` con 3 páginas, o un filtro que
3542
+ // deja menos filas) se corrige a la más cercana válida y se notifica por
3543
+ // `pageChange`. Sin filas o cargando no se corrige: el total aún no es
3544
+ // real y se perdería la página pedida.
3545
+ effect(() => {
3546
+ if (this.loading() || this.rows().length === 0 || !this.effectivePageSize()) {
3547
+ return;
3548
+ }
3549
+ const total = this.totalPages();
3550
+ const current = this.page();
3551
+ if (current > total) {
3552
+ this.page.set(total);
3553
+ }
3554
+ else if (current < 1) {
3555
+ this.page.set(1);
3556
+ }
3557
+ });
3558
+ // Un `pageSize` nuevo desde el consumidor (ej. leído de la URL) manda
3559
+ // sobre el tamaño que el usuario eligió antes en el selector.
3560
+ effect(() => {
3561
+ this.pageSize();
3562
+ untracked(() => this.internalPageSize.set(null));
3115
3563
  });
3116
3564
  }
3117
3565
  updateScrollState(el) {
@@ -3120,6 +3568,7 @@ class DataTable {
3120
3568
  // al final (o nunca tuvo overflow real).
3121
3569
  this.canScrollLeft.set(el.scrollLeft > 1);
3122
3570
  this.canScrollRight.set(el.scrollWidth - el.clientWidth - el.scrollLeft > 1);
3571
+ this.hasOverflow.set(el.scrollWidth - el.clientWidth > 1);
3123
3572
  }
3124
3573
  /** Default: índice de la fila dentro de `rows`. */
3125
3574
  rowKey = input(/* @ts-ignore */
@@ -3128,10 +3577,16 @@ class DataTable {
3128
3577
  flush = input(false, /* @ts-ignore */
3129
3578
  ...(ngDevMode ? [{ debugName: "flush" }] : /* istanbul ignore next */ []));
3130
3579
  /** Habilita el toggle de vista tabla/cards. */
3131
- cardable = input(false, /* @ts-ignore */
3132
- ...(ngDevMode ? [{ debugName: "cardable" }] : /* istanbul ignore next */ []));
3580
+ cardable = input(false, { ...(ngDevMode ? { debugName: "cardable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3133
3581
  defaultView = input('table', /* @ts-ignore */
3134
3582
  ...(ngDevMode ? [{ debugName: "defaultView" }] : /* istanbul ignore next */ []));
3583
+ /** Muestra el toggle tabla/tarjetas interno (con `cardable`). En `false`,
3584
+ * el consumidor controla la vista con `[(view)]` desde su propio control. */
3585
+ showViewToggle = input(true, { ...(ngDevMode ? { debugName: "showViewToggle" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3586
+ /** Ancho mínimo de cada tarjeta en la grilla (default `220px`, histórico).
3587
+ * Nunca supera el ancho disponible: en pantallas angostas, una columna. */
3588
+ cardMinWidth = input('220px', /* @ts-ignore */
3589
+ ...(ngDevMode ? [{ debugName: "cardMinWidth" }] : /* istanbul ignore next */ []));
3135
3590
  /**
3136
3591
  * Formateo simple de celda como texto: `(row) => string`. Para marcado
3137
3592
  * real (badges, links, botones) usar `cellTemplates`.
@@ -3149,6 +3604,17 @@ class DataTable {
3149
3604
  rowClickable = input(null, /* @ts-ignore */
3150
3605
  ...(ngDevMode ? [{ debugName: "rowClickable" }] : /* istanbul ignore next */ []));
3151
3606
  rowClick = output();
3607
+ /** Nombre accesible del contenedor con scroll horizontal (solo se aplica cuando hay overflow). */
3608
+ ariaLabel = input('Tabla de datos', /* @ts-ignore */
3609
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
3610
+ /**
3611
+ * Clave (la misma que devuelve `rowKey`, o el índice sin él) de la fila
3612
+ * "actual" — ej. el registro que se está viendo en un selector. Esa fila
3613
+ * o card lleva `aria-current="true"` y la clase `--current` (fondo
3614
+ * `primary-soft`). `null` (default): ninguna.
3615
+ */
3616
+ currentRowKey = input(null, /* @ts-ignore */
3617
+ ...(ngDevMode ? [{ debugName: "currentRowKey" }] : /* istanbul ignore next */ []));
3152
3618
  /**
3153
3619
  * Paginación interna opcional — sin `pageSize`, cero cambio de
3154
3620
  * comportamiento (el consumidor sigue paginando afuera si quiere, vía el
@@ -3165,7 +3631,17 @@ class DataTable {
3165
3631
  * aunque `pageSize` esté activo (paginación sin selector). */
3166
3632
  pageSizeOptions = input(DEFAULT_PAGE_SIZE_OPTIONS, /* @ts-ignore */
3167
3633
  ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
3168
- pageChange = output();
3634
+ /** Página actual (desde 1), bidireccional: `[(page)]="page"`. El consumidor
3635
+ * puede llevarla fuera de la tabla (ej. en la URL) y la tabla la corrige
3636
+ * si queda fuera de rango. `pageChange` emite en cada cambio, también los
3637
+ * automáticos (volver a 1 al cambiar de tamaño de página). */
3638
+ page = model(1, /* @ts-ignore */
3639
+ ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
3640
+ /** Con `true`, un cambio de `rows` NO devuelve la página a 1: el
3641
+ * consumidor decide cuándo (ej. al filtrar) y la tabla solo la corrige si
3642
+ * queda fuera de rango. Default `false`: vuelve a 1, como siempre. */
3643
+ keepPageOnRowsChange = input(false, { ...(ngDevMode ? { debugName: "keepPageOnRowsChange" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3644
+ /** Emite el tamaño elegido en el selector de la paginación interna. */
3169
3645
  pageSizeChange = output();
3170
3646
  /** Nombre de la entidad listada (ej. "beneficios", "facturas"), en
3171
3647
  * plural — se agrega al final del texto informativo de la paginación
@@ -3174,8 +3650,6 @@ class DataTable {
3174
3650
  * fijo, cada consumidor decide el suyo. */
3175
3651
  itemLabel = input('', /* @ts-ignore */
3176
3652
  ...(ngDevMode ? [{ debugName: "itemLabel" }] : /* istanbul ignore next */ []));
3177
- page = signal(1, /* @ts-ignore */
3178
- ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
3179
3653
  internalPageSize = signal(null, /* @ts-ignore */
3180
3654
  ...(ngDevMode ? [{ debugName: "internalPageSize" }] : /* istanbul ignore next */ []));
3181
3655
  /** Tamaño de página efectivo: el que el usuario elija en el selector
@@ -3280,15 +3754,82 @@ class DataTable {
3280
3754
  return;
3281
3755
  }
3282
3756
  this.page.set(clamped);
3283
- this.pageChange.emit(clamped);
3284
3757
  }
3285
3758
  setPageSize(size) {
3286
3759
  if (size === null) {
3287
3760
  return;
3288
3761
  }
3289
3762
  this.internalPageSize.set(size);
3290
- this.page.set(1);
3763
+ // Primero el tamaño y después la página: un consumidor que lleva ambos en
3764
+ // la URL los escribe en una sola navegación.
3291
3765
  this.pageSizeChange.emit(size);
3766
+ this.page.set(1);
3767
+ }
3768
+ /**
3769
+ * Reparto de anchos activo: hay al menos una columna `grow`. Sin ella, los
3770
+ * anchos son los de siempre (`width`/`minWidth`/`maxWidth` tal cual).
3771
+ */
3772
+ weighted = computed(() => this.columns().some((column) => this.growWeight(column) > 0), /* @ts-ignore */
3773
+ ...(ngDevMode ? [{ debugName: "weighted" }] : /* istanbul ignore next */ []));
3774
+ /** Porción (%) del espacio sobrante de cada columna `grow`, según su peso. */
3775
+ growShares = computed(() => {
3776
+ const weights = this.columns()
3777
+ .map((column) => [column.key, this.growWeight(column)])
3778
+ .filter(([, weight]) => weight > 0);
3779
+ const total = weights.reduce((sum, [, weight]) => sum + weight, 0);
3780
+ return new Map(weights.map(([key, weight]) => [key, Math.round((weight / total) * 10000) / 100]));
3781
+ }, /* @ts-ignore */
3782
+ ...(ngDevMode ? [{ debugName: "growShares" }] : /* istanbul ignore next */ []));
3783
+ growWeight(column) {
3784
+ const weight = column.grow ?? 0;
3785
+ return Number.isFinite(weight) && weight > 0 ? weight : 0;
3786
+ }
3787
+ /**
3788
+ * `width` de la celda. En reparto de anchos: la columna `grow` toma su
3789
+ * porción en porcentaje (las porciones suman 100% y el navegador las escala
3790
+ * al espacio que dejan las demás), la de `width` conserva su ancho y la que
3791
+ * no trae ni `width` ni `grow` pide 1% — con `nowrap` queda en el ancho de
3792
+ * su contenido.
3793
+ */
3794
+ widthFor(column) {
3795
+ if (!this.weighted()) {
3796
+ return column.width ?? null;
3797
+ }
3798
+ const share = this.growShares().get(column.key);
3799
+ if (share !== undefined) {
3800
+ return `${share}%`;
3801
+ }
3802
+ return column.width ?? '1%';
3803
+ }
3804
+ /**
3805
+ * `min-width` de la celda. En reparto de anchos, el `width` de una columna
3806
+ * (también de una `grow`) funciona como piso: ni las porciones en
3807
+ * porcentaje ni el contenido de las demás pueden comprimirla.
3808
+ */
3809
+ minWidthFor(column) {
3810
+ if (!this.weighted()) {
3811
+ return column.minWidth ?? null;
3812
+ }
3813
+ // Una columna `grow` sin piso propio nunca baja de `GROW_MIN_WIDTH`: con
3814
+ // poco espacio la tabla desplaza en horizontal en vez de dejar sin ancho
3815
+ // a la columna principal.
3816
+ const fallback = this.growShares().has(column.key) ? GROW_MIN_WIDTH : null;
3817
+ return column.minWidth ?? column.width ?? fallback;
3818
+ }
3819
+ /**
3820
+ * `max-width` de la celda. En reparto de anchos, una columna que se ajusta
3821
+ * a su contenido y es `truncate` SIN `maxWidth` no debe heredar el techo de
3822
+ * 0 de `truncate` (quedaría sin ancho): se libera con `none`.
3823
+ */
3824
+ maxWidthFor(column) {
3825
+ if (this.weighted() &&
3826
+ column.truncate &&
3827
+ !column.maxWidth &&
3828
+ !column.width &&
3829
+ !this.growShares().has(column.key)) {
3830
+ return 'none';
3831
+ }
3832
+ return column.maxWidth ?? null;
3292
3833
  }
3293
3834
  /** Columnas fijas del lado izquierdo/derecho, con el offset acumulado
3294
3835
  * (medido en runtime, ver `measureStickyOffsets`) para `position: sticky`. */
@@ -3309,11 +3850,18 @@ class DataTable {
3309
3850
  zIndex: '1',
3310
3851
  };
3311
3852
  }
3312
- internalView = signal(null, /* @ts-ignore */
3313
- ...(ngDevMode ? [{ debugName: "internalView" }] : /* istanbul ignore next */ []));
3314
- /** Vista activa: la elegida por el usuario, o `defaultView` si no tocó el toggle. */
3315
- view = computed(() => this.internalView() ?? this.defaultView(), /* @ts-ignore */
3853
+ /**
3854
+ * Vista elegida, two-way: `[(view)]="view"`. Sin valor (default) manda
3855
+ * `defaultView`; el toggle interno la escribe. Para controlarla desde
3856
+ * afuera (ej. con el toggle en `kf-list-toolbar` y el estado en la URL),
3857
+ * enlazar `[(view)]` y ocultar el interno con `[showViewToggle]="false"`.
3858
+ * La vista efectivamente renderizada es `activeView()`.
3859
+ */
3860
+ view = model(undefined, /* @ts-ignore */
3316
3861
  ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
3862
+ /** Vista activa: `view` si está definida, si no `defaultView`. */
3863
+ activeView = computed(() => this.view() ?? this.defaultView(), /* @ts-ignore */
3864
+ ...(ngDevMode ? [{ debugName: "activeView" }] : /* istanbul ignore next */ []));
3317
3865
  /** Primera columna de cada rol único; el resto se ignora en el encabezado. */
3318
3866
  cardMediaColumn = computed(() => this.columns().find((c) => c.cardRole === 'media'), /* @ts-ignore */
3319
3867
  ...(ngDevMode ? [{ debugName: "cardMediaColumn" }] : /* istanbul ignore next */ []));
@@ -3323,14 +3871,20 @@ class DataTable {
3323
3871
  ...(ngDevMode ? [{ debugName: "cardStatusColumns" }] : /* istanbul ignore next */ []));
3324
3872
  cardActionColumns = computed(() => this.columns().filter((c) => c.cardRole === 'action'), /* @ts-ignore */
3325
3873
  ...(ngDevMode ? [{ debugName: "cardActionColumns" }] : /* istanbul ignore next */ []));
3326
- cardFieldColumns = computed(() => this.columns().filter((c) => !c.cardRole || c.cardRole === 'field'), /* @ts-ignore */
3874
+ cardFieldColumns = computed(() => this.columns().filter((c) => (!c.cardRole || c.cardRole === 'field') && !c.cardClamp), /* @ts-ignore */
3327
3875
  ...(ngDevMode ? [{ debugName: "cardFieldColumns" }] : /* istanbul ignore next */ []));
3876
+ /** Campos a ancho completo y recortados (`cardClamp`), antes del resto. */
3877
+ cardLeadColumns = computed(() => this.columns().filter((c) => (!c.cardRole || c.cardRole === 'field') && !!c.cardClamp), /* @ts-ignore */
3878
+ ...(ngDevMode ? [{ debugName: "cardLeadColumns" }] : /* istanbul ignore next */ []));
3879
+ /** Fila inferior de datos secundarios (`cardRole: 'meta'`). */
3880
+ cardMetaColumns = computed(() => this.columns().filter((c) => c.cardRole === 'meta'), /* @ts-ignore */
3881
+ ...(ngDevMode ? [{ debugName: "cardMetaColumns" }] : /* istanbul ignore next */ []));
3328
3882
  /**
3329
3883
  * La card entera es clickeable solo si no tiene acciones propias: un
3330
3884
  * `role="button"` con botones adentro es inválido y deja el foco ambiguo.
3331
3885
  * Con columnas `action`, la interacción la manejan esos botones.
3332
3886
  */
3333
- cardInteractive = computed(() => this.cardActionColumns().length === 0, /* @ts-ignore */
3887
+ cardInteractive = computed(() => this.rowClickable() ?? this.cardActionColumns().length === 0, /* @ts-ignore */
3334
3888
  ...(ngDevMode ? [{ debugName: "cardInteractive" }] : /* istanbul ignore next */ []));
3335
3889
  /**
3336
3890
  * Mismo criterio que `cardInteractive`, para la vista de tabla: la fila
@@ -3343,12 +3897,23 @@ class DataTable {
3343
3897
  rowInteractive = computed(() => this.rowClickable() ?? this.cardActionColumns().length === 0, /* @ts-ignore */
3344
3898
  ...(ngDevMode ? [{ debugName: "rowInteractive" }] : /* istanbul ignore next */ []));
3345
3899
  setView(next) {
3346
- this.internalView.set(next);
3900
+ this.view.set(next);
3347
3901
  }
3348
3902
  trackRow(row, index) {
3349
3903
  const keyFn = this.rowKey();
3350
3904
  return keyFn ? keyFn(row, index) : index;
3351
3905
  }
3906
+ /** Alineada al final: `align: 'end'` o el histórico `right: true`. */
3907
+ isEndAligned(column) {
3908
+ return column.align === 'end' || !!column.right;
3909
+ }
3910
+ /** `title` de una celda `truncate`: el valor completo como texto. */
3911
+ truncateTitle(row, column) {
3912
+ if (!column.truncate) {
3913
+ return null;
3914
+ }
3915
+ return this.cellValue(row, column) || null;
3916
+ }
3352
3917
  cellValue(row, column) {
3353
3918
  const renderFn = this.render();
3354
3919
  if (renderFn) {
@@ -3357,12 +3922,45 @@ class DataTable {
3357
3922
  const value = row[column.key];
3358
3923
  return value === null || value === undefined ? '' : String(value);
3359
3924
  }
3360
- onRowClick(row, index) {
3361
- if (!this.rowInteractive()) {
3925
+ isCurrentRow(row, index) {
3926
+ const current = this.currentRowKey();
3927
+ return current !== null && this.trackRow(row, index) === current;
3928
+ }
3929
+ /**
3930
+ * Espacio activa la fila/card igual que Enter, como un botón. Solo si el
3931
+ * foco está en la fila misma (no en un control dentro de ella): ahí
3932
+ * `preventDefault` evita el scroll de página; en cualquier otro caso
3933
+ * Espacio conserva su comportamiento nativo.
3934
+ */
3935
+ onRowSpace(event, row, index) {
3936
+ if (event.target !== event.currentTarget || !this.rowInteractive()) {
3937
+ return;
3938
+ }
3939
+ event.preventDefault();
3940
+ this.onRowClick(row, index);
3941
+ }
3942
+ /**
3943
+ * Emite `rowClick` salvo que el evento nazca dentro de un control
3944
+ * interactivo de la celda (enlace, botón, campo…): ese control ya resuelve
3945
+ * su propia acción. Así una celda con un `<a href>` real (clic derecho →
3946
+ * "abrir en nueva pestaña", Ctrl/Cmd+clic) no navega dos veces. El parámetro
3947
+ * `event` es opcional para no romper llamadas programáticas existentes.
3948
+ */
3949
+ onRowClick(row, index, event) {
3950
+ if (!this.rowInteractive() || this.originatesInInteractiveControl(event)) {
3362
3951
  return;
3363
3952
  }
3364
3953
  this.rowClick.emit({ row, index });
3365
3954
  }
3955
+ originatesInInteractiveControl(event) {
3956
+ const target = event?.target;
3957
+ const host = event?.currentTarget;
3958
+ if (!(target instanceof Element) || !(host instanceof Element) || target === host) {
3959
+ return false;
3960
+ }
3961
+ const control = target.closest(INTERACTIVE_CONTROL_SELECTOR);
3962
+ return control !== null && control !== host && host.contains(control);
3963
+ }
3366
3964
  /**
3367
3965
  * Ancho real de cada columna `sticky` — se mide después de cada render
3368
3966
  * porque el ancho de una columna depende de su contenido, no hay forma
@@ -3392,12 +3990,12 @@ class DataTable {
3392
3990
  this.stickyOffsets.set(offsets);
3393
3991
  }
3394
3992
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
3395
- 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 }, 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 }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", 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 } }, outputs: { rowClick: "rowClick", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange" }, 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()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"view()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && view() === 'cards') {\n <div class=\"kf-data-table__cards\" aria-busy=\"true\">\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]=\"column.right\"\n >\n {{ column.label }}\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 [class]=\"column.className\" [class.kf-data-table__cell--right]=\"column.right\">\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 (view() === '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 >\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 [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"column.right\"\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]=\"column.minWidth\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n {{ column.label }}\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 [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i)\"\n (keydown.enter)=\"onRowClick(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]=\"column.right\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"column.minWidth\"\n >\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 </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\">\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 [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() && onRowClick(row, i)\"\n (keydown.enter)=\"cardInteractive() && onRowClick(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 class=\"kf-data-table__card-title\">\n @if (cellTemplates()?.[title.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n {{ cellValue(row, title) }}\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 @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 }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\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 class=\"kf-data-table__card-value\">\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 </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 @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 }\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", 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: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__cell--right{text-align:right}.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(220px,1fr));gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{display:flex;flex-direction:column;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);overflow:hidden;transition:border-color .14s,box-shadow .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:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-md)}.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-direction:column;gap:var(--kf-space-xs);min-width:0}.kf-data-table__card-title{font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-statuses,.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);padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm)}.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__thumb{width:32px;height:32px}.kf-data-table__card-value{color:var(--kf-color-text);text-align:right}\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"], outputs: ["viewChange"] }] });
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"] }] });
3396
3994
  }
3397
3995
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, decorators: [{
3398
3996
  type: Component,
3399
- 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()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"view()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && view() === 'cards') {\n <div class=\"kf-data-table__cards\" aria-busy=\"true\">\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]=\"column.right\"\n >\n {{ column.label }}\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 [class]=\"column.className\" [class.kf-data-table__cell--right]=\"column.right\">\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 (view() === '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 >\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 [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"column.right\"\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]=\"column.minWidth\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n {{ column.label }}\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 [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i)\"\n (keydown.enter)=\"onRowClick(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]=\"column.right\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"column.minWidth\"\n >\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 </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\">\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 [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() && onRowClick(row, i)\"\n (keydown.enter)=\"cardInteractive() && onRowClick(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 class=\"kf-data-table__card-title\">\n @if (cellTemplates()?.[title.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n {{ cellValue(row, title) }}\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 @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 }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\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 class=\"kf-data-table__card-value\">\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 </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 @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 }\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", 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: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__cell--right{text-align:right}.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(220px,1fr));gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{display:flex;flex-direction:column;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);overflow:hidden;transition:border-color .14s,box-shadow .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:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-md)}.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-direction:column;gap:var(--kf-space-xs);min-width:0}.kf-data-table__card-title{font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-statuses,.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);padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm)}.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__thumb{width:32px;height:32px}.kf-data-table__card-value{color:var(--kf-color-text);text-align:right}\n"] }]
3400
- }], 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 }] }], 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"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], 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"] }] } });
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"] }] } });
3401
3999
 
3402
4000
  /**
3403
4001
  * Signal con la vista de `kf-data-table` ('table' | 'cards') derivada de un
@@ -3517,6 +4115,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3517
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"] }]
3518
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 }] }] } });
3519
4117
 
4118
+ let nextId$2 = 0;
3520
4119
  /**
3521
4120
  * Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
3522
4121
  * ni qué es sesión, solo da tres lugares para proyectar contenido
@@ -3549,6 +4148,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3549
4148
  * <a href="/">Home</a>
3550
4149
  * <a href="/vehiculos">Vehículos</a>
3551
4150
  * </kf-drawer>
4151
+ *
4152
+ * ## Panel de tarea (`role="dialog"`)
4153
+ *
4154
+ * Para un panel modal que no es navegación (filtros de un listado, resumen
4155
+ * de un registro): `role="dialog"` expone `role="dialog"` + `aria-modal`,
4156
+ * `title` dibuja una barra clara con el título (que nombra el diálogo vía
4157
+ * `aria-labelledby`) y un botón de cierre con `closeLabel`. En pantallas de
4158
+ * hasta 640px el panel ocupa el ancho completo; arriba de eso mide
4159
+ * `--kf-drawer-width` (default 480px). El pie (`[kf-drawer-footer]`) queda
4160
+ * fijo abajo mientras el cuerpo hace scroll.
4161
+ *
4162
+ * <kf-drawer [(open)]="filtersOpen" role="dialog" side="right"
4163
+ * title="Filtros" closeLabel="Cerrar filtros">
4164
+ * <kf-option-list label="Estado" ... />
4165
+ * <div kf-drawer-footer> ...Limpiar / Ver 12 resultados... </div>
4166
+ * </kf-drawer>
4167
+ *
4168
+ * `side="bottom"` lo convierte en hoja inferior (grip, esquinas superiores
4169
+ * redondeadas, alto máximo 80vh) — pensado para un resumen en mobile.
4170
+ *
4171
+ * 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).
4174
+ *
4175
+ * Nombres por defecto: con `role="navigation"` el panel se llama "Menú" y
4176
+ * el cierre "Cerrar menú" (comportamiento histórico). Con `role="dialog"`
4177
+ * los defaults son neutros ("Diálogo" / "Cerrar") y en modo desarrollo se
4178
+ * avisa por consola si no hay `title` ni `ariaLabel`: un diálogo sin nombre
4179
+ * propio no le dice nada al lector de pantalla.
4180
+ *
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`.
4187
+ *
4188
+ * Al cerrar, el foco vuelve al elemento que lo tenía al abrir solo si sigue
4189
+ * en el documento (`isConnected`); si fue desmontado mientras el drawer
4190
+ * estaba abierto, no se fuerza ningún destino.
3552
4191
  */
3553
4192
  class Drawer {
3554
4193
  /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
@@ -3556,13 +4195,32 @@ class Drawer {
3556
4195
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
3557
4196
  /** Borde de anclaje del panel. `left` (default) es la convención dominante
3558
4197
  * (Material/iOS/Android): el botón que abre el drawer y el propio panel
3559
- * quedan del mismo lado. */
4198
+ * quedan del mismo lado. `bottom` lo convierte en hoja inferior. */
3560
4199
  side = input('left', /* @ts-ignore */
3561
4200
  ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
3562
- /** `aria-label` del `<nav>` interno — describe el propósito del panel para
3563
- * lectores de pantalla (ej. "Menú de navegación"). */
3564
- ariaLabel = input('Menú', /* @ts-ignore */
3565
- ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4201
+ /** `navigation` (default, menú) o `dialog` (panel modal de tarea). Ver JSDoc de la clase. */
4202
+ role = input('navigation', /* @ts-ignore */
4203
+ ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
4204
+ /** `aria-label` del panel — describe su propósito para lectores de
4205
+ * pantalla (ej. "Menú de navegación"). Ignorado cuando hay `title`, que
4206
+ * nombra el panel con su texto visible. Default: "Menú" (`navigation`) o
4207
+ * "Diálogo" (`dialog`). */
4208
+ ariaLabel = input(/* @ts-ignore */
4209
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4210
+ /** Título visible en una barra clara arriba del panel, con botón de cierre. Sin él no hay barra (comportamiento histórico). */
4211
+ title = input(/* @ts-ignore */
4212
+ ...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
4213
+ /** Nombre accesible del botón de cierre de la barra de título y del
4214
+ * overlay. Default: "Cerrar menú" (`navigation`) o "Cerrar" (`dialog`). */
4215
+ closeLabel = input(/* @ts-ignore */
4216
+ ...(ngDevMode ? [undefined, { debugName: "closeLabel" }] : /* istanbul ignore next */ []));
4217
+ /** `ariaLabel` o, sin él, el default según el rol. */
4218
+ resolvedAriaLabel = computed(() => this.ariaLabel() ?? (this.role() === 'dialog' ? 'Diálogo' : 'Menú'), /* @ts-ignore */
4219
+ ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
4220
+ /** `closeLabel` o, sin él, el default según el rol. */
4221
+ resolvedCloseLabel = computed(() => this.closeLabel() ?? (this.role() === 'dialog' ? 'Cerrar' : 'Cerrar menú'), /* @ts-ignore */
4222
+ ...(ngDevMode ? [{ debugName: "resolvedCloseLabel" }] : /* istanbul ignore next */ []));
4223
+ titleId = `kf-drawer-title-${nextId$2++}`;
3566
4224
  panelRef = viewChild.required('panel', /* @ts-ignore */
3567
4225
  ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
3568
4226
  focusTrapFactory = inject(FocusTrapFactory);
@@ -3579,14 +4237,26 @@ class Drawer {
3579
4237
  else {
3580
4238
  this.focusTrap?.destroy();
3581
4239
  this.focusTrap = null;
3582
- this.lastFocusedElement?.focus();
4240
+ if (this.lastFocusedElement?.isConnected) {
4241
+ this.lastFocusedElement.focus();
4242
+ }
3583
4243
  this.lastFocusedElement = null;
3584
4244
  }
3585
4245
  });
4246
+ if (isDevMode()) {
4247
+ effect(() => {
4248
+ if (this.role() === 'dialog' && !this.title() && !this.ariaLabel()) {
4249
+ console.warn('[kf-drawer] role="dialog" sin `title` ni `ariaLabel`: el diálogo se anuncia con el nombre genérico "Diálogo".');
4250
+ }
4251
+ });
4252
+ }
3586
4253
  inject(DestroyRef).onDestroy(() => this.focusTrap?.destroy());
3587
4254
  }
3588
4255
  /** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
3589
- onEscape() {
4256
+ onEscape(event) {
4257
+ if (!this.open() || event.defaultPrevented) {
4258
+ return;
4259
+ }
3590
4260
  this.open.set(false);
3591
4261
  }
3592
4262
  getOrCreateFocusTrap() {
@@ -3596,14 +4266,21 @@ class Drawer {
3596
4266
  return this.focusTrap;
3597
4267
  }
3598
4268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
3599
- 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 }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, 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 aria-label=\"Cerrar men\u00FA\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<nav\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"!open()\"\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</nav>\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--open{transform:translate(0);visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.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;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__footer{padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}\n"] });
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"] }] });
3600
4270
  }
3601
4271
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, decorators: [{
3602
4272
  type: Component,
3603
- args: [{ selector: 'kf-drawer', template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<nav\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-hidden]=\"!open()\"\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</nav>\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--open{transform:translate(0);visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.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;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__footer{padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}\n"] }]
3604
- }], 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 }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onEscape: [{
4273
+ args: [{ selector: 'kf-drawer', imports: [Button], host: {
4274
+ // `role` y `title` son inputs con nombre de atributo nativo: escritos
4275
+ // como atributo estático (`role="dialog"`, `title="Filtros"`) Angular
4276
+ // también los deja en el host, lo que anidaría dos diálogos y mostraría
4277
+ // un tooltip nativo sobre todo el panel. El rol real vive en el panel.
4278
+ '[attr.role]': 'null',
4279
+ '[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"] }]
4281
+ }], 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: [{
3605
4282
  type: HostListener,
3606
- args: ['document:keydown.escape']
4283
+ args: ['document:keydown.escape', ['$event']]
3607
4284
  }] } });
3608
4285
 
3609
4286
  /**
@@ -3774,6 +4451,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3774
4451
  * está en vuelo).
3775
4452
  *
3776
4453
  * <kf-input label="DNI" readonly formControlName="documentId" />
4454
+ *
4455
+ * Buscador sin label visible: `ariaLabel` da el nombre accesible del
4456
+ * `<input>` (el placeholder no lo reemplaza: desaparece al escribir) y
4457
+ * `clearable` agrega un botón "×" mientras haya texto. Borrar limpia el
4458
+ * valor, notifica al formulario (`ngModel`/`formControl`), dispara un
4459
+ * evento `input` nativo (para quien escucha `(input)`), emite `cleared` y
4460
+ * devuelve el foco al campo.
4461
+ *
4462
+ * <kf-input type="search" icon="search" ariaLabel="Buscar cotizaciones"
4463
+ * placeholder="Buscar cotización" clearable [(ngModel)]="query" />
4464
+ *
4465
+ * `focus()` enfoca el `<input>` nativo, sin que el consumidor busque el
4466
+ * control interno con `querySelector`.
4467
+ *
4468
+ * `autocomplete`, `name` e `inputmode` se reenvían al `<input>` nativo (un
4469
+ * atributo en el host no llega adentro) para que el navegador y los gestores
4470
+ * de contraseñas identifiquen el campo; sin valor no se renderiza ningún
4471
+ * atributo. Funcionan igual con `type="password"` y `toggleable`.
4472
+ *
4473
+ * <kf-input label="RUT" autocomplete="username" name="rut" inputmode="text" formControlName="rut" />
4474
+ * <kf-input type="password" label="Contraseña" toggleable autocomplete="current-password" name="password" formControlName="password" />
3777
4475
  */
3778
4476
  class Input {
3779
4477
  static nextId = 0;
@@ -3808,12 +4506,34 @@ class Input {
3808
4506
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
3809
4507
  placeholder = input('', /* @ts-ignore */
3810
4508
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
3811
- /** Teclado virtual sugerido en mobile (ej. `"numeric"` para montos o
3812
- * kilometraje) — sin valor, el navegador decide según `type`. */
4509
+ /** Token de autocompletado HTML (`username`, `email`, `current-password`,
4510
+ * `new-password`, `off`, etc.) que se reenvía al `<input>` nativo para que
4511
+ * el navegador y los gestores de contraseñas identifiquen el propósito del
4512
+ * campo (WCAG 1.3.5 y 3.3.8). Sin valor, el atributo no se renderiza. */
4513
+ autocomplete = input(/* @ts-ignore */
4514
+ ...(ngDevMode ? [undefined, { debugName: "autocomplete" }] : /* istanbul ignore next */ []));
4515
+ /** `name` del `<input>` nativo; los gestores de contraseñas y el envío
4516
+ * nativo de formularios lo usan para identificar el campo. Sin valor, el
4517
+ * atributo no se renderiza. */
4518
+ name = input(/* @ts-ignore */
4519
+ ...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
4520
+ /** Sugiere el teclado virtual (`numeric`, `email`, `tel`, etc.) sin cambiar
4521
+ * el `type`. Sin valor, el atributo no se renderiza. */
3813
4522
  inputmode = input(/* @ts-ignore */
3814
4523
  ...(ngDevMode ? [undefined, { debugName: "inputmode" }] : /* istanbul ignore next */ []));
3815
4524
  /** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
3816
4525
  toggleable = input(false, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4526
+ /** Nombre accesible del `<input>` cuando no hay `label` visible. Ignorado
4527
+ * si hay `label`: un `aria-label` pisaría al `<label for>` visible. */
4528
+ ariaLabel = input(/* @ts-ignore */
4529
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4530
+ /** Agrega un botón "×" para borrar el valor mientras haya texto (sin efecto con `disabled`/`readonly`). */
4531
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4532
+ /** Nombre accesible del botón de borrar. */
4533
+ clearLabel = input('Borrar texto', /* @ts-ignore */
4534
+ ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
4535
+ /** Emitido después de borrar el valor con el botón "×". */
4536
+ cleared = output();
3817
4537
  inputId = `kf-input-${Input.nextId++}`;
3818
4538
  hintId = `${this.inputId}-hint`;
3819
4539
  errorId = `${this.inputId}-error`;
@@ -3833,6 +4553,9 @@ class Input {
3833
4553
  /** El botón solo tiene sentido si el campo realmente es de contraseña. */
3834
4554
  showPasswordToggle = computed(() => this.toggleable() && this.type() === 'password', /* @ts-ignore */
3835
4555
  ...(ngDevMode ? [{ debugName: "showPasswordToggle" }] : /* istanbul ignore next */ []));
4556
+ /** El botón de borrar solo aparece con texto y con el campo editable. */
4557
+ showClear = computed(() => this.clearable() && !!this.value() && !this.isDisabled() && !this.readonly(), /* @ts-ignore */
4558
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
3836
4559
  /**
3837
4560
  * `type` real que recibe el `<input>` nativo: igual a `type()` salvo
3838
4561
  * cuando el toggle está activo y visible, donde pasa a `text` para
@@ -3851,6 +4574,10 @@ class Input {
3851
4574
  // ya cargado (ngModel/formControl con valor inicial) se refleje siempre.
3852
4575
  this.nativeInput().nativeElement.value = this.value();
3853
4576
  }
4577
+ /** Enfoca el `<input>` nativo (mismas opciones que `HTMLElement.focus`). */
4578
+ focus(options) {
4579
+ this.nativeInput().nativeElement.focus(options);
4580
+ }
3854
4581
  writeValue(value) {
3855
4582
  const nextValue = value ?? '';
3856
4583
  this.value.set(nextValue);
@@ -3880,6 +4607,20 @@ class Input {
3880
4607
  this.focused.set(false);
3881
4608
  this.onTouched();
3882
4609
  }
4610
+ /**
4611
+ * Borra el valor reutilizando el mismo camino que el tipeo: un evento
4612
+ * `input` nativo pasa por `onInput` (notifica al formulario) y además
4613
+ * llega a cualquier `(input)` del consumidor. Con el mouse no hay blur:
4614
+ * el `mousedown` del botón hace `preventDefault` (ver Input.html), así el
4615
+ * campo no se marca `touched` por usar su propio "×".
4616
+ */
4617
+ onClear() {
4618
+ const native = this.nativeInput().nativeElement;
4619
+ native.value = '';
4620
+ native.dispatchEvent(new Event('input', { bubbles: true }));
4621
+ native.focus();
4622
+ this.cleared.emit();
4623
+ }
3883
4624
  /**
3884
4625
  * Alterna la visibilidad del valor. No dispara `onBlur`/pierde el foco:
3885
4626
  * el botón vive dentro del mismo `.kf-input__field`, así que un click acá
@@ -3889,13 +4630,13 @@ class Input {
3889
4630
  this.passwordVisible.update((visible) => !visible);
3890
4631
  }
3891
4632
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Component });
3892
- 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 }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
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: [
3893
4634
  {
3894
4635
  provide: NG_VALUE_ACCESSOR,
3895
4636
  useExisting: forwardRef(() => Input),
3896
4637
  multi: true,
3897
4638
  },
3898
- ], 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.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 [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\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__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__toggle:hover{color:var(--kf-color-text)}.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 });
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 });
3899
4640
  }
3900
4641
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, decorators: [{
3901
4642
  type: Component,
@@ -3905,10 +4646,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3905
4646
  useExisting: forwardRef(() => Input),
3906
4647
  multi: true,
3907
4648
  },
3908
- ], 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.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 [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\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__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__toggle:hover{color:var(--kf-color-text)}.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
3909
- }], 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 }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }] } });
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"] }] } });
3910
4651
 
3911
- let nextId = 0;
4652
+ let nextId$1 = 0;
3912
4653
  /**
3913
4654
  * Dropdown de selección múltiple con checkboxes internos. El panel flotante
3914
4655
  * se conecta al trigger vía `@angular/cdk/overlay` — el mismo mecanismo que
@@ -3965,7 +4706,7 @@ class MultiSelect {
3965
4706
  searchThreshold = input(7, /* @ts-ignore */
3966
4707
  ...(ngDevMode ? [{ debugName: "searchThreshold" }] : /* istanbul ignore next */ []));
3967
4708
  valuesChange = output();
3968
- instanceId = `kf-multi-select-${nextId++}`;
4709
+ instanceId = `kf-multi-select-${nextId$1++}`;
3969
4710
  triggerId = `${this.instanceId}-trigger`;
3970
4711
  listboxId = `${this.instanceId}-listbox`;
3971
4712
  isOpen = signal(false, /* @ts-ignore */
@@ -4162,7 +4903,7 @@ class Filter {
4162
4903
  this.fieldChange.emit({ key, value });
4163
4904
  }
4164
4905
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, deps: [], target: i0.ɵɵFactoryTarget.Component });
4165
- 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", "inputmode", "toggleable"] }, { 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"], outputs: ["valueChange"] }] });
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"] }] });
4166
4907
  }
4167
4908
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, decorators: [{
4168
4909
  type: Component,
@@ -4173,19 +4914,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4173
4914
  * Fila de chips tipo "pill" para filtros de una sola selección.
4174
4915
  *
4175
4916
  * Cada opción puede pasarse como string simple (se usa como value y label
4176
- * a la vez) o como objeto { value, label } cuando el texto mostrado difiere
4177
- * del valor interno.
4917
+ * a la vez) o como objeto `{ value, label, count? }` cuando el texto
4918
+ * mostrado difiere del valor interno o cuando cada chip lleva un contador.
4178
4919
  *
4179
4920
  * El chip activo se resalta con `--kf-color-primary` — mismo color de marca
4180
4921
  * que `kf-tabs`/`kf-select` para su propio estado activo, así el filtro
4181
4922
  * seleccionado se lee con el color del tema activo, no un negro fijo que
4182
4923
  * no cambia entre marcas.
4183
4924
  *
4925
+ * Densidad: el padding horizontal de cada chip sale de
4926
+ * `--kf-filter-chips-padding-inline` (default `--kf-space-md`). Un
4927
+ * contenedor puede reducirlo sin tocar este componente — `kf-list-toolbar`
4928
+ * lo baja a 12px en su modo compacto.
4929
+ *
4184
4930
  * <kf-filter-chips [options]="['Todos', 'Activos', 'Archivados']"
4185
4931
  * [value]="filtro"
4186
4932
  * (valueChange)="filtro = $event" />
4187
4933
  *
4188
- * <kf-filter-chips [options]="[{ value: 'all', label: 'Todos' }]"
4934
+ * `layout="scroll"` deja todos los chips en UNA línea con desplazamiento
4935
+ * horizontal (para mobile: los filtros no ocupan más alto que una fila); el
4936
+ * default `wrap` los reparte en varias líneas. `deselectable` permite
4937
+ * quitar el filtro volviendo a tocar el chip activo (emite `''`), para el
4938
+ * caso "ningún chip = sin filtro".
4939
+ *
4940
+ * <kf-filter-chips ariaLabel="Filtrar por estado"
4941
+ * [options]="[{ value: '', label: 'Todas', count: 12 }]"
4189
4942
  * [value]="filtro"
4190
4943
  * (valueChange)="filtro = $event" />
4191
4944
  */
@@ -4194,23 +4947,34 @@ class FilterChips {
4194
4947
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4195
4948
  value = input('', /* @ts-ignore */
4196
4949
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4950
+ /** Nombre accesible del grupo (ej. "Filtrar por estado"). Sin él, el grupo se anuncia sin nombre. */
4951
+ ariaLabel = input(/* @ts-ignore */
4952
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4953
+ /** `wrap` (default): varias líneas. `scroll`: una sola línea con desplazamiento horizontal. */
4954
+ layout = input('wrap', /* @ts-ignore */
4955
+ ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
4956
+ /** Tocar el chip activo lo desactiva y emite `''`. Default `false` (comportamiento histórico). */
4957
+ deselectable = input(false, { ...(ngDevMode ? { debugName: "deselectable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4197
4958
  valueChange = output();
4198
- /** Normaliza cada opción string/objeto a la forma { value, label }. */
4959
+ /** Normaliza cada opción string/objeto a la forma `{ value, label, count? }`. */
4199
4960
  normalizedOptions = computed(() => this.options().map((option) => typeof option === 'string' ? { value: option, label: option } : option), /* @ts-ignore */
4200
4961
  ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
4201
4962
  select(value) {
4202
4963
  if (value === this.value()) {
4964
+ if (this.deselectable() && value !== '') {
4965
+ this.valueChange.emit('');
4966
+ }
4203
4967
  return;
4204
4968
  }
4205
4969
  this.valueChange.emit(value);
4206
4970
  }
4207
4971
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FilterChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
4208
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FilterChips, isStandalone: true, selector: "kf-filter-chips", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"kf-filter-chips\" role=\"group\">\n @for (option of normalizedOptions(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-filter-chips__chip\"\n [class.kf-filter-chips__chip--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:block}.kf-filter-chips{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-filter-chips__chip{font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border-radius:var(--kf-radius-pill);border:1px solid var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-filter-chips__chip:hover:not(.kf-filter-chips__chip--active){border-color:var(--kf-color-border-strong)}.kf-filter-chips__chip:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-filter-chips__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}\n"] });
4972
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FilterChips, isStandalone: true, selector: "kf-filter-chips", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, deselectable: { classPropertyName: "deselectable", publicName: "deselectable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div\n class=\"kf-filter-chips\"\n [class.kf-filter-chips--scroll]=\"layout() === 'scroll'\"\n role=\"group\"\n [attr.aria-label]=\"ariaLabel() || null\"\n>\n @for (option of normalizedOptions(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-filter-chips__chip\"\n [class.kf-filter-chips__chip--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n @if (option.count !== undefined) {\n <span class=\"kf-filter-chips__count\">{{ option.count }}</span>\n }\n </button>\n }\n</div>\n", styles: [":host{display:block}.kf-filter-chips{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-filter-chips--scroll{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:thin;padding:3px;margin:-3px}.kf-filter-chips__chip{display:inline-flex;align-items:center;gap:var(--kf-space-xs);font:inherit;flex:none;white-space:nowrap;padding:var(--kf-space-xs) var(--kf-filter-chips-padding-inline, var(--kf-space-md));border-radius:var(--kf-radius-pill);border:1px solid var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-filter-chips__chip:hover:not(.kf-filter-chips__chip--active){border-color:var(--kf-color-border-strong)}.kf-filter-chips__chip:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-filter-chips__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}.kf-filter-chips__count{font-size:var(--kf-font-size-caption);font-variant-numeric:tabular-nums}@media(prefers-reduced-motion:reduce){.kf-filter-chips__chip{transition:none}}\n"] });
4209
4973
  }
4210
4974
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FilterChips, decorators: [{
4211
4975
  type: Component,
4212
- args: [{ selector: 'kf-filter-chips', template: "<div class=\"kf-filter-chips\" role=\"group\">\n @for (option of normalizedOptions(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-filter-chips__chip\"\n [class.kf-filter-chips__chip--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:block}.kf-filter-chips{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-filter-chips__chip{font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border-radius:var(--kf-radius-pill);border:1px solid var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-filter-chips__chip:hover:not(.kf-filter-chips__chip--active){border-color:var(--kf-color-border-strong)}.kf-filter-chips__chip:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-filter-chips__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}\n"] }]
4213
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
4976
+ args: [{ selector: 'kf-filter-chips', template: "<div\n class=\"kf-filter-chips\"\n [class.kf-filter-chips--scroll]=\"layout() === 'scroll'\"\n role=\"group\"\n [attr.aria-label]=\"ariaLabel() || null\"\n>\n @for (option of normalizedOptions(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-filter-chips__chip\"\n [class.kf-filter-chips__chip--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n @if (option.count !== undefined) {\n <span class=\"kf-filter-chips__count\">{{ option.count }}</span>\n }\n </button>\n }\n</div>\n", styles: [":host{display:block}.kf-filter-chips{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-filter-chips--scroll{flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;scrollbar-width:thin;padding:3px;margin:-3px}.kf-filter-chips__chip{display:inline-flex;align-items:center;gap:var(--kf-space-xs);font:inherit;flex:none;white-space:nowrap;padding:var(--kf-space-xs) var(--kf-filter-chips-padding-inline, var(--kf-space-md));border-radius:var(--kf-radius-pill);border:1px solid var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-filter-chips__chip:hover:not(.kf-filter-chips__chip--active){border-color:var(--kf-color-border-strong)}.kf-filter-chips__chip:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-filter-chips__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}.kf-filter-chips__count{font-size:var(--kf-font-size-caption);font-variant-numeric:tabular-nums}@media(prefers-reduced-motion:reduce){.kf-filter-chips__chip{transition:none}}\n"] }]
4977
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], deselectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "deselectable", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
4214
4978
 
4215
4979
  /**
4216
4980
  * Pie de página general del portal (página 38): logo + copyright a la
@@ -4327,6 +5091,408 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4327
5091
  args: [{ selector: 'kf-journey-stepper', imports: [Icon], template: "<div\n #track\n class=\"kf-journey-stepper\"\n [class.kf-journey-stepper--vertical]=\"orientation() === 'vertical'\"\n [class.kf-journey-stepper--card]=\"surface() === 'card'\"\n [class.kf-journey-stepper--semantic]=\"colorScheme() === 'semantic'\"\n [class.kf-journey-stepper--overflowing]=\"hasOverflow()\"\n>\n @for (step of resolvedSteps(); track step.key; let last = $last) {\n <div class=\"kf-journey-stepper__step\" [class]=\"'kf-journey-stepper__step--' + step.status\">\n @if (clickable()) {\n <button type=\"button\" class=\"kf-journey-stepper__dot\" (click)=\"stepClick.emit(step.key)\">\n <kf-icon [name]=\"dotIcon(step.status, step.icon)\" size=\"sm\" />\n </button>\n } @else {\n <span class=\"kf-journey-stepper__dot\">\n <kf-icon [name]=\"dotIcon(step.status, step.icon)\" size=\"sm\" />\n </span>\n }\n\n <div class=\"kf-journey-stepper__text\">\n <span class=\"kf-journey-stepper__label\">{{ step.label }}</span>\n @if (step.date) {\n <span class=\"kf-journey-stepper__date\">{{ step.date }}</span>\n }\n </div>\n </div>\n\n @if (!last && orientation() === 'horizontal') {\n <span\n class=\"kf-journey-stepper__line\"\n [class.kf-journey-stepper__line--done]=\"step.status === 'done'\"\n ></span>\n }\n }\n</div>\n", styles: [":host{display:block;min-width:0}.kf-journey-stepper--overflowing:not(.kf-journey-stepper--vertical){-webkit-mask-image:linear-gradient(to right,transparent 0,black 24px,black calc(100% - 24px),transparent 100%);mask-image:linear-gradient(to right,transparent 0,black 24px,black calc(100% - 24px),transparent 100%)}.kf-journey-stepper{display:flex;align-items:center;overflow-x:auto;padding-bottom:var(--kf-space-xs);font-family:var(--kf-font-body);scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-journey-stepper::-webkit-scrollbar{height:6px}.kf-journey-stepper::-webkit-scrollbar-track{background:transparent}.kf-journey-stepper::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-journey-stepper::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-journey-stepper:not(.kf-journey-stepper--card){scrollbar-color:rgba(255,255,255,.3) transparent}.kf-journey-stepper:not(.kf-journey-stepper--card)::-webkit-scrollbar-thumb{background-color:#ffffff4d}.kf-journey-stepper:not(.kf-journey-stepper--card)::-webkit-scrollbar-thumb:hover{background-color:#ffffff73}.kf-journey-stepper--vertical{flex-direction:column;align-items:stretch;overflow-x:visible}.kf-journey-stepper__step{display:flex;align-items:center;flex-direction:column;gap:var(--kf-space-sm);flex:none}.kf-journey-stepper--vertical .kf-journey-stepper__step{flex-direction:row;align-items:flex-start;position:relative;padding-bottom:var(--kf-space-lg)}.kf-journey-stepper--vertical .kf-journey-stepper__step:last-child{padding-bottom:0}.kf-journey-stepper--vertical .kf-journey-stepper__step:before{content:\"\";position:absolute;left:13px;top:28px;bottom:0;width:2px;background:var(--kf-color-gray-200)}.kf-journey-stepper--vertical .kf-journey-stepper__step:last-child:before{display:none}.kf-journey-stepper--vertical:not(.kf-journey-stepper--semantic) .kf-journey-stepper__step--done:before{background:var(--kf-color-primary)}.kf-journey-stepper--semantic .kf-journey-stepper__step--done:before{background:var(--kf-color-success)}.kf-journey-stepper__dot{width:36px;height:36px;border-radius:var(--kf-radius-pill);display:grid;place-items:center;flex:none;z-index:1;border:2px solid transparent;background:transparent;cursor:default;font:inherit}.kf-journey-stepper--vertical .kf-journey-stepper__dot{width:28px;height:28px;border:none}button.kf-journey-stepper__dot{cursor:pointer}.kf-journey-stepper__step--pending .kf-journey-stepper__dot{background:#ffffff14;color:#ffffff80;border-color:#ffffff24}.kf-journey-stepper__step--done .kf-journey-stepper__dot{background:var(--kf-color-primary);color:var(--kf-color-white);border-color:var(--kf-color-primary)}.kf-journey-stepper__step--current .kf-journey-stepper__dot{background:var(--kf-color-white);color:var(--kf-color-primary);border-color:var(--kf-color-white);box-shadow:0 0 0 4px #ffffff29}.kf-journey-stepper__label{font-size:.72rem;font-weight:700;color:#ffffff8c;white-space:nowrap}.kf-journey-stepper__step--done .kf-journey-stepper__label,.kf-journey-stepper__step--current .kf-journey-stepper__label{color:var(--kf-color-white)}.kf-journey-stepper__line{flex:1;height:2px;background:#ffffff24;margin:0 var(--kf-space-xs);margin-bottom:20px;min-width:24px}.kf-journey-stepper__line--done{background:var(--kf-color-primary)}.kf-journey-stepper--card .kf-journey-stepper__step--pending .kf-journey-stepper__dot{background:var(--kf-color-surface);color:var(--kf-color-gray-400);border-color:var(--kf-color-border)}.kf-journey-stepper--card .kf-journey-stepper__step--current .kf-journey-stepper__dot{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);border-color:var(--kf-color-primary);box-shadow:0 0 0 4px var(--kf-color-primary-soft)}.kf-journey-stepper--card .kf-journey-stepper__label{color:var(--kf-color-text-muted)}.kf-journey-stepper--card .kf-journey-stepper__step--done .kf-journey-stepper__label,.kf-journey-stepper--card .kf-journey-stepper__step--current .kf-journey-stepper__label{color:var(--kf-color-ink)}.kf-journey-stepper--card .kf-journey-stepper__line{background:var(--kf-color-border)}.kf-journey-stepper--vertical .kf-journey-stepper__text{display:flex;flex-direction:column;gap:2px;padding-top:3px}.kf-journey-stepper--vertical .kf-journey-stepper__label{font-size:.875rem;font-weight:700;color:var(--kf-color-gray-500);white-space:normal}.kf-journey-stepper--vertical .kf-journey-stepper__date{font-size:.78rem;color:var(--kf-color-gray-500)}.kf-journey-stepper--vertical .kf-journey-stepper__step--pending .kf-journey-stepper__dot{background:var(--kf-color-gray-100);color:var(--kf-color-gray-400)}.kf-journey-stepper--vertical .kf-journey-stepper__step--done .kf-journey-stepper__label,.kf-journey-stepper--vertical .kf-journey-stepper__step--current .kf-journey-stepper__label,.kf-journey-stepper--vertical .kf-journey-stepper__step--delayed .kf-journey-stepper__label{color:var(--kf-color-ink)}.kf-journey-stepper--vertical:not(.kf-journey-stepper--semantic) .kf-journey-stepper__step--done .kf-journey-stepper__dot{background:var(--kf-color-primary);color:var(--kf-color-white);box-shadow:none}.kf-journey-stepper--vertical:not(.kf-journey-stepper--semantic) .kf-journey-stepper__step--current .kf-journey-stepper__dot{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);border-color:transparent;box-shadow:0 0 0 3px var(--kf-color-primary-soft)}.kf-journey-stepper--vertical:not(.kf-journey-stepper--semantic) .kf-journey-stepper__step--delayed .kf-journey-stepper__dot{background:var(--kf-color-gray-100);color:var(--kf-color-gray-400)}.kf-journey-stepper--vertical:not(.kf-journey-stepper--semantic) .kf-journey-stepper__step--current .kf-journey-stepper__date{color:var(--kf-color-primary);font-weight:600}.kf-journey-stepper--semantic .kf-journey-stepper__step--done .kf-journey-stepper__dot{background:var(--kf-color-success);color:var(--kf-color-white);box-shadow:none}.kf-journey-stepper--semantic .kf-journey-stepper__step--current .kf-journey-stepper__dot{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong);border-color:transparent;box-shadow:0 0 0 3px var(--kf-color-warning-soft)}.kf-journey-stepper--semantic .kf-journey-stepper__step--delayed .kf-journey-stepper__dot{background:var(--kf-color-error-soft);color:var(--kf-color-error);border-color:transparent;box-shadow:0 0 0 3px var(--kf-color-error-soft)}.kf-journey-stepper--semantic .kf-journey-stepper__step--delayed .kf-journey-stepper__label{color:var(--kf-color-ink)}.kf-journey-stepper--vertical .kf-journey-stepper__step--done .kf-journey-stepper__date{color:var(--kf-color-gray-500)}.kf-journey-stepper--semantic .kf-journey-stepper__step--current .kf-journey-stepper__date{color:var(--kf-color-warning-strong);font-weight:600}.kf-journey-stepper--semantic .kf-journey-stepper__step--delayed .kf-journey-stepper__date{color:var(--kf-color-error);font-weight:600}\n"] }]
4328
5092
  }], ctorParameters: () => [], propDecorators: { steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], colorScheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorScheme", required: false }] }], currentStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentStep", required: false }] }], stepClick: [{ type: i0.Output, args: ["stepClick"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
4329
5093
 
5094
+ /**
5095
+ * Margen de histéresis (px) para SUBIR de modo. Bajar es inmediato (si no
5096
+ * cabe, no cabe); subir exige este margen extra, así un ancho que oscila
5097
+ * justo en el umbral (scrollbar que aparece y desaparece, un contador que
5098
+ * pasa de 9 a 10) no hace parpadear la barra entre dos modos.
5099
+ */
5100
+ const LIST_TOOLBAR_HYSTERESIS = 24;
5101
+ /**
5102
+ * Decide el modo a partir del modo actual y los anchos medidos. Función pura
5103
+ * para poder probar la histéresis sin layout real.
5104
+ */
5105
+ function resolveListToolbarMode(widths, current, hysteresis = LIST_TOOLBAR_HYSTERESIS) {
5106
+ const { available, full, compact } = widths;
5107
+ const fullThreshold = current === 'full' ? full : full + hysteresis;
5108
+ if (available >= fullThreshold) {
5109
+ return 'full';
5110
+ }
5111
+ const compactThreshold = current === 'panel' ? compact + hysteresis : compact;
5112
+ if (available >= compactThreshold) {
5113
+ return 'compact';
5114
+ }
5115
+ return 'panel';
5116
+ }
5117
+
5118
+ /** Selector de elementos que pueden recibir foco por teclado dentro de un slot. */
5119
+ const FOCUSABLE$1 = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
5120
+ /**
5121
+ * Barra de herramientas de un listado: buscador, filtros y acciones finales
5122
+ * (orden, cambio de vista) en UNA fila, que se adapta al ancho REAL que
5123
+ * tiene disponible — no al del viewport. El mismo listado puede renderizarse
5124
+ * con el menú lateral abierto o cerrado, dentro de un panel o a pantalla
5125
+ * completa, y una `@media` no distingue esos casos; la barra mide su propio
5126
+ * ancho con `ResizeObserver`.
5127
+ *
5128
+ * ## Cuándo usarla y cuándo `kf-filter`
5129
+ * - `kf-list-toolbar`: chips con contador (`kf-filter-chips`) + orden + cambio
5130
+ * de vista + panel de filtros en modo estrecho, fija al hacer scroll y
5131
+ * guiada por el ancho del contenedor.
5132
+ * - `kf-filter`: selects, multi-select y buscador de texto en una fila de
5133
+ * formulario, que se pliega en un `kf-collapsible` a ≤768px de viewport.
5134
+ * Si el listado se filtra con campos, usar `kf-filter`; no se combinan ni
5135
+ * se reimplementan entre sí.
5136
+ *
5137
+ * ## Slots
5138
+ * - `[kf-toolbar-search]`: el buscador (ej. `kf-input icon="search"`). Siempre visible; ocupa el espacio libre.
5139
+ * - `[kf-toolbar-filters]`: filtros rápidos (ej. `kf-filter-chips`).
5140
+ * - `[kf-toolbar-end]`: acciones al final de la fila (ej. `kf-select` de orden, `kf-view-toggle`).
5141
+ *
5142
+ * Cada slot acepta VARIOS elementos con el mismo atributo y los ubica en
5143
+ * fila con `gap`; marcar cada control (no un `<div>` envoltorio sin
5144
+ * `display: flex`, que apilaría sus hijos en bloque).
5145
+ *
5146
+ * La barra ocupa el ancho de su contenedor (`display: block`). Dentro de un
5147
+ * contenedor flex que ajusta su ancho al contenido, darle un ancho
5148
+ * explícito: si el contenedor mide lo que la barra pide, la barra mide lo
5149
+ * que el contenedor le da y el modo nunca puede subir de `panel`.
5150
+ * - default: contenido no visual que debe quedar SIEMPRE presente, como una
5151
+ * región `aria-live` con el conteo de resultados (nunca dentro de un slot
5152
+ * que se oculta).
5153
+ *
5154
+ * ## Modos (`mode`, `modeChange`, atributo `data-mode`)
5155
+ * - `full`: todo cabe en una fila.
5156
+ * - `compact`: cabe en una fila solo con chips más angostos. El host expone
5157
+ * `data-density="compact"` y redefine `--kf-filter-chips-padding-inline`
5158
+ * (12px), que `kf-filter-chips` adopta sin configuración extra.
5159
+ * - `panel`: no cabe. Se ocultan `filters` y `end` y aparece, junto al
5160
+ * buscador, un botón "Filtros" (`filtersLabel`) con un badge de
5161
+ * `activeCount` (filtros/orden distintos del valor por defecto) que emite
5162
+ * `filtersClick` — el consumidor abre su propio panel (típicamente
5163
+ * `kf-drawer role="dialog"` con `kf-option-list`).
5164
+ *
5165
+ * Contrato con el panel de filtros: el consumidor DEBE pasar
5166
+ * `[filtersExpanded]` con el estado de su panel y DEBE cerrarlo al recibir
5167
+ * un `modeChange` distinto de `'panel'`. Al salir de `panel` con el panel
5168
+ * abierto, la barra mueve el foco al primer filtro visible (el botón
5169
+ * "Filtros" ya no existe para recibirlo); si el panel modal siguiera
5170
+ * abierto, ese foco quedaría fuera del diálogo.
5171
+ *
5172
+ * El modo sale de comparar el ancho disponible con el ancho natural de los
5173
+ * slots en cada densidad (más `searchMinWidth` para el buscador), con
5174
+ * histéresis al subir de modo (`LIST_TOOLBAR_HYSTERESIS`) para que no
5175
+ * parpadee en el umbral. Los slots ocultos en modo `panel` se siguen
5176
+ * midiendo (quedan fuera de vista con `visibility: hidden`, sin foco ni
5177
+ * árbol de accesibilidad), así un contador que crece se refleja igual.
5178
+ *
5179
+ * ## Fija (`sticky`)
5180
+ * Con `sticky`, la barra queda pegada arriba al hacer scroll (`stickyTop`
5181
+ * compensa una cabecera fija del shell). Cuando efectivamente queda pegada,
5182
+ * el host recibe la clase `kf-list-toolbar--stuck`: sombra más marcada y
5183
+ * menos alto. "Pegada" = el contenedor con scroll (el ancestro más cercano
5184
+ * con `overflow-y: auto|scroll`, o la ventana) ya se desplazó Y la barra
5185
+ * está en su posición fija. Sin centinela: un centinela por encima de la
5186
+ * barra quedaría recortado cuando la barra es el primer elemento del
5187
+ * contenedor, y la marcaría pegada en reposo. El fondo alrededor de
5188
+ * la barra fija sale de `--kf-list-toolbar-backdrop` (default
5189
+ * `--kf-color-surface`, el fondo habitual del área de contenido).
5190
+ *
5191
+ * Foco tapado (WCAG 2.4.11): una barra fija puede quedar encima del control
5192
+ * que recibe el foco al tabular hacia arriba. El consumidor debe aplicar
5193
+ * `scroll-padding-top` = `stickyTop` + alto de la barra al contenedor que
5194
+ * hace scroll (o a `html` si es la ventana), así el navegador deja ese
5195
+ * margen al llevar un elemento enfocado a la vista. El alto vigente se
5196
+ * publica como señal `height` (px), output `heightChange` y variable CSS
5197
+ * `--kf-list-toolbar-height` en el host (esta última solo la heredan los
5198
+ * descendientes; un ancestro debe usar la señal):
5199
+ *
5200
+ * <main class="content" [style.scroll-padding-top.px]="56 + toolbar.height()">
5201
+ * <kf-list-toolbar #toolbar sticky [stickyTop]="56" ... />
5202
+ * </main>
5203
+ *
5204
+ * <kf-list-toolbar sticky ariaLabel="Filtrar cotizaciones" [activeCount]="activeFilters()"
5205
+ * (filtersClick)="filtersOpen.set(true)" (modeChange)="mode.set($event)">
5206
+ * <span class="sr-only" aria-live="polite">{{ resultsLabel() }}</span>
5207
+ * <kf-input kf-toolbar-search icon="search" ... />
5208
+ * <kf-filter-chips kf-toolbar-filters ... />
5209
+ * <kf-select kf-toolbar-end variant="compact" icon="sort" ariaLabel="Ordenar por" ... />
5210
+ * <kf-view-toggle kf-toolbar-end ... />
5211
+ * </kf-list-toolbar>
5212
+ */
5213
+ class ListToolbar {
5214
+ /** Nombre accesible del landmark de búsqueda (`role="search"`). */
5215
+ ariaLabel = input('Filtrar resultados', /* @ts-ignore */
5216
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
5217
+ /** Fija la barra arriba al hacer scroll. */
5218
+ sticky = input(false, { ...(ngDevMode ? { debugName: "sticky" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5219
+ /** Offset superior de la barra fija: número (px) o longitud CSS (ej. `'var(--shell-header-height)'`). */
5220
+ stickyTop = input(0, /* @ts-ignore */
5221
+ ...(ngDevMode ? [{ debugName: "stickyTop" }] : /* istanbul ignore next */ []));
5222
+ /** Ancho mínimo útil del buscador (px) — entra en el cálculo de si todo cabe en una fila. */
5223
+ searchMinWidth = input(200, /* @ts-ignore */
5224
+ ...(ngDevMode ? [{ debugName: "searchMinWidth" }] : /* istanbul ignore next */ []));
5225
+ /** Texto del botón del modo `panel`. */
5226
+ filtersLabel = input('Filtros', /* @ts-ignore */
5227
+ ...(ngDevMode ? [{ debugName: "filtersLabel" }] : /* istanbul ignore next */ []));
5228
+ /** Cantidad de filtros/orden distintos del valor por defecto; se muestra como badge en el botón del modo `panel`. */
5229
+ activeCount = input(0, /* @ts-ignore */
5230
+ ...(ngDevMode ? [{ debugName: "activeCount" }] : /* istanbul ignore next */ []));
5231
+ /**
5232
+ * `aria-expanded` del botón del modo `panel`: el estado del panel que abre
5233
+ * el consumidor. Además, si es `true` al salir de `panel`, el foco pasa al
5234
+ * primer filtro visible — por eso el consumidor DEBE cerrar su panel en
5235
+ * ese mismo `modeChange` (ver "Contrato con el panel de filtros").
5236
+ */
5237
+ filtersExpanded = input(/* @ts-ignore */
5238
+ ...(ngDevMode ? [undefined, { debugName: "filtersExpanded" }] : /* istanbul ignore next */ []));
5239
+ filtersClick = output();
5240
+ /**
5241
+ * Nuevo modo. Al recibir un valor distinto de `'panel'`, el consumidor
5242
+ * DEBE cerrar su panel de filtros (ver "Contrato con el panel de filtros").
5243
+ */
5244
+ modeChange = output();
5245
+ /** Alto del host (px) cada vez que cambia — ver "Foco tapado" en el JSDoc de la clase. */
5246
+ heightChange = output();
5247
+ modeState = signal('full', /* @ts-ignore */
5248
+ ...(ngDevMode ? [{ debugName: "modeState" }] : /* istanbul ignore next */ []));
5249
+ /** Modo vigente (también disponible como `data-mode` en el host). */
5250
+ mode = this.modeState.asReadonly();
5251
+ stuckState = signal(false, /* @ts-ignore */
5252
+ ...(ngDevMode ? [{ debugName: "stuckState" }] : /* istanbul ignore next */ []));
5253
+ /** `true` mientras la barra fija está efectivamente pegada arriba. */
5254
+ stuck = this.stuckState.asReadonly();
5255
+ heightState = signal(0, /* @ts-ignore */
5256
+ ...(ngDevMode ? [{ debugName: "heightState" }] : /* istanbul ignore next */ []));
5257
+ /** Alto vigente del host en px (barra + padding de la variante fija). */
5258
+ height = this.heightState.asReadonly();
5259
+ stickyTopCss = computed(() => {
5260
+ const top = this.stickyTop();
5261
+ return typeof top === 'number' ? `${top}px` : top;
5262
+ }, /* @ts-ignore */
5263
+ ...(ngDevMode ? [{ debugName: "stickyTopCss" }] : /* istanbul ignore next */ []));
5264
+ filtersAriaLabel = computed(() => {
5265
+ const count = this.activeCount();
5266
+ if (count <= 0) {
5267
+ return undefined;
5268
+ }
5269
+ return `${this.filtersLabel()}, ${count} ${count === 1 ? 'activo' : 'activos'}`;
5270
+ }, /* @ts-ignore */
5271
+ ...(ngDevMode ? [{ debugName: "filtersAriaLabel" }] : /* istanbul ignore next */ []));
5272
+ hostRef = inject(ElementRef);
5273
+ document = inject(DOCUMENT);
5274
+ destroyRef = inject(DestroyRef);
5275
+ barRef = viewChild.required('bar', /* @ts-ignore */
5276
+ ...(ngDevMode ? [{ debugName: "barRef" }] : /* istanbul ignore next */ []));
5277
+ filtersRef = viewChild.required('filters', /* @ts-ignore */
5278
+ ...(ngDevMode ? [{ debugName: "filtersRef" }] : /* istanbul ignore next */ []));
5279
+ endRef = viewChild.required('end', /* @ts-ignore */
5280
+ ...(ngDevMode ? [{ debugName: "endRef" }] : /* istanbul ignore next */ []));
5281
+ filtersButtonRef = viewChild('filtersButton', { ...(ngDevMode ? { debugName: "filtersButtonRef" } : /* istanbul ignore next */ {}), read: ElementRef });
5282
+ /** A dónde mover el foco tras un cambio de modo que desmonta/oculta el elemento enfocado. */
5283
+ pendingFocus = null;
5284
+ /** `requestAnimationFrame` pendiente de `measure()` (coalesce de notificaciones). */
5285
+ measureFrame = null;
5286
+ stuckFrame = null;
5287
+ removeScrollListener = null;
5288
+ constructor() {
5289
+ afterNextRender(() => {
5290
+ // Primera medición síncrona: sin parpadeo del modo inicial.
5291
+ this.measure();
5292
+ this.observeSize();
5293
+ });
5294
+ // `stuck` solo se calcula con `sticky`; se (des)engancha si cambia en runtime.
5295
+ afterRenderEffect(() => {
5296
+ const sticky = this.sticky();
5297
+ untracked(() => this.watchScroll(sticky));
5298
+ });
5299
+ // Conserva el foco cuando el cambio de modo oculta lo que lo tenía.
5300
+ afterRenderEffect(() => {
5301
+ this.mode();
5302
+ untracked(() => this.restorePendingFocus());
5303
+ });
5304
+ this.destroyRef.onDestroy(() => {
5305
+ this.removeScrollListener?.();
5306
+ if (this.measureFrame !== null) {
5307
+ cancelFrame(this.measureFrame);
5308
+ }
5309
+ if (this.stuckFrame !== null) {
5310
+ cancelFrame(this.stuckFrame);
5311
+ }
5312
+ });
5313
+ }
5314
+ onFiltersClick() {
5315
+ this.filtersClick.emit();
5316
+ }
5317
+ /**
5318
+ * Mide el ancho disponible y el natural de cada slot en ambas
5319
+ * densidades, y actualiza el modo. La densidad de medición se fuerza con
5320
+ * `data-measure` (que gana sobre `data-density` en el CSS) y se retira en
5321
+ * el mismo frame: el navegador nunca pinta ese estado intermedio.
5322
+ */
5323
+ measure() {
5324
+ const host = this.hostRef.nativeElement;
5325
+ this.updateHeight(host);
5326
+ const bar = this.barRef().nativeElement;
5327
+ const style = getComputedStyle(bar);
5328
+ const padding = (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0);
5329
+ const available = bar.clientWidth - padding;
5330
+ if (available <= 0) {
5331
+ return;
5332
+ }
5333
+ const gap = parseFloat(style.columnGap) || 0;
5334
+ const required = (density) => {
5335
+ host.setAttribute('data-measure', density);
5336
+ const slots = [this.filtersRef(), this.endRef()].map((ref) => ref.nativeElement.getBoundingClientRect().width);
5337
+ return slots.reduce((total, width) => (width > 0 ? total + gap + width : total), this.searchMinWidth());
5338
+ };
5339
+ const full = required('full');
5340
+ const compact = required('compact');
5341
+ host.removeAttribute('data-measure');
5342
+ this.applyMode(resolveListToolbarMode({ available, full, compact }, this.modeState()));
5343
+ }
5344
+ updateHeight(host) {
5345
+ const height = Math.round(host.getBoundingClientRect().height);
5346
+ if (height !== this.heightState()) {
5347
+ this.heightState.set(height);
5348
+ this.heightChange.emit(height);
5349
+ }
5350
+ }
5351
+ applyMode(next) {
5352
+ const current = this.modeState();
5353
+ if (next === current) {
5354
+ return;
5355
+ }
5356
+ const active = this.document.activeElement;
5357
+ if (next === 'panel' && this.slotsContain(active)) {
5358
+ this.pendingFocus = 'filters-button';
5359
+ }
5360
+ else if (current === 'panel' &&
5361
+ // El botón "Filtros" se desmonta al salir de `panel`. Si tenía el foco,
5362
+ // o si el panel que abrió sigue abierto (el drawer devolvería el foco
5363
+ // a ese botón ya desmontado y caería en <body>), el foco pasa al
5364
+ // primer filtro visible.
5365
+ ((active && this.filtersButtonRef()?.nativeElement.contains(active)) ||
5366
+ this.filtersExpanded() === true)) {
5367
+ this.pendingFocus = 'slots';
5368
+ }
5369
+ this.modeState.set(next);
5370
+ this.modeChange.emit(next);
5371
+ }
5372
+ slotsContain(element) {
5373
+ return (!!element &&
5374
+ (this.filtersRef().nativeElement.contains(element) ||
5375
+ this.endRef().nativeElement.contains(element)));
5376
+ }
5377
+ restorePendingFocus() {
5378
+ const target = this.pendingFocus;
5379
+ this.pendingFocus = null;
5380
+ if (target === 'filters-button') {
5381
+ this.filtersButtonRef()?.nativeElement.querySelector('button')?.focus();
5382
+ }
5383
+ else if (target === 'slots') {
5384
+ const first = this.filtersRef().nativeElement.querySelector(FOCUSABLE$1) ??
5385
+ this.endRef().nativeElement.querySelector(FOCUSABLE$1);
5386
+ first?.focus();
5387
+ }
5388
+ }
5389
+ observeSize() {
5390
+ if (typeof ResizeObserver === 'undefined') {
5391
+ return;
5392
+ }
5393
+ // Varias notificaciones en el mismo frame (barra, slots, host durante la
5394
+ // transición de padding de `stuck`) se resuelven en UNA medición por
5395
+ // frame: `heightChange` no se emite en cada paso intermedio.
5396
+ const observer = new ResizeObserver(() => this.scheduleMeasure());
5397
+ // El host también: su alto cambia con `sticky` (padding) sin que cambie la barra.
5398
+ observer.observe(this.hostRef.nativeElement);
5399
+ observer.observe(this.barRef().nativeElement);
5400
+ observer.observe(this.filtersRef().nativeElement);
5401
+ observer.observe(this.endRef().nativeElement);
5402
+ this.destroyRef.onDestroy(() => observer.disconnect());
5403
+ }
5404
+ scheduleMeasure() {
5405
+ if (this.measureFrame !== null) {
5406
+ return;
5407
+ }
5408
+ this.measureFrame = requestFrame(() => {
5409
+ this.measureFrame = null;
5410
+ this.measure();
5411
+ });
5412
+ }
5413
+ /** Engancha (o suelta) el cálculo de `stuck` al scroll del contenedor. */
5414
+ watchScroll(sticky) {
5415
+ this.removeScrollListener?.();
5416
+ this.removeScrollListener = null;
5417
+ if (!sticky) {
5418
+ this.stuckState.set(false);
5419
+ return;
5420
+ }
5421
+ const container = scrollParent(this.hostRef.nativeElement);
5422
+ const target = container ?? this.document.defaultView ?? window;
5423
+ const onScroll = () => {
5424
+ if (this.stuckFrame === null) {
5425
+ this.stuckFrame = requestFrame(() => {
5426
+ this.stuckFrame = null;
5427
+ this.updateStuck(container);
5428
+ });
5429
+ }
5430
+ };
5431
+ target.addEventListener('scroll', onScroll, { passive: true });
5432
+ this.removeScrollListener = () => target.removeEventListener('scroll', onScroll);
5433
+ this.updateStuck(container);
5434
+ }
5435
+ /**
5436
+ * Pegada = el contenedor ya se desplazó Y la barra está en su posición
5437
+ * fija (`top` resuelto, con medio píxel de tolerancia por subpíxeles).
5438
+ * Sin la primera condición, una barra que es el primer elemento del
5439
+ * contenedor se leería pegada en reposo.
5440
+ */
5441
+ updateStuck(container) {
5442
+ const host = this.hostRef.nativeElement;
5443
+ const view = this.document.defaultView;
5444
+ const scrolled = container ? container.scrollTop > 0 : (view?.scrollY ?? 0) > 0;
5445
+ const containerTop = container
5446
+ ? container.getBoundingClientRect().top + container.clientTop
5447
+ : 0;
5448
+ const stickyTop = parseFloat(getComputedStyle(host).top) || 0;
5449
+ const offset = host.getBoundingClientRect().top - containerTop;
5450
+ this.stuckState.set(scrolled && offset <= stickyTop + 0.5);
5451
+ }
5452
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ListToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
5453
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ListToolbar, isStandalone: true, selector: "kf-list-toolbar", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, stickyTop: { classPropertyName: "stickyTop", publicName: "stickyTop", isSignal: true, isRequired: false, transformFunction: null }, searchMinWidth: { classPropertyName: "searchMinWidth", publicName: "searchMinWidth", isSignal: true, isRequired: false, transformFunction: null }, filtersLabel: { classPropertyName: "filtersLabel", publicName: "filtersLabel", isSignal: true, isRequired: false, transformFunction: null }, activeCount: { classPropertyName: "activeCount", publicName: "activeCount", isSignal: true, isRequired: false, transformFunction: null }, filtersExpanded: { classPropertyName: "filtersExpanded", publicName: "filtersExpanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filtersClick: "filtersClick", modeChange: "modeChange", heightChange: "heightChange" }, host: { properties: { "attr.data-mode": "mode()", "attr.data-density": "mode() === 'full' ? 'full' : 'compact'", "class.kf-list-toolbar--sticky": "sticky()", "class.kf-list-toolbar--stuck": "sticky() && stuck()", "style.--kf-list-toolbar-top": "stickyTopCss()", "style.--kf-list-toolbar-search-min": "searchMinWidth() + 'px'", "style.--kf-list-toolbar-height": "height() + 'px'" } }, viewQueries: [{ propertyName: "barRef", first: true, predicate: ["bar"], descendants: true, isSignal: true }, { propertyName: "filtersRef", first: true, predicate: ["filters"], descendants: true, isSignal: true }, { propertyName: "endRef", first: true, predicate: ["end"], descendants: true, isSignal: true }, { propertyName: "filtersButtonRef", first: true, predicate: ["filtersButton"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div #bar class=\"kf-list-toolbar__bar\" role=\"search\" [attr.aria-label]=\"ariaLabel()\">\n <ng-content />\n <div class=\"kf-list-toolbar__search\"><ng-content select=\"[kf-toolbar-search]\" /></div>\n @if (mode() === 'panel') {\n <kf-button\n #filtersButton\n class=\"kf-list-toolbar__filters-button\"\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n icon=\"filter\"\n ariaHaspopup=\"dialog\"\n [ariaExpanded]=\"filtersExpanded()\"\n [ariaLabel]=\"filtersAriaLabel()\"\n (click)=\"onFiltersClick()\"\n >\n {{ filtersLabel() }}\n @if (activeCount() > 0) {\n <kf-badge class=\"kf-list-toolbar__filters-count\" aria-hidden=\"true\">{{\n activeCount()\n }}</kf-badge>\n }\n </kf-button>\n }\n <!-- `display: contents` en full/compact (los slots son \u00EDtems de la fila);\n en panel, caja de 0x0 que recorta: los slots se siguen midiendo sin\n ensanchar la p\u00E1gina ni ser alcanzables. -->\n <div class=\"kf-list-toolbar__collapsible\">\n <div #filters class=\"kf-list-toolbar__filters kf-list-toolbar__slot\">\n <ng-content select=\"[kf-toolbar-filters]\" />\n </div>\n <div #end class=\"kf-list-toolbar__end kf-list-toolbar__slot\">\n <ng-content select=\"[kf-toolbar-end]\" />\n </div>\n </div>\n</div>\n", styles: [":host{display:block;position:relative;font-family:var(--kf-font-body)}:host(.kf-list-toolbar--sticky){position:sticky;top:var(--kf-list-toolbar-top, 0px);z-index:var(--kf-list-toolbar-z-index, 30);padding-block:var(--kf-space-sm);background:var(--kf-list-toolbar-backdrop, var(--kf-color-surface))}.kf-list-toolbar__bar{position:relative;display:flex;align-items:center;gap:var(--kf-space-sm) var(--kf-space-md);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-md);box-shadow:var(--kf-shadow-xs);transition:box-shadow .14s,padding .14s}:host(.kf-list-toolbar--stuck) .kf-list-toolbar__bar{padding-block:6px;box-shadow:var(--kf-shadow-md)}.kf-list-toolbar__search{flex:1 1 auto;min-width:var(--kf-list-toolbar-search-min, 200px)}.kf-list-toolbar__slot{flex:none;width:max-content;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-list-toolbar__slot:empty{display:none}.kf-list-toolbar__end{margin-inline-start:auto}.kf-list-toolbar__filters-button{flex:none}.kf-list-toolbar__filters-count{margin-inline-start:2px}:host([data-density=\"compact\"]){--kf-filter-chips-padding-inline: 12px}:host([data-measure=\"full\"]){--kf-filter-chips-padding-inline: var(--kf-space-md)}:host([data-measure=\"compact\"]){--kf-filter-chips-padding-inline: 12px}.kf-list-toolbar__collapsible{display:contents}:host([data-mode=\"panel\"]) .kf-list-toolbar__collapsible{display:block;position:absolute;top:0;left:0;width:0;height:0;overflow:hidden;visibility:hidden;pointer-events:none}:host([data-mode=\"panel\"]) .kf-list-toolbar__search{flex:1 1 0;min-width:0}@media(pointer:coarse),(max-width:760px){.kf-list-toolbar__filters-button{--kf-button-min-height: 44px}}@media(prefers-reduced-motion:reduce){.kf-list-toolbar__bar{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"] }] });
5454
+ }
5455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ListToolbar, decorators: [{
5456
+ type: Component,
5457
+ args: [{ selector: 'kf-list-toolbar', imports: [Badge, Button], host: {
5458
+ '[attr.data-mode]': 'mode()',
5459
+ '[attr.data-density]': "mode() === 'full' ? 'full' : 'compact'",
5460
+ '[class.kf-list-toolbar--sticky]': 'sticky()',
5461
+ '[class.kf-list-toolbar--stuck]': 'sticky() && stuck()',
5462
+ '[style.--kf-list-toolbar-top]': 'stickyTopCss()',
5463
+ '[style.--kf-list-toolbar-search-min]': "searchMinWidth() + 'px'",
5464
+ '[style.--kf-list-toolbar-height]': "height() + 'px'",
5465
+ }, template: "<div #bar class=\"kf-list-toolbar__bar\" role=\"search\" [attr.aria-label]=\"ariaLabel()\">\n <ng-content />\n <div class=\"kf-list-toolbar__search\"><ng-content select=\"[kf-toolbar-search]\" /></div>\n @if (mode() === 'panel') {\n <kf-button\n #filtersButton\n class=\"kf-list-toolbar__filters-button\"\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n icon=\"filter\"\n ariaHaspopup=\"dialog\"\n [ariaExpanded]=\"filtersExpanded()\"\n [ariaLabel]=\"filtersAriaLabel()\"\n (click)=\"onFiltersClick()\"\n >\n {{ filtersLabel() }}\n @if (activeCount() > 0) {\n <kf-badge class=\"kf-list-toolbar__filters-count\" aria-hidden=\"true\">{{\n activeCount()\n }}</kf-badge>\n }\n </kf-button>\n }\n <!-- `display: contents` en full/compact (los slots son \u00EDtems de la fila);\n en panel, caja de 0x0 que recorta: los slots se siguen midiendo sin\n ensanchar la p\u00E1gina ni ser alcanzables. -->\n <div class=\"kf-list-toolbar__collapsible\">\n <div #filters class=\"kf-list-toolbar__filters kf-list-toolbar__slot\">\n <ng-content select=\"[kf-toolbar-filters]\" />\n </div>\n <div #end class=\"kf-list-toolbar__end kf-list-toolbar__slot\">\n <ng-content select=\"[kf-toolbar-end]\" />\n </div>\n </div>\n</div>\n", styles: [":host{display:block;position:relative;font-family:var(--kf-font-body)}:host(.kf-list-toolbar--sticky){position:sticky;top:var(--kf-list-toolbar-top, 0px);z-index:var(--kf-list-toolbar-z-index, 30);padding-block:var(--kf-space-sm);background:var(--kf-list-toolbar-backdrop, var(--kf-color-surface))}.kf-list-toolbar__bar{position:relative;display:flex;align-items:center;gap:var(--kf-space-sm) var(--kf-space-md);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-md);box-shadow:var(--kf-shadow-xs);transition:box-shadow .14s,padding .14s}:host(.kf-list-toolbar--stuck) .kf-list-toolbar__bar{padding-block:6px;box-shadow:var(--kf-shadow-md)}.kf-list-toolbar__search{flex:1 1 auto;min-width:var(--kf-list-toolbar-search-min, 200px)}.kf-list-toolbar__slot{flex:none;width:max-content;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-list-toolbar__slot:empty{display:none}.kf-list-toolbar__end{margin-inline-start:auto}.kf-list-toolbar__filters-button{flex:none}.kf-list-toolbar__filters-count{margin-inline-start:2px}:host([data-density=\"compact\"]){--kf-filter-chips-padding-inline: 12px}:host([data-measure=\"full\"]){--kf-filter-chips-padding-inline: var(--kf-space-md)}:host([data-measure=\"compact\"]){--kf-filter-chips-padding-inline: 12px}.kf-list-toolbar__collapsible{display:contents}:host([data-mode=\"panel\"]) .kf-list-toolbar__collapsible{display:block;position:absolute;top:0;left:0;width:0;height:0;overflow:hidden;visibility:hidden;pointer-events:none}:host([data-mode=\"panel\"]) .kf-list-toolbar__search{flex:1 1 0;min-width:0}@media(pointer:coarse),(max-width:760px){.kf-list-toolbar__filters-button{--kf-button-min-height: 44px}}@media(prefers-reduced-motion:reduce){.kf-list-toolbar__bar{transition:none}}\n"] }]
5466
+ }], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], stickyTop: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyTop", required: false }] }], searchMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchMinWidth", required: false }] }], filtersLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtersLabel", required: false }] }], activeCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeCount", required: false }] }], filtersExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtersExpanded", required: false }] }], filtersClick: [{ type: i0.Output, args: ["filtersClick"] }], modeChange: [{ type: i0.Output, args: ["modeChange"] }], heightChange: [{ type: i0.Output, args: ["heightChange"] }], barRef: [{ type: i0.ViewChild, args: ['bar', { isSignal: true }] }], filtersRef: [{ type: i0.ViewChild, args: ['filters', { isSignal: true }] }], endRef: [{ type: i0.ViewChild, args: ['end', { isSignal: true }] }], filtersButtonRef: [{ type: i0.ViewChild, args: ['filtersButton', { ...{
5467
+ read: ElementRef,
5468
+ }, isSignal: true }] }] } });
5469
+ /** Ancestro más cercano que hace scroll vertical, o `null` si es la ventana. */
5470
+ function scrollParent(element) {
5471
+ let parent = element.parentElement;
5472
+ while (parent && parent !== element.ownerDocument.documentElement) {
5473
+ const overflowY = getComputedStyle(parent).overflowY;
5474
+ if (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') {
5475
+ return parent;
5476
+ }
5477
+ parent = parent.parentElement;
5478
+ }
5479
+ return null;
5480
+ }
5481
+ /** `requestAnimationFrame` con respaldo para entornos sin él (SSR, algunos runners de test). */
5482
+ function requestFrame(callback) {
5483
+ return typeof requestAnimationFrame === 'function'
5484
+ ? requestAnimationFrame(callback)
5485
+ : setTimeout(callback, 16);
5486
+ }
5487
+ function cancelFrame(handle) {
5488
+ if (typeof cancelAnimationFrame === 'function') {
5489
+ cancelAnimationFrame(handle);
5490
+ }
5491
+ else {
5492
+ clearTimeout(handle);
5493
+ }
5494
+ }
5495
+
4330
5496
  /**
4331
5497
  * Card de estadística/KPI: label, valor grande, ícono opcional en la esquina
4332
5498
  * superior derecha, badge de esquina opcional (pegado al valor) y una nota
@@ -4456,6 +5622,8 @@ function installModalAwareOverlayContainer(container) {
4456
5622
  }
4457
5623
 
4458
5624
  let modalId = 0;
5625
+ /** Controles que pueden recibir el foco inicial. */
5626
+ const FOCUSABLE = 'input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
4459
5627
  /**
4460
5628
  * Diálogo centrado sobre un backdrop: confirmaciones destructivas, formularios
4461
5629
  * cortos, decisiones que bloquean el flujo.
@@ -4493,6 +5661,27 @@ let modalId = 0;
4493
5661
  *
4494
5662
  * <kf-link footer class="kf-modal__footer-start" disabled>Ver documento</kf-link>
4495
5663
  * <kf-button footer (click)="open.set(false)">Cerrar</kf-button>
5664
+ *
5665
+ * ## Modal de listado (alto acotado, filtros fijos)
5666
+ * Para un diálogo con mucho contenido (ej. elegir un ítem entre cientos):
5667
+ * - `maxHeight` (ej. `"85vh"`) acota el panel; solo `.kf-modal__body` se
5668
+ * desplaza, el encabezado, la zona `[kf-modal-toolbar]` y el footer
5669
+ * quedan fijos.
5670
+ * - `[kf-modal-toolbar]`: zona FIJA entre el encabezado y el cuerpo, para
5671
+ * buscador y filtros.
5672
+ * - `[kf-modal-meta]`: dato corto junto al título (ej. "12 de 86").
5673
+ * - `fullscreenOnMobile`: hasta 640px de viewport ocupa toda la pantalla
5674
+ * (`100dvh`, sin radio).
5675
+ * - `initialFocus`: selector CSS del elemento que recibe el foco al abrir
5676
+ * (si apunta a un contenedor, se enfoca su primer control). Sin él, el
5677
+ * `<dialog>` nativo decide (`autofocus` o el primer control).
5678
+ *
5679
+ * <kf-modal [(open)]="open" title="Cambiar cotización" width="1040px"
5680
+ * maxHeight="85vh" fullscreenOnMobile initialFocus=".search">
5681
+ * <span kf-modal-meta aria-live="polite">12 de 86</span>
5682
+ * <div kf-modal-toolbar><kf-input class="search" … /></div>
5683
+ * <ul>…</ul>
5684
+ * </kf-modal>
4496
5685
  */
4497
5686
  class Modal {
4498
5687
  title = input('', /* @ts-ignore */
@@ -4508,9 +5697,20 @@ class Modal {
4508
5697
  * angostas, mismo `min()` de siempre. */
4509
5698
  width = input(null, /* @ts-ignore */
4510
5699
  ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
5700
+ /** Alto máximo del panel (ej. `"85vh"`); con valor, solo el cuerpo se
5701
+ * desplaza. Sin valor (default) el panel crece con su contenido. */
5702
+ maxHeight = input(null, /* @ts-ignore */
5703
+ ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
5704
+ /** Hasta 640px de viewport, ocupa toda la pantalla (100dvh, sin radio). */
5705
+ fullscreenOnMobile = input(false, { ...(ngDevMode ? { debugName: "fullscreenOnMobile" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5706
+ /** Selector CSS (dentro del panel) del elemento a enfocar al abrir. */
5707
+ initialFocus = input(null, /* @ts-ignore */
5708
+ ...(ngDevMode ? [{ debugName: "initialFocus" }] : /* istanbul ignore next */ []));
4511
5709
  titleId = `kf-modal-title-${modalId++}`;
4512
5710
  dialog = viewChild('dialog', /* @ts-ignore */
4513
5711
  ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
5712
+ panel = viewChild('panel', /* @ts-ignore */
5713
+ ...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
4514
5714
  document = inject(DOCUMENT);
4515
5715
  overlayContainer = inject(OverlayContainer);
4516
5716
  constructor() {
@@ -4534,6 +5734,7 @@ class Modal {
4534
5734
  else {
4535
5735
  el.open = true;
4536
5736
  }
5737
+ this.focusInitial();
4537
5738
  }
4538
5739
  else if (!this.open() && el.open) {
4539
5740
  if (typeof el.close === 'function') {
@@ -4565,6 +5766,24 @@ class Modal {
4565
5766
  }
4566
5767
  });
4567
5768
  }
5769
+ /**
5770
+ * Enfoca `initialFocus` DESPUÉS de `showModal()` (que ya movió el foco al
5771
+ * primer control): un elemento dentro de un `<dialog>` cerrado no se puede
5772
+ * enfocar. Si el selector apunta a un host (ej. `kf-input`), se enfoca su
5773
+ * primer control interno.
5774
+ */
5775
+ focusInitial() {
5776
+ const selector = this.initialFocus();
5777
+ const panel = this.panel()?.nativeElement;
5778
+ if (!selector || !panel) {
5779
+ return;
5780
+ }
5781
+ const target = panel.querySelector(selector);
5782
+ const focusable = target?.matches(FOCUSABLE)
5783
+ ? target
5784
+ : target?.querySelector(FOCUSABLE);
5785
+ focusable?.focus();
5786
+ }
4568
5787
  /** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
4569
5788
  onCancel(event) {
4570
5789
  if (!this.dismissible()) {
@@ -4586,12 +5805,12 @@ class Modal {
4586
5805
  }
4587
5806
  }
4588
5807
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Modal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4589
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Modal, isStandalone: true, selector: "kf-modal", inputs: { 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 }, width: { classPropertyName: "width", publicName: "width", 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-modal\"\n [style.--kf-modal-width]=\"width()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-modal__panel\">\n <div class=\"kf-modal__head\">\n @if (title()) {\n <h2 class=\"kf-modal__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-modal__close\"\n aria-label=\"Cerrar di\u00E1logo\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n <div class=\"kf-modal__body\"><ng-content /></div>\n <div class=\"kf-modal__footer\"><ng-content select=\"[footer]\" /></div>\n </div>\n }\n</dialog>\n", styles: [".kf-modal{width:min(var(--kf-modal-width, 480px),calc(100vw - 2 * var(--kf-space-md)));max-height:calc(100vh - 2 * var(--kf-space-xl));padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-modal::backdrop{background:#171d2573}.kf-modal__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-lg);background:var(--kf-color-white);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-lg)}.kf-modal__head{display:flex;align-items:flex-start;gap:var(--kf-space-sm)}.kf-modal__title{flex:1;margin:0;font-family:var(--kf-font-title);font-size:1.375rem;font-weight:700;color:var(--kf-color-ink)}.kf-modal__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-xs);cursor:pointer}.kf-modal__close:hover{color:var(--kf-color-ink)}.kf-modal__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-modal__body{color:var(--kf-color-text);overflow-y:auto}.kf-modal__footer:empty{display:none}.kf-modal__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm)}.kf-modal__footer-start{margin-right:auto}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
5808
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Modal, isStandalone: true, selector: "kf-modal", inputs: { 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 }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, fullscreenOnMobile: { classPropertyName: "fullscreenOnMobile", publicName: "fullscreenOnMobile", isSignal: true, isRequired: false, transformFunction: null }, initialFocus: { classPropertyName: "initialFocus", publicName: "initialFocus", 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 }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-modal\"\n [class.kf-modal--bounded]=\"!!maxHeight()\"\n [class.kf-modal--fullscreen-mobile]=\"fullscreenOnMobile()\"\n [style.--kf-modal-width]=\"width()\"\n [style.--kf-modal-max-height]=\"maxHeight()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div #panel class=\"kf-modal__panel\">\n <div class=\"kf-modal__head\">\n <div class=\"kf-modal__heading\">\n @if (title()) {\n <h2 class=\"kf-modal__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n <ng-content select=\"[kf-modal-meta]\" />\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-modal__close\"\n aria-label=\"Cerrar di\u00E1logo\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n <div class=\"kf-modal__toolbar\"><ng-content select=\"[kf-modal-toolbar]\" /></div>\n <div class=\"kf-modal__body\"><ng-content /></div>\n <div class=\"kf-modal__footer\"><ng-content select=\"[footer]\" /></div>\n </div>\n }\n</dialog>\n", styles: [".kf-modal{width:min(var(--kf-modal-width, 480px),calc(100vw - 2 * var(--kf-space-md)));max-height:calc(100vh - 2 * var(--kf-space-xl));padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-modal::backdrop{background:#171d2573}.kf-modal__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-lg);background:var(--kf-color-white);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-lg)}.kf-modal__head{display:flex;align-items:flex-start;gap:var(--kf-space-sm)}.kf-modal__heading{flex:1;min-width:0;display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--kf-space-sm);row-gap:var(--kf-space-xs)}.kf-modal__title{margin:0;font-family:var(--kf-font-title);font-size:1.375rem;font-weight:700;color:var(--kf-color-ink)}.kf-modal__close{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:32px;min-height:32px;margin:-4px -6px 0 0;padding:2px;border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-xs);cursor:pointer}.kf-modal__close:hover{color:var(--kf-color-ink);background:var(--kf-color-surface)}.kf-modal__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-modal__toolbar:empty{display:none}.kf-modal__body{color:var(--kf-color-text);overflow-y:auto}.kf-modal__footer:empty{display:none}.kf-modal__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm)}.kf-modal__footer-start{margin-right:auto}.kf-modal--bounded{max-height:min(var(--kf-modal-max-height),calc(100dvh - 2 * var(--kf-space-md)))}.kf-modal--bounded .kf-modal__panel{box-sizing:border-box;max-height:inherit}.kf-modal--bounded .kf-modal__body{flex:1 1 auto;min-height:0;overscroll-behavior:contain}@media(max-width:640px){.kf-modal--fullscreen-mobile{width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;margin:0}.kf-modal--fullscreen-mobile .kf-modal__panel{box-sizing:border-box;height:100%;max-height:100%;padding:var(--kf-space-md);gap:var(--kf-space-sm);border-radius:0;box-shadow:none}.kf-modal--fullscreen-mobile .kf-modal__body{flex:1 1 auto;min-height:0}.kf-modal--fullscreen-mobile .kf-modal__title{font-size:var(--kf-font-size-title-sm)}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
4590
5809
  }
4591
5810
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Modal, decorators: [{
4592
5811
  type: Component,
4593
- args: [{ selector: 'kf-modal', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-modal\"\n [style.--kf-modal-width]=\"width()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-modal__panel\">\n <div class=\"kf-modal__head\">\n @if (title()) {\n <h2 class=\"kf-modal__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-modal__close\"\n aria-label=\"Cerrar di\u00E1logo\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n <div class=\"kf-modal__body\"><ng-content /></div>\n <div class=\"kf-modal__footer\"><ng-content select=\"[footer]\" /></div>\n </div>\n }\n</dialog>\n", styles: [".kf-modal{width:min(var(--kf-modal-width, 480px),calc(100vw - 2 * var(--kf-space-md)));max-height:calc(100vh - 2 * var(--kf-space-xl));padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-modal::backdrop{background:#171d2573}.kf-modal__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-lg);background:var(--kf-color-white);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-lg)}.kf-modal__head{display:flex;align-items:flex-start;gap:var(--kf-space-sm)}.kf-modal__title{flex:1;margin:0;font-family:var(--kf-font-title);font-size:1.375rem;font-weight:700;color:var(--kf-color-ink)}.kf-modal__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-xs);cursor:pointer}.kf-modal__close:hover{color:var(--kf-color-ink)}.kf-modal__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-modal__body{color:var(--kf-color-text);overflow-y:auto}.kf-modal__footer:empty{display:none}.kf-modal__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm)}.kf-modal__footer-start{margin-right:auto}\n"] }]
4594
- }], ctorParameters: () => [], propDecorators: { 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 }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], onHostClick: [{
5812
+ args: [{ selector: 'kf-modal', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-modal\"\n [class.kf-modal--bounded]=\"!!maxHeight()\"\n [class.kf-modal--fullscreen-mobile]=\"fullscreenOnMobile()\"\n [style.--kf-modal-width]=\"width()\"\n [style.--kf-modal-max-height]=\"maxHeight()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div #panel class=\"kf-modal__panel\">\n <div class=\"kf-modal__head\">\n <div class=\"kf-modal__heading\">\n @if (title()) {\n <h2 class=\"kf-modal__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n <ng-content select=\"[kf-modal-meta]\" />\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-modal__close\"\n aria-label=\"Cerrar di\u00E1logo\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n <div class=\"kf-modal__toolbar\"><ng-content select=\"[kf-modal-toolbar]\" /></div>\n <div class=\"kf-modal__body\"><ng-content /></div>\n <div class=\"kf-modal__footer\"><ng-content select=\"[footer]\" /></div>\n </div>\n }\n</dialog>\n", styles: [".kf-modal{width:min(var(--kf-modal-width, 480px),calc(100vw - 2 * var(--kf-space-md)));max-height:calc(100vh - 2 * var(--kf-space-xl));padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-modal::backdrop{background:#171d2573}.kf-modal__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-lg);background:var(--kf-color-white);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-lg)}.kf-modal__head{display:flex;align-items:flex-start;gap:var(--kf-space-sm)}.kf-modal__heading{flex:1;min-width:0;display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--kf-space-sm);row-gap:var(--kf-space-xs)}.kf-modal__title{margin:0;font-family:var(--kf-font-title);font-size:1.375rem;font-weight:700;color:var(--kf-color-ink)}.kf-modal__close{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:32px;min-height:32px;margin:-4px -6px 0 0;padding:2px;border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-xs);cursor:pointer}.kf-modal__close:hover{color:var(--kf-color-ink);background:var(--kf-color-surface)}.kf-modal__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-modal__toolbar:empty{display:none}.kf-modal__body{color:var(--kf-color-text);overflow-y:auto}.kf-modal__footer:empty{display:none}.kf-modal__footer{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm)}.kf-modal__footer-start{margin-right:auto}.kf-modal--bounded{max-height:min(var(--kf-modal-max-height),calc(100dvh - 2 * var(--kf-space-md)))}.kf-modal--bounded .kf-modal__panel{box-sizing:border-box;max-height:inherit}.kf-modal--bounded .kf-modal__body{flex:1 1 auto;min-height:0;overscroll-behavior:contain}@media(max-width:640px){.kf-modal--fullscreen-mobile{width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;margin:0}.kf-modal--fullscreen-mobile .kf-modal__panel{box-sizing:border-box;height:100%;max-height:100%;padding:var(--kf-space-md);gap:var(--kf-space-sm);border-radius:0;box-shadow:none}.kf-modal--fullscreen-mobile .kf-modal__body{flex:1 1 auto;min-height:0}.kf-modal--fullscreen-mobile .kf-modal__title{font-size:var(--kf-font-size-title-sm)}}\n"] }]
5813
+ }], ctorParameters: () => [], propDecorators: { 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 }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], fullscreenOnMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullscreenOnMobile", required: false }] }], initialFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFocus", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onHostClick: [{
4595
5814
  type: HostListener,
4596
5815
  args: ['click', ['$event']]
4597
5816
  }] } });
@@ -4623,6 +5842,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4623
5842
  }, template: "@if (title() || subtitle()) {\n <span class=\"kf-note-block__head\">\n @if (title(); as heading) {\n <span class=\"kf-note-block__title kf-overline-text\">{{ heading }}</span>\n }\n @if (subtitle(); as sub) {\n <span class=\"kf-note-block__subtitle kf-caption-text\">{{ sub }}</span>\n }\n </span>\n}\n\n<span class=\"kf-note-block__text\"><ng-content /></span>\n\n@if (signature(); as author) {\n <span class=\"kf-note-block__signature kf-caption-text\">{{ author }}</span>\n}\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-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;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);border-left:3px solid var(--kf-color-border-strong);background:var(--kf-color-surface);min-width:0}:host(.kf-note-block--info){border-left-color:var(--kf-color-info);background:var(--kf-color-info-soft)}:host(.kf-note-block--ok){border-left-color:var(--kf-color-success);background:var(--kf-color-success-soft)}:host(.kf-note-block--warn){border-left-color:var(--kf-color-warning);background:var(--kf-color-warning-soft)}:host(.kf-note-block--error){border-left-color:var(--kf-color-error);background:var(--kf-color-error-soft)}.kf-note-block__head{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--kf-space-sm)}.kf-note-block__title{font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-note-block__text{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);color:var(--kf-color-text);white-space:pre-line;overflow-wrap:anywhere}\n"] }]
4624
5843
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], signature: [{ type: i0.Input, args: [{ isSignal: true, alias: "signature", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
4625
5844
 
5845
+ let nextId = 0;
5846
+ /**
5847
+ * Lista vertical de opciones de UNA sola selección para paneles (ej. las
5848
+ * secciones "Estado", "Ordenar por" y "Vista" de un panel de filtros): una
5849
+ * fila de al menos 44px por opción, contador opcional a la derecha y la
5850
+ * opción elegida resaltada con `--kf-color-primary-soft`.
5851
+ *
5852
+ * Es la contraparte en panel de `kf-filter-chips`: misma función (filtrar u
5853
+ * ordenar lo que se muestra) cuando no hay ancho para una fila de chips. No
5854
+ * es `kf-selectable-card-group`: aquel responde una pregunta de un flujo
5855
+ * ("¿con qué método pagas?") con affordance de radio y cards grandes; esta
5856
+ * lista ajusta una vista, con filas densas y contador.
5857
+ *
5858
+ * Semántica: `role="radiogroup"` + `role="radio"` con `aria-checked`, no
5859
+ * botones con `aria-pressed`. Las opciones son mutuamente excluyentes (eso
5860
+ * es un radio; `aria-pressed` describe toggles independientes), el lector
5861
+ * de pantalla anuncia la posición ("2 de 4") y cada grupo es UNA sola
5862
+ * parada de Tab: un panel con tres secciones se recorre con 3 Tabs en vez
5863
+ * de uno por opción. Dentro del grupo, flechas arriba/abajo y Home/End
5864
+ * mueven el foco y seleccionan (`handleArrowKeyNavigation`, mismo helper que
5865
+ * `kf-tabs`/`kf-selectable-card-group`).
5866
+ *
5867
+ * La opción elegida lleva además un check (no solo color), para que el
5868
+ * estado no dependa de distinguir el tinte `primary-soft` (WCAG 1.4.1).
5869
+ *
5870
+ * El `@for` hace `track` por índice: cuando el padre re-renderiza (ej. los
5871
+ * contadores cambian al elegir), los botones existentes se reutilizan y el
5872
+ * foco no se pierde. Por índice y no por `value` para no fallar (NG0955)
5873
+ * si dos opciones repiten `value`; aun así los values deben ser únicos:
5874
+ * con repetidos, ambas filas se marcan elegidas.
5875
+ *
5876
+ * `label` dibuja el título de la sección (chico, en mayúsculas) y nombra el
5877
+ * grupo vía `aria-labelledby`; sin título visible, pasar `ariaLabel`.
5878
+ *
5879
+ * <kf-option-list label="Estado"
5880
+ * [options]="[{ value: '', label: 'Todas', count: 12 },
5881
+ * { value: 'pending', label: 'Pendientes', count: 3 }]"
5882
+ * [value]="status()"
5883
+ * (valueChange)="status.set($event)" />
5884
+ */
5885
+ class OptionList {
5886
+ options = input.required(/* @ts-ignore */
5887
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
5888
+ value = input('', /* @ts-ignore */
5889
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5890
+ /** Título visible de la sección; nombra el grupo. */
5891
+ label = input(/* @ts-ignore */
5892
+ ...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
5893
+ /** Nombre accesible del grupo cuando no hay `label` visible. */
5894
+ ariaLabel = input(/* @ts-ignore */
5895
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
5896
+ valueChange = output();
5897
+ instanceId = `kf-option-list-${nextId++}`;
5898
+ labelId = `${this.instanceId}-label`;
5899
+ /**
5900
+ * Prefijo de id de cada opción — único por instancia (dos listas con los
5901
+ * mismos values no colisionan). El id termina en el ÍNDICE, no en el
5902
+ * value: un value con espacios u otros caracteres daría un id inválido.
5903
+ */
5904
+ optionIdPrefix = `${this.instanceId}-option-`;
5905
+ normalizedOptions = computed(() => this.options().map((option) => typeof option === 'string' ? { value: option, label: option } : option), /* @ts-ignore */
5906
+ ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
5907
+ /**
5908
+ * Opción que recibe `tabindex="0"`: la seleccionada o, si ninguna lo
5909
+ * está, la primera — el grupo siempre debe ser alcanzable con Tab.
5910
+ */
5911
+ tabStopValue = computed(() => {
5912
+ const options = this.normalizedOptions();
5913
+ const selected = options.find((option) => option.value === this.value());
5914
+ return (selected ?? options[0])?.value;
5915
+ }, /* @ts-ignore */
5916
+ ...(ngDevMode ? [{ debugName: "tabStopValue" }] : /* istanbul ignore next */ []));
5917
+ select(value) {
5918
+ if (value === this.value()) {
5919
+ return;
5920
+ }
5921
+ this.valueChange.emit(value);
5922
+ }
5923
+ /**
5924
+ * `handleArrowKeyNavigation` ubica el botón destino por `idPrefix + value`;
5925
+ * como los ids usan el índice, se le pasa la lista con el índice como
5926
+ * value y se traduce de vuelta al value real al seleccionar.
5927
+ */
5928
+ onKeydown(event, index) {
5929
+ const options = this.normalizedOptions();
5930
+ handleArrowKeyNavigation(event, options.map((_, i) => ({ value: String(i) })), String(index), this.optionIdPrefix, (next) => this.select(options[Number(next)].value), 'vertical');
5931
+ }
5932
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: OptionList, deps: [], target: i0.ɵɵFactoryTarget.Component });
5933
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: OptionList, isStandalone: true, selector: "kf-option-list", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", 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 } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "@if (label(); as labelText) {\n <span class=\"kf-option-list__title kf-overline-text\" [id]=\"labelId\">{{ labelText }}</span>\n}\n<div\n class=\"kf-option-list\"\n role=\"radiogroup\"\n [attr.aria-labelledby]=\"label() ? labelId : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n>\n @for (option of normalizedOptions(); track $index; let i = $index) {\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-option-list__option\"\n [id]=\"optionIdPrefix + i\"\n [class.kf-option-list__option--selected]=\"option.value === value()\"\n [attr.aria-checked]=\"option.value === value()\"\n [attr.tabindex]=\"option.value === tabStopValue() ? 0 : -1\"\n (click)=\"select(option.value)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n <span class=\"kf-option-list__label\">{{ option.label }}</span>\n <span class=\"kf-option-list__end\">\n @if (option.count !== undefined) {\n <span class=\"kf-option-list__count\"> {{ option.count }}</span>\n }\n <span class=\"kf-option-list__check\">\n @if (option.value === value()) {\n <kf-icon name=\"check\" size=\"sm\" />\n }\n </span>\n </span>\n </button>\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)}:host{display:flex;flex-direction:column;gap:var(--kf-space-sm);font-family:var(--kf-font-body)}.kf-option-list__title{color:var(--kf-color-text)}.kf-option-list{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-option-list__option{display:flex;align-items:center;gap:var(--kf-space-sm);box-sizing:border-box;width:100%;min-height:44px;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);font:inherit;font-size:var(--kf-font-size-body);color:var(--kf-color-text);text-align:start;cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-option-list__option:hover:not(.kf-option-list__option--selected){border-color:var(--kf-color-border-strong)}.kf-option-list__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-option-list__option--selected{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft);color:var(--kf-color-primary-active);font-weight:600}.kf-option-list__label{min-width:0;overflow-wrap:anywhere}.kf-option-list__end{display:inline-flex;align-items:center;gap:var(--kf-space-sm);margin-inline-start:auto;flex:none}.kf-option-list__check{display:inline-flex;width:16px;height:16px}.kf-option-list__count{font-size:var(--kf-font-size-caption);font-variant-numeric:tabular-nums}@media(prefers-reduced-motion:reduce){.kf-option-list__option{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
5934
+ }
5935
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: OptionList, decorators: [{
5936
+ type: Component,
5937
+ args: [{ selector: 'kf-option-list', imports: [Icon], template: "@if (label(); as labelText) {\n <span class=\"kf-option-list__title kf-overline-text\" [id]=\"labelId\">{{ labelText }}</span>\n}\n<div\n class=\"kf-option-list\"\n role=\"radiogroup\"\n [attr.aria-labelledby]=\"label() ? labelId : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n>\n @for (option of normalizedOptions(); track $index; let i = $index) {\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-option-list__option\"\n [id]=\"optionIdPrefix + i\"\n [class.kf-option-list__option--selected]=\"option.value === value()\"\n [attr.aria-checked]=\"option.value === value()\"\n [attr.tabindex]=\"option.value === tabStopValue() ? 0 : -1\"\n (click)=\"select(option.value)\"\n (keydown)=\"onKeydown($event, i)\"\n >\n <span class=\"kf-option-list__label\">{{ option.label }}</span>\n <span class=\"kf-option-list__end\">\n @if (option.count !== undefined) {\n <span class=\"kf-option-list__count\"> {{ option.count }}</span>\n }\n <span class=\"kf-option-list__check\">\n @if (option.value === value()) {\n <kf-icon name=\"check\" size=\"sm\" />\n }\n </span>\n </span>\n </button>\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)}:host{display:flex;flex-direction:column;gap:var(--kf-space-sm);font-family:var(--kf-font-body)}.kf-option-list__title{color:var(--kf-color-text)}.kf-option-list{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-option-list__option{display:flex;align-items:center;gap:var(--kf-space-sm);box-sizing:border-box;width:100%;min-height:44px;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);font:inherit;font-size:var(--kf-font-size-body);color:var(--kf-color-text);text-align:start;cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-option-list__option:hover:not(.kf-option-list__option--selected){border-color:var(--kf-color-border-strong)}.kf-option-list__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-option-list__option--selected{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft);color:var(--kf-color-primary-active);font-weight:600}.kf-option-list__label{min-width:0;overflow-wrap:anywhere}.kf-option-list__end{display:inline-flex;align-items:center;gap:var(--kf-space-sm);margin-inline-start:auto;flex:none}.kf-option-list__check{display:inline-flex;width:16px;height:16px}.kf-option-list__count{font-size:var(--kf-font-size-caption);font-variant-numeric:tabular-nums}@media(prefers-reduced-motion:reduce){.kf-option-list__option{transition:none}}\n"] }]
5938
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
5939
+
4626
5940
  const MOBILE_STEP_DOWN = {
4627
5941
  xxl: 'xl',
4628
5942
  xl: 'lg',
@@ -4855,7 +6169,7 @@ class PortalHeader {
4855
6169
  }, /* @ts-ignore */
4856
6170
  ...(ngDevMode ? [{ debugName: "thumbStyle" }] : /* istanbul ignore next */ []));
4857
6171
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
4858
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PortalHeader, isStandalone: true, selector: "kf-portal-header", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, logoSrc: { classPropertyName: "logoSrc", publicName: "logoSrc", isSignal: true, isRequired: false, transformFunction: null }, logoAlt: { classPropertyName: "logoAlt", publicName: "logoAlt", isSignal: true, isRequired: false, transformFunction: null }, isAdmin: { classPropertyName: "isAdmin", publicName: "isAdmin", isSignal: true, isRequired: false, transformFunction: null }, adminActions: { classPropertyName: "adminActions", publicName: "adminActions", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, userInitials: { classPropertyName: "userInitials", publicName: "userInitials", isSignal: true, isRequired: true, transformFunction: null }, tagline: { classPropertyName: "tagline", publicName: "tagline", isSignal: true, isRequired: false, transformFunction: null }, userName: { classPropertyName: "userName", publicName: "userName", isSignal: true, isRequired: false, transformFunction: null }, showCart: { classPropertyName: "showCart", publicName: "showCart", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sessionClick: "sessionClick", cartClick: "cartClick" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "sessionButtonRef", first: true, predicate: ["sessionButton"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "navItemAnchors", predicate: ["navItemAnchor"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isAdmin()) {\n <div class=\"kf-portal-header__admin-bar\">\n <div class=\"kf-portal-header__admin-inner\">\n <kf-admin-badge inline />\n <div class=\"kf-portal-header__admin-actions\">\n @for (action of adminActions(); track action.label) {\n <a class=\"kf-portal-header__admin-action\" [href]=\"action.href\">{{ action.label }}</a>\n }\n </div>\n </div>\n </div>\n}\n<div class=\"kf-portal-header__brand-bar\">\n <div class=\"kf-portal-header__brand-inner\">\n <div class=\"kf-portal-header__top-row\">\n <div class=\"kf-portal-header__logo-group\">\n @if (logoSrc()) {\n <img class=\"kf-portal-header__logo-img\" [src]=\"logoSrc()\" [alt]=\"logoAlt() || brand()\" />\n } @else {\n <span class=\"kf-portal-header__logo\">{{ brand() }}</span>\n }\n @if (tagline()) {\n <span class=\"kf-portal-header__logo-sep\" aria-hidden=\"true\"></span>\n <span class=\"kf-portal-header__logo-tag\">{{ tagline() }}</span>\n }\n </div>\n\n <div class=\"kf-portal-header__actions\">\n @if (showCart()) {\n <button\n type=\"button\"\n class=\"kf-portal-header__cart\"\n aria-label=\"Carrito de compras\"\n (click)=\"cartClick.emit()\"\n >\n <kf-icon name=\"cart\" size=\"md\" />\n </button>\n }\n\n <button\n #sessionButton\n type=\"button\"\n class=\"kf-portal-header__session\"\n [class.kf-portal-header__session--expanded]=\"userName()\"\n (click)=\"sessionClick.emit()\"\n >\n @if (userName()) {\n <kf-avatar [initials]=\"userInitials()\" />\n <span class=\"kf-portal-header__session-who\">\n <span class=\"kf-portal-header__session-greeting\">Hola</span>\n <span class=\"kf-portal-header__session-name\">{{ userName() }}</span>\n </span>\n <kf-icon name=\"chevron-down\" size=\"sm\" class=\"kf-portal-header__session-chevron\" />\n } @else {\n <kf-avatar [initials]=\"userInitials()\" pill />\n }\n </button>\n\n <button\n type=\"button\"\n class=\"kf-portal-header__menu-toggle\"\n [attr.aria-expanded]=\"mobileMenuOpen()\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileMenuOpen.set(!mobileMenuOpen())\"\n >\n <kf-icon [name]=\"mobileMenuOpen() ? 'x' : 'menu'\" size=\"md\" />\n </button>\n </div>\n </div>\n\n <nav class=\"kf-portal-header__nav\">\n <span class=\"kf-portal-header__nav-thumb\" [style]=\"thumbStyle()\" aria-hidden=\"true\"></span>\n @for (item of navItems(); track item.label) {\n <a\n #navItemAnchor\n class=\"kf-portal-header__nav-item\"\n [class.kf-portal-header__nav-item--active]=\"item.active\"\n [href]=\"item.href\"\n >\n {{ item.label }}\n </a>\n }\n </nav>\n </div>\n</div>\n\n<kf-drawer [(open)]=\"mobileMenuOpen\" ariaLabel=\"Men\u00FA de navegaci\u00F3n\">\n <div kf-drawer-header>\n <ng-content select=\"[kf-portal-header-extra]\" />\n </div>\n @for (item of navItems(); track item.label) {\n <a\n class=\"kf-portal-header__mobile-nav-item\"\n [class.kf-portal-header__mobile-nav-item--active]=\"item.active\"\n [href]=\"item.href\"\n (click)=\"mobileMenuOpen.set(false)\"\n >\n @if (item.icon) {\n <kf-icon [name]=\"item.icon\" size=\"sm\" />\n }\n <span class=\"kf-portal-header__mobile-nav-item-label\">{{ item.label }}</span>\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n </a>\n }\n <div kf-drawer-footer>\n <ng-content select=\"[kf-portal-header-footer]\" />\n </div>\n</kf-drawer>\n", styles: [":host{display:block;position:sticky;top:0;z-index:30;font-family:var(--kf-font-body)}.kf-portal-header__admin-bar{padding-block:var(--kf-space-xs);background:var(--kf-color-tertiary-deep)}.kf-portal-header__admin-inner{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);max-width:var(--kf-header-max-width, none);margin:0 auto;padding:0 var(--kf-space-lg)}.kf-portal-header__admin-actions{display:flex;gap:var(--kf-space-md)}.kf-portal-header__admin-action{font-size:.8125rem;font-weight:600;color:#ffffffd9;text-decoration:none}.kf-portal-header__admin-action:hover{color:var(--kf-color-white)}.kf-portal-header__brand-bar{background:var(--kf-color-tertiary-deep);border-top:1px solid rgba(255,255,255,.1)}.kf-portal-header__brand-inner{display:flex;flex-direction:column;max-width:var(--kf-header-max-width, none);margin:0 auto;padding:0 var(--kf-space-lg)}.kf-portal-header__top-row{display:flex;align-items:center;gap:var(--kf-space-lg);min-height:60px;padding-block:var(--kf-space-sm) 4px}.kf-portal-header__logo-group{display:flex;align-items:center;gap:var(--kf-space-md);flex:1 1 auto;min-width:0}.kf-portal-header__logo{font-family:var(--kf-font-title);font-size:1.25rem;font-weight:700;letter-spacing:.02em;color:var(--kf-color-white);text-transform:uppercase}.kf-portal-header__logo-img{height:28px;width:auto;display:block}.kf-portal-header__logo-sep{flex:none;width:1px;height:30px;background:#fff3}.kf-portal-header__logo-tag{font-family:var(--kf-font-title);font-weight:400;font-size:1.1875rem;color:#ffffffc7}.kf-portal-header__actions{margin-left:auto;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-portal-header__cart{flex:none;width:46px;height:46px;border-radius:var(--kf-radius-pill);border:1px solid rgba(255,255,255,.18);background:transparent;color:var(--kf-color-white);display:grid;place-items:center;cursor:pointer}.kf-portal-header__cart:hover{background:#ffffff1a;border-color:#ffffff4d}.kf-portal-header__nav{display:flex;align-items:center;gap:2px;padding:4px;margin-bottom:var(--kf-space-sm);background:#ffffff14;border-radius:var(--kf-radius-pill);width:fit-content;max-width:100%;position:relative}.kf-portal-header__nav-thumb{position:absolute;top:4px;bottom:4px;left:0;background:var(--kf-color-white);border-radius:var(--kf-radius-pill);transition:transform .25s cubic-bezier(.4,0,.2,1),width .25s cubic-bezier(.4,0,.2,1),opacity .15s linear;pointer-events:none}.kf-portal-header__nav-item{position:relative;padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-pill);color:#ffffffbf;font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-portal-header__nav-item:hover:not(.kf-portal-header__nav-item--active){color:var(--kf-color-white);background:#ffffff14}.kf-portal-header__nav-item--active{color:var(--kf-color-ink)}.kf-portal-header__session{flex:none;border:none;background:transparent;padding:0;cursor:pointer}.kf-portal-header__session--expanded{display:flex;align-items:center;gap:var(--kf-space-sm);padding:7px 12px 7px 8px;background:var(--kf-color-primary);border-radius:var(--kf-radius-pill)}.kf-portal-header__session--expanded:hover{background:var(--kf-color-primary-active)}.kf-portal-header__session-who{display:flex;flex-direction:column;line-height:1.2;color:var(--kf-color-white)}.kf-portal-header__session-greeting{font-size:.65625rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#ffffffb3}.kf-portal-header__session-name{font-size:.875rem;font-weight:700;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-portal-header__session-chevron{color:#ffffffd9}.kf-portal-header__menu-toggle{display:none;flex:none;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-white);cursor:pointer}.kf-portal-header__menu-toggle:hover{background:#ffffff1a}.kf-portal-header__mobile-nav-item{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-xs);color:var(--kf-color-text);font-size:.9375rem;font-weight:600;text-decoration:none;border-radius:var(--kf-radius-sm)}.kf-portal-header__mobile-nav-item:hover{background:var(--kf-color-surface)}.kf-portal-header__mobile-nav-item--active{color:var(--kf-color-primary-active);background:var(--kf-color-primary-soft)}.kf-portal-header__mobile-nav-item-label{flex:1 1 auto}.kf-portal-header__mobile-nav-item kf-icon:last-child{color:#171d2559}@media(max-width:768px){.kf-portal-header__nav,.kf-portal-header__session{display:none}.kf-portal-header__menu-toggle{display:inline-flex}}\n"], dependencies: [{ kind: "component", type: AdminBadge, selector: "kf-admin-badge", inputs: ["inline"] }, { kind: "component", type: Avatar, selector: "kf-avatar", inputs: ["initials", "src", "round", "pill"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Drawer, selector: "kf-drawer", inputs: ["open", "side", "ariaLabel"], outputs: ["openChange"] }] });
6172
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PortalHeader, isStandalone: true, selector: "kf-portal-header", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, logoSrc: { classPropertyName: "logoSrc", publicName: "logoSrc", isSignal: true, isRequired: false, transformFunction: null }, logoAlt: { classPropertyName: "logoAlt", publicName: "logoAlt", isSignal: true, isRequired: false, transformFunction: null }, isAdmin: { classPropertyName: "isAdmin", publicName: "isAdmin", isSignal: true, isRequired: false, transformFunction: null }, adminActions: { classPropertyName: "adminActions", publicName: "adminActions", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, userInitials: { classPropertyName: "userInitials", publicName: "userInitials", isSignal: true, isRequired: true, transformFunction: null }, tagline: { classPropertyName: "tagline", publicName: "tagline", isSignal: true, isRequired: false, transformFunction: null }, userName: { classPropertyName: "userName", publicName: "userName", isSignal: true, isRequired: false, transformFunction: null }, showCart: { classPropertyName: "showCart", publicName: "showCart", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sessionClick: "sessionClick", cartClick: "cartClick" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "sessionButtonRef", first: true, predicate: ["sessionButton"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "navItemAnchors", predicate: ["navItemAnchor"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isAdmin()) {\n <div class=\"kf-portal-header__admin-bar\">\n <div class=\"kf-portal-header__admin-inner\">\n <kf-admin-badge inline />\n <div class=\"kf-portal-header__admin-actions\">\n @for (action of adminActions(); track action.label) {\n <a class=\"kf-portal-header__admin-action\" [href]=\"action.href\">{{ action.label }}</a>\n }\n </div>\n </div>\n </div>\n}\n<div class=\"kf-portal-header__brand-bar\">\n <div class=\"kf-portal-header__brand-inner\">\n <div class=\"kf-portal-header__top-row\">\n <div class=\"kf-portal-header__logo-group\">\n @if (logoSrc()) {\n <img class=\"kf-portal-header__logo-img\" [src]=\"logoSrc()\" [alt]=\"logoAlt() || brand()\" />\n } @else {\n <span class=\"kf-portal-header__logo\">{{ brand() }}</span>\n }\n @if (tagline()) {\n <span class=\"kf-portal-header__logo-sep\" aria-hidden=\"true\"></span>\n <span class=\"kf-portal-header__logo-tag\">{{ tagline() }}</span>\n }\n </div>\n\n <div class=\"kf-portal-header__actions\">\n @if (showCart()) {\n <button\n type=\"button\"\n class=\"kf-portal-header__cart\"\n aria-label=\"Carrito de compras\"\n (click)=\"cartClick.emit()\"\n >\n <kf-icon name=\"cart\" size=\"md\" />\n </button>\n }\n\n <button\n #sessionButton\n type=\"button\"\n class=\"kf-portal-header__session\"\n [class.kf-portal-header__session--expanded]=\"userName()\"\n (click)=\"sessionClick.emit()\"\n >\n @if (userName()) {\n <kf-avatar [initials]=\"userInitials()\" />\n <span class=\"kf-portal-header__session-who\">\n <span class=\"kf-portal-header__session-greeting\">Hola</span>\n <span class=\"kf-portal-header__session-name\">{{ userName() }}</span>\n </span>\n <kf-icon name=\"chevron-down\" size=\"sm\" class=\"kf-portal-header__session-chevron\" />\n } @else {\n <kf-avatar [initials]=\"userInitials()\" pill />\n }\n </button>\n\n <button\n type=\"button\"\n class=\"kf-portal-header__menu-toggle\"\n [attr.aria-expanded]=\"mobileMenuOpen()\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileMenuOpen.set(!mobileMenuOpen())\"\n >\n <kf-icon [name]=\"mobileMenuOpen() ? 'x' : 'menu'\" size=\"md\" />\n </button>\n </div>\n </div>\n\n <nav class=\"kf-portal-header__nav\">\n <span class=\"kf-portal-header__nav-thumb\" [style]=\"thumbStyle()\" aria-hidden=\"true\"></span>\n @for (item of navItems(); track item.label) {\n <a\n #navItemAnchor\n class=\"kf-portal-header__nav-item\"\n [class.kf-portal-header__nav-item--active]=\"item.active\"\n [href]=\"item.href\"\n >\n {{ item.label }}\n </a>\n }\n </nav>\n </div>\n</div>\n\n<kf-drawer [(open)]=\"mobileMenuOpen\" ariaLabel=\"Men\u00FA de navegaci\u00F3n\">\n <div kf-drawer-header>\n <ng-content select=\"[kf-portal-header-extra]\" />\n </div>\n @for (item of navItems(); track item.label) {\n <a\n class=\"kf-portal-header__mobile-nav-item\"\n [class.kf-portal-header__mobile-nav-item--active]=\"item.active\"\n [href]=\"item.href\"\n (click)=\"mobileMenuOpen.set(false)\"\n >\n @if (item.icon) {\n <kf-icon [name]=\"item.icon\" size=\"sm\" />\n }\n <span class=\"kf-portal-header__mobile-nav-item-label\">{{ item.label }}</span>\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n </a>\n }\n <div kf-drawer-footer>\n <ng-content select=\"[kf-portal-header-footer]\" />\n </div>\n</kf-drawer>\n", styles: [":host{display:block;position:sticky;top:0;z-index:30;font-family:var(--kf-font-body)}.kf-portal-header__admin-bar{padding-block:var(--kf-space-xs);background:var(--kf-color-tertiary-deep)}.kf-portal-header__admin-inner{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);max-width:var(--kf-header-max-width, none);margin:0 auto;padding:0 var(--kf-space-lg)}.kf-portal-header__admin-actions{display:flex;gap:var(--kf-space-md)}.kf-portal-header__admin-action{font-size:.8125rem;font-weight:600;color:#ffffffd9;text-decoration:none}.kf-portal-header__admin-action:hover{color:var(--kf-color-white)}.kf-portal-header__brand-bar{background:var(--kf-color-tertiary-deep);border-top:1px solid rgba(255,255,255,.1)}.kf-portal-header__brand-inner{display:flex;flex-direction:column;max-width:var(--kf-header-max-width, none);margin:0 auto;padding:0 var(--kf-space-lg)}.kf-portal-header__top-row{display:flex;align-items:center;gap:var(--kf-space-lg);min-height:60px;padding-block:var(--kf-space-sm) 4px}.kf-portal-header__logo-group{display:flex;align-items:center;gap:var(--kf-space-md);flex:1 1 auto;min-width:0}.kf-portal-header__logo{font-family:var(--kf-font-title);font-size:1.25rem;font-weight:700;letter-spacing:.02em;color:var(--kf-color-white);text-transform:uppercase}.kf-portal-header__logo-img{height:28px;width:auto;display:block}.kf-portal-header__logo-sep{flex:none;width:1px;height:30px;background:#fff3}.kf-portal-header__logo-tag{font-family:var(--kf-font-title);font-weight:400;font-size:1.1875rem;color:#ffffffc7}.kf-portal-header__actions{margin-left:auto;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-portal-header__cart{flex:none;width:46px;height:46px;border-radius:var(--kf-radius-pill);border:1px solid rgba(255,255,255,.18);background:transparent;color:var(--kf-color-white);display:grid;place-items:center;cursor:pointer}.kf-portal-header__cart:hover{background:#ffffff1a;border-color:#ffffff4d}.kf-portal-header__nav{display:flex;align-items:center;gap:2px;padding:4px;margin-bottom:var(--kf-space-sm);background:#ffffff14;border-radius:var(--kf-radius-pill);width:fit-content;max-width:100%;position:relative}.kf-portal-header__nav-thumb{position:absolute;top:4px;bottom:4px;left:0;background:var(--kf-color-white);border-radius:var(--kf-radius-pill);transition:transform .25s cubic-bezier(.4,0,.2,1),width .25s cubic-bezier(.4,0,.2,1),opacity .15s linear;pointer-events:none}.kf-portal-header__nav-item{position:relative;padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-pill);color:#ffffffbf;font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-portal-header__nav-item:hover:not(.kf-portal-header__nav-item--active){color:var(--kf-color-white);background:#ffffff14}.kf-portal-header__nav-item--active{color:var(--kf-color-ink)}.kf-portal-header__session{flex:none;border:none;background:transparent;padding:0;cursor:pointer}.kf-portal-header__session--expanded{display:flex;align-items:center;gap:var(--kf-space-sm);padding:7px 12px 7px 8px;background:var(--kf-color-primary);border-radius:var(--kf-radius-pill)}.kf-portal-header__session--expanded:hover{background:var(--kf-color-primary-active)}.kf-portal-header__session-who{display:flex;flex-direction:column;line-height:1.2;color:var(--kf-color-white)}.kf-portal-header__session-greeting{font-size:.65625rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#ffffffb3}.kf-portal-header__session-name{font-size:.875rem;font-weight:700;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-portal-header__session-chevron{color:#ffffffd9}.kf-portal-header__menu-toggle{display:none;flex:none;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-white);cursor:pointer}.kf-portal-header__menu-toggle:hover{background:#ffffff1a}.kf-portal-header__mobile-nav-item{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-xs);color:var(--kf-color-text);font-size:.9375rem;font-weight:600;text-decoration:none;border-radius:var(--kf-radius-sm)}.kf-portal-header__mobile-nav-item:hover{background:var(--kf-color-surface)}.kf-portal-header__mobile-nav-item--active{color:var(--kf-color-primary-active);background:var(--kf-color-primary-soft)}.kf-portal-header__mobile-nav-item-label{flex:1 1 auto}.kf-portal-header__mobile-nav-item kf-icon:last-child{color:#171d2559}@media(max-width:768px){.kf-portal-header__nav,.kf-portal-header__session{display:none}.kf-portal-header__menu-toggle{display:inline-flex}}\n"], dependencies: [{ kind: "component", type: AdminBadge, selector: "kf-admin-badge", inputs: ["inline"] }, { kind: "component", type: Avatar, selector: "kf-avatar", inputs: ["initials", "src", "round", "pill"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Drawer, selector: "kf-drawer", inputs: ["open", "side", "role", "ariaLabel", "title", "closeLabel"], outputs: ["openChange"] }] });
4859
6173
  }
4860
6174
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, decorators: [{
4861
6175
  type: Component,
@@ -5370,11 +6684,11 @@ class Segmented {
5370
6684
  this.valueChange.emit(nextValue);
5371
6685
  }
5372
6686
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Segmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
5373
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Segmented, isStandalone: true, selector: "kf-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"kf-segmented__track\" [style.--kf-segmented-count]=\"options().length\">\n <div\n class=\"kf-segmented__thumb\"\n [style.--kf-segmented-index]=\"selectedIndex()\"\n [style.visibility]=\"selectedIndex() === -1 ? 'hidden' : 'visible'\"\n ></div>\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-segmented__option\"\n [class.kf-segmented__option--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:inline-block}.kf-segmented__track{position:relative;display:grid;grid-template-columns:repeat(var(--kf-segmented-count),1fr);gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-segmented__thumb{position:absolute;top:2px;left:2px;width:calc((100% - 4px) / var(--kf-segmented-count));height:calc(100% - 4px);background:var(--kf-color-white);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-xs);transform:translate(calc(var(--kf-segmented-index, 0) * 100%));transition:transform .2s ease}.kf-segmented__option{position:relative;z-index:1;font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-sm);cursor:pointer;transition:color .14s}.kf-segmented__option:hover:not(.kf-segmented__option--active){color:var(--kf-color-text)}.kf-segmented__option--active{color:var(--kf-color-ink)}.kf-segmented__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] });
6687
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Segmented, isStandalone: true, selector: "kf-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"kf-segmented__track\" [style.--kf-segmented-count]=\"options().length\">\n <div\n class=\"kf-segmented__thumb\"\n [style.--kf-segmented-index]=\"selectedIndex()\"\n [style.visibility]=\"selectedIndex() === -1 ? 'hidden' : 'visible'\"\n ></div>\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-segmented__option\"\n [class.kf-segmented__option--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:inline-block}.kf-segmented__track{position:relative;display:grid;grid-template-columns:repeat(var(--kf-segmented-count),1fr);gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-segmented__thumb{position:absolute;top:2px;left:2px;width:calc((100% - 4px) / var(--kf-segmented-count));height:calc(100% - 4px);background:var(--kf-color-primary);border-radius:var(--kf-radius-sm);transform:translate(calc(var(--kf-segmented-index, 0) * 100%));transition:transform .2s ease,background .14s}.kf-segmented__option{position:relative;z-index:1;font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-sm);cursor:pointer;transition:color .14s}.kf-segmented__option:hover:not(.kf-segmented__option--active){color:var(--kf-color-text)}.kf-segmented__option--active{color:var(--kf-color-white)}.kf-segmented__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] });
5374
6688
  }
5375
6689
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Segmented, decorators: [{
5376
6690
  type: Component,
5377
- args: [{ selector: 'kf-segmented', template: "<div class=\"kf-segmented__track\" [style.--kf-segmented-count]=\"options().length\">\n <div\n class=\"kf-segmented__thumb\"\n [style.--kf-segmented-index]=\"selectedIndex()\"\n [style.visibility]=\"selectedIndex() === -1 ? 'hidden' : 'visible'\"\n ></div>\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-segmented__option\"\n [class.kf-segmented__option--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:inline-block}.kf-segmented__track{position:relative;display:grid;grid-template-columns:repeat(var(--kf-segmented-count),1fr);gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-segmented__thumb{position:absolute;top:2px;left:2px;width:calc((100% - 4px) / var(--kf-segmented-count));height:calc(100% - 4px);background:var(--kf-color-white);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-xs);transform:translate(calc(var(--kf-segmented-index, 0) * 100%));transition:transform .2s ease}.kf-segmented__option{position:relative;z-index:1;font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-sm);cursor:pointer;transition:color .14s}.kf-segmented__option:hover:not(.kf-segmented__option--active){color:var(--kf-color-text)}.kf-segmented__option--active{color:var(--kf-color-ink)}.kf-segmented__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
6691
+ args: [{ selector: 'kf-segmented', template: "<div class=\"kf-segmented__track\" [style.--kf-segmented-count]=\"options().length\">\n <div\n class=\"kf-segmented__thumb\"\n [style.--kf-segmented-index]=\"selectedIndex()\"\n [style.visibility]=\"selectedIndex() === -1 ? 'hidden' : 'visible'\"\n ></div>\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-segmented__option\"\n [class.kf-segmented__option--active]=\"option.value === value()\"\n [attr.aria-pressed]=\"option.value === value()\"\n (click)=\"select(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n</div>\n", styles: [":host{display:inline-block}.kf-segmented__track{position:relative;display:grid;grid-template-columns:repeat(var(--kf-segmented-count),1fr);gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-segmented__thumb{position:absolute;top:2px;left:2px;width:calc((100% - 4px) / var(--kf-segmented-count));height:calc(100% - 4px);background:var(--kf-color-primary);border-radius:var(--kf-radius-sm);transform:translate(calc(var(--kf-segmented-index, 0) * 100%));transition:transform .2s ease,background .14s}.kf-segmented__option{position:relative;z-index:1;font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-sm);cursor:pointer;transition:color .14s}.kf-segmented__option:hover:not(.kf-segmented__option--active){color:var(--kf-color-text)}.kf-segmented__option--active{color:var(--kf-color-white)}.kf-segmented__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
5378
6692
  }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
5379
6693
 
5380
6694
  /**
@@ -5436,6 +6750,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5436
6750
  * estado activo. `collapsed` oculta el label y centra el ícono, controlado
5437
6751
  * por el propio shell. No sabe nada de routing: emite `href` como
5438
6752
  * cualquier link, el consumidor intercepta el click.
6753
+ *
6754
+ * Colapsado, el link solo muestra el ícono: su nombre accesible sale de
6755
+ * `aria-label` = `label` (el tooltip es solo visual y no nombra el link).
5439
6756
  */
5440
6757
  class SidebarNavItem {
5441
6758
  icon = input.required(/* @ts-ignore */
@@ -5447,11 +6764,11 @@ class SidebarNavItem {
5447
6764
  active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5448
6765
  collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5449
6766
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
5450
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarNavItem, isStandalone: true, selector: "kf-sidebar-nav-item", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<a\n class=\"kf-sidebar-nav-item\"\n [class.kf-sidebar-nav-item--active]=\"active()\"\n [class.kf-sidebar-nav-item--collapsed]=\"collapsed()\"\n [href]=\"href()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [kfTooltip]=\"collapsed() ? label() : ''\"\n>\n <kf-icon [name]=\"icon()\" size=\"md\" />\n @if (!collapsed()) {\n <span class=\"kf-sidebar-nav-item__label\">{{ label() }}</span>\n }\n</a>\n", styles: [".kf-sidebar-nav-item{box-sizing:border-box;position:relative;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffb3;font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-sidebar-nav-item:hover:not(.kf-sidebar-nav-item--active){background:#ffffff0f;color:var(--kf-color-white)}.kf-sidebar-nav-item--collapsed:hover:not(.kf-sidebar-nav-item--active){background:#ffffff1f}.kf-sidebar-nav-item--active{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-nav-item--active:before{content:\"\";position:absolute;left:calc(-1 * var(--kf-space-xs));top:20%;bottom:20%;width:3px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary-hover)}.kf-sidebar-nav-item--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-nav-item--collapsed.kf-sidebar-nav-item--active:before{left:calc(-1 * var(--kf-space-sm))}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
6767
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarNavItem, isStandalone: true, selector: "kf-sidebar-nav-item", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<a\n class=\"kf-sidebar-nav-item\"\n [class.kf-sidebar-nav-item--active]=\"active()\"\n [class.kf-sidebar-nav-item--collapsed]=\"collapsed()\"\n [href]=\"href()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.aria-label]=\"collapsed() ? label() : null\"\n [kfTooltip]=\"collapsed() ? label() : ''\"\n>\n <kf-icon [name]=\"icon()\" size=\"md\" />\n @if (!collapsed()) {\n <span class=\"kf-sidebar-nav-item__label\">{{ label() }}</span>\n }\n</a>\n", styles: [".kf-sidebar-nav-item{box-sizing:border-box;position:relative;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffb3;font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-sidebar-nav-item:hover:not(.kf-sidebar-nav-item--active){background:#ffffff0f;color:var(--kf-color-white)}.kf-sidebar-nav-item--collapsed:hover:not(.kf-sidebar-nav-item--active){background:#ffffff1f}.kf-sidebar-nav-item--active{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-nav-item--active:before{content:\"\";position:absolute;left:calc(-1 * var(--kf-space-xs));top:20%;bottom:20%;width:3px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary-hover)}.kf-sidebar-nav-item--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-nav-item--collapsed.kf-sidebar-nav-item--active:before{left:calc(-1 * var(--kf-space-sm))}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
5451
6768
  }
5452
6769
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarNavItem, decorators: [{
5453
6770
  type: Component,
5454
- args: [{ selector: 'kf-sidebar-nav-item', imports: [Icon, TooltipDirective], template: "<a\n class=\"kf-sidebar-nav-item\"\n [class.kf-sidebar-nav-item--active]=\"active()\"\n [class.kf-sidebar-nav-item--collapsed]=\"collapsed()\"\n [href]=\"href()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [kfTooltip]=\"collapsed() ? label() : ''\"\n>\n <kf-icon [name]=\"icon()\" size=\"md\" />\n @if (!collapsed()) {\n <span class=\"kf-sidebar-nav-item__label\">{{ label() }}</span>\n }\n</a>\n", styles: [".kf-sidebar-nav-item{box-sizing:border-box;position:relative;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffb3;font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-sidebar-nav-item:hover:not(.kf-sidebar-nav-item--active){background:#ffffff0f;color:var(--kf-color-white)}.kf-sidebar-nav-item--collapsed:hover:not(.kf-sidebar-nav-item--active){background:#ffffff1f}.kf-sidebar-nav-item--active{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-nav-item--active:before{content:\"\";position:absolute;left:calc(-1 * var(--kf-space-xs));top:20%;bottom:20%;width:3px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary-hover)}.kf-sidebar-nav-item--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-nav-item--collapsed.kf-sidebar-nav-item--active:before{left:calc(-1 * var(--kf-space-sm))}\n"] }]
6771
+ args: [{ selector: 'kf-sidebar-nav-item', imports: [Icon, TooltipDirective], template: "<a\n class=\"kf-sidebar-nav-item\"\n [class.kf-sidebar-nav-item--active]=\"active()\"\n [class.kf-sidebar-nav-item--collapsed]=\"collapsed()\"\n [href]=\"href()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.aria-label]=\"collapsed() ? label() : null\"\n [kfTooltip]=\"collapsed() ? label() : ''\"\n>\n <kf-icon [name]=\"icon()\" size=\"md\" />\n @if (!collapsed()) {\n <span class=\"kf-sidebar-nav-item__label\">{{ label() }}</span>\n }\n</a>\n", styles: [".kf-sidebar-nav-item{box-sizing:border-box;position:relative;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffb3;font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-sidebar-nav-item:hover:not(.kf-sidebar-nav-item--active){background:#ffffff0f;color:var(--kf-color-white)}.kf-sidebar-nav-item--collapsed:hover:not(.kf-sidebar-nav-item--active){background:#ffffff1f}.kf-sidebar-nav-item--active{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-nav-item--active:before{content:\"\";position:absolute;left:calc(-1 * var(--kf-space-xs));top:20%;bottom:20%;width:3px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary-hover)}.kf-sidebar-nav-item--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-nav-item--collapsed.kf-sidebar-nav-item--active:before{left:calc(-1 * var(--kf-space-sm))}\n"] }]
5455
6772
  }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: true }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }] } });
5456
6773
 
5457
6774
  const MOBILE_BREAKPOINT_QUERY = '(max-width: 900px)';
@@ -6334,5 +7651,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
6334
7651
  * Generated bundle index. Do not edit.
6335
7652
  */
6336
7653
 
6337
- 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, Link, MetricCard, Modal, MultiSelect, NoteBlock, 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 };
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 };
6338
7655
  //# sourceMappingURL=kaufmann-lab-ui-core.mjs.map