@kaufmann-lab/ui 0.1.20 → 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
@@ -2666,6 +2793,25 @@ const COMPACT_PANEL_MIN_WIDTH = 72;
2666
2793
  * layouts flex/grid donde el ancho fijo de `md` no encaja.
2667
2794
  *
2668
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"`).
2669
2815
  */
2670
2816
  class Select {
2671
2817
  label = input(/* @ts-ignore */
@@ -2702,8 +2848,32 @@ class Select {
2702
2848
  ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
2703
2849
  searchThreshold = input(7, /* @ts-ignore */
2704
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 */ []));
2705
2875
  valueChange = output();
2706
- instanceId = `kf-select-${nextId$1++}`;
2876
+ instanceId = `kf-select-${nextId$3++}`;
2707
2877
  triggerId = `${this.instanceId}-trigger`;
2708
2878
  listboxId = `${this.instanceId}-listbox`;
2709
2879
  errorId = `${this.instanceId}-error`;
@@ -2718,6 +2888,25 @@ class Select {
2718
2888
  ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
2719
2889
  selectedOption = computed(() => this.normalizedOptions().find((option) => option.value === this.value()), /* @ts-ignore */
2720
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 */ []));
2721
2910
  showSearch = computed(() => this.searchable() ?? this.normalizedOptions().length > this.searchThreshold(), /* @ts-ignore */
2722
2911
  ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
2723
2912
  /** Opciones visibles tras aplicar el filtro de búsqueda (case/acento-insensitive). */
@@ -2735,11 +2924,14 @@ class Select {
2735
2924
  ...(ngDevMode ? [{ debugName: "panelTemplate" }] : /* istanbul ignore next */ []));
2736
2925
  searchInputRef = viewChild('searchInput', /* @ts-ignore */
2737
2926
  ...(ngDevMode ? [{ debugName: "searchInputRef" }] : /* istanbul ignore next */ []));
2738
- 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'));
2739
2931
  constructor() {
2740
2932
  effect(() => {
2741
2933
  if (this.isOpen()) {
2742
- this.overlay.attach(this.panelTemplate(), () => this.isOpen.set(false));
2934
+ this.overlay.attach(this.panelTemplate(), () => this.close());
2743
2935
  }
2744
2936
  else {
2745
2937
  this.overlay.detach();
@@ -2751,9 +2943,20 @@ class Select {
2751
2943
  // dentro de un effect() (NG0203: pierde el contexto de inyección) — hay
2752
2944
  // que usar afterRenderEffect(), que sí es reactivo a signals y corre
2753
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.
2754
2950
  afterRenderEffect(() => {
2755
- if (this.isOpen() && this.showSearch()) {
2951
+ if (!this.isOpen()) {
2952
+ return;
2953
+ }
2954
+ if (this.showSearch()) {
2756
2955
  this.searchInputRef()?.nativeElement.focus();
2956
+ return;
2957
+ }
2958
+ if (this.panelRef()) {
2959
+ untracked(() => this.focusOption(this.activeIndex()));
2757
2960
  }
2758
2961
  });
2759
2962
  inject(DestroyRef).onDestroy(() => this.overlay.dispose());
@@ -2765,13 +2968,125 @@ class Select {
2765
2968
  * panel está abierto, `keydownEvents()` nunca dispara.
2766
2969
  */
2767
2970
  onDocumentEscape() {
2768
- this.isOpen.set(false);
2971
+ this.close();
2769
2972
  }
2770
2973
  toggle() {
2771
2974
  if (this.disabled()) {
2772
2975
  return;
2773
2976
  }
2774
- this.isOpen.update((open) => !open);
2977
+ if (this.isOpen()) {
2978
+ this.close();
2979
+ }
2980
+ else {
2981
+ this.open();
2982
+ }
2983
+ }
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
+ }
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}`;
2775
3090
  }
2776
3091
  /** Nombre accesible explícito solo para opciones con `tag`/`detail`, para separar las partes al leerlas. */
2777
3092
  optionAriaLabel(option) {
@@ -2781,7 +3096,7 @@ class Select {
2781
3096
  return [option.label, option.tag, option.detail].filter(Boolean).join(', ');
2782
3097
  }
2783
3098
  select(option) {
2784
- this.isOpen.set(false);
3099
+ this.close(true);
2785
3100
  if (option.value !== this.value()) {
2786
3101
  this.valueChange.emit(option.value);
2787
3102
  }
@@ -2803,6 +3118,17 @@ class Select {
2803
3118
  return;
2804
3119
  }
2805
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' });
2806
3132
  }
2807
3133
  selectActive() {
2808
3134
  const option = this.filteredOptions()[this.activeIndex()];
@@ -2811,17 +3137,17 @@ class Select {
2811
3137
  }
2812
3138
  }
2813
3139
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
2814
- 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 [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\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 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 [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.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 @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)}: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__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;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__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__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"] }, { 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"] }] });
2815
3141
  }
2816
3142
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, decorators: [{
2817
3143
  type: Component,
2818
- args: [{ selector: 'kf-select', imports: [Badge, FieldLabel], host: {
3144
+ args: [{ selector: 'kf-select', imports: [Badge, FieldLabel, Icon], host: {
2819
3145
  '[class.kf-select--compact]': "variant() === 'compact'",
2820
3146
  '[class.kf-select--width-sm]': "width() === 'sm'",
2821
3147
  '[class.kf-select--width-lg]': "width() === 'lg'",
2822
3148
  '[class.kf-select--width-full]': "width() === 'full'",
2823
- }, 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 [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\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 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 [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.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 @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)}: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__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;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__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__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
2824
- }], 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: [{
2825
3151
  type: HostListener,
2826
3152
  args: ['document:keydown.escape']
2827
3153
  }] } });
@@ -2898,11 +3224,11 @@ class Pagination {
2898
3224
  this.pageChange.emit(clamped);
2899
3225
  }
2900
3226
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Pagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
2901
- 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"] }] });
2902
3228
  }
2903
3229
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Pagination, decorators: [{
2904
3230
  type: Component,
2905
- 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"] }]
2906
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"] }] } });
2907
3233
  /**
2908
3234
  * Construye la lista de items visibles con el algoritmo de elipsis estándar:
@@ -2947,11 +3273,28 @@ function buildPaginationItems(current, total) {
2947
3273
  * navegación con flechas izquierda/derecha, igual que un <input type="radio">
2948
3274
  * nativo agrupado.
2949
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
+ *
2950
3281
  * <kf-view-toggle [view]="view" (viewChange)="view = $event" />
3282
+ * <kf-view-toggle iconOnly [view]="view" (viewChange)="view = $event" />
2951
3283
  */
2952
3284
  class ViewToggle {
2953
3285
  view = input.required(/* @ts-ignore */
2954
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 */ []));
2955
3298
  viewChange = output();
2956
3299
  select(next) {
2957
3300
  if (next === this.view()) {
@@ -2973,13 +3316,17 @@ class ViewToggle {
2973
3316
  nextButton?.focus();
2974
3317
  }
2975
3318
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ViewToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
2976
- 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"] }] });
2977
3320
  }
2978
3321
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ViewToggle, decorators: [{
2979
3322
  type: Component,
2980
- 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"] }]
2981
- }], 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"] }] } });
2982
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';
2983
3330
  /** Opciones de "filas por página" estándar del portal — 10 como default
2984
3331
  * cubre bien tanto un dataset chico (ej. Beneficios) como uno con volumen
2985
3332
  * real (ej. 30+ cuotas en Billing), sin sentirse vacío ni insuficiente; 25
@@ -3048,6 +3395,12 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
3048
3395
  * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" itemLabel="beneficios" />
3049
3396
  * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" [pageSizeOptions]="[]" />
3050
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
+ *
3051
3404
  * Ordenamiento por columna: cada `DataTableColumn` con `sortable: true`
3052
3405
  * vuelve su header clickeable, con el mismo formato `key:direction` que
3053
3406
  * usa `sort` de `kf-quote-picker` (two-way manual, no `model()` — el
@@ -3061,6 +3414,55 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
3061
3414
  *
3062
3415
  * Densidad: `density="compact"` reduce el padding vertical de cada fila,
3063
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)" />
3064
3466
  */
3065
3467
  class DataTable {
3066
3468
  columns = input.required(/* @ts-ignore */
@@ -3086,6 +3488,14 @@ class DataTable {
3086
3488
  ...(ngDevMode ? [{ debugName: "canScrollLeft" }] : /* istanbul ignore next */ []));
3087
3489
  canScrollRight = signal(false, /* @ts-ignore */
3088
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 */ []));
3089
3499
  constructor() {
3090
3500
  afterNextRender(() => {
3091
3501
  const el = this.scrollEl()?.nativeElement;
@@ -3111,15 +3521,45 @@ class DataTable {
3111
3521
  this.measureStickyOffsets();
3112
3522
  });
3113
3523
  // `rows()` cambia de identidad cuando el consumidor filtra el dataset
3114
- // base (ej. cambiar de tab) — sin esto, `page` queda en un número que
3115
- // puede no existir más para el nuevo conjunto (ej. estar en la página 2
3116
- // de "Todos" y pasar a "Bonos", que solo tiene 1 página), dejando la
3117
- // tabla en blanco porque `displayedRows` corta un rango vacío. Reacciona
3118
- // solo a `rows()`, no a `page()`/`sort()`: cambiar de página o de orden
3119
- // 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;
3120
3530
  effect(() => {
3121
3531
  this.rows();
3122
- 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));
3123
3563
  });
3124
3564
  }
3125
3565
  updateScrollState(el) {
@@ -3128,6 +3568,7 @@ class DataTable {
3128
3568
  // al final (o nunca tuvo overflow real).
3129
3569
  this.canScrollLeft.set(el.scrollLeft > 1);
3130
3570
  this.canScrollRight.set(el.scrollWidth - el.clientWidth - el.scrollLeft > 1);
3571
+ this.hasOverflow.set(el.scrollWidth - el.clientWidth > 1);
3131
3572
  }
3132
3573
  /** Default: índice de la fila dentro de `rows`. */
3133
3574
  rowKey = input(/* @ts-ignore */
@@ -3136,10 +3577,16 @@ class DataTable {
3136
3577
  flush = input(false, /* @ts-ignore */
3137
3578
  ...(ngDevMode ? [{ debugName: "flush" }] : /* istanbul ignore next */ []));
3138
3579
  /** Habilita el toggle de vista tabla/cards. */
3139
- cardable = input(false, /* @ts-ignore */
3140
- ...(ngDevMode ? [{ debugName: "cardable" }] : /* istanbul ignore next */ []));
3580
+ cardable = input(false, { ...(ngDevMode ? { debugName: "cardable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3141
3581
  defaultView = input('table', /* @ts-ignore */
3142
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 */ []));
3143
3590
  /**
3144
3591
  * Formateo simple de celda como texto: `(row) => string`. Para marcado
3145
3592
  * real (badges, links, botones) usar `cellTemplates`.
@@ -3157,6 +3604,17 @@ class DataTable {
3157
3604
  rowClickable = input(null, /* @ts-ignore */
3158
3605
  ...(ngDevMode ? [{ debugName: "rowClickable" }] : /* istanbul ignore next */ []));
3159
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 */ []));
3160
3618
  /**
3161
3619
  * Paginación interna opcional — sin `pageSize`, cero cambio de
3162
3620
  * comportamiento (el consumidor sigue paginando afuera si quiere, vía el
@@ -3173,7 +3631,17 @@ class DataTable {
3173
3631
  * aunque `pageSize` esté activo (paginación sin selector). */
3174
3632
  pageSizeOptions = input(DEFAULT_PAGE_SIZE_OPTIONS, /* @ts-ignore */
3175
3633
  ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
3176
- 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. */
3177
3645
  pageSizeChange = output();
3178
3646
  /** Nombre de la entidad listada (ej. "beneficios", "facturas"), en
3179
3647
  * plural — se agrega al final del texto informativo de la paginación
@@ -3182,8 +3650,6 @@ class DataTable {
3182
3650
  * fijo, cada consumidor decide el suyo. */
3183
3651
  itemLabel = input('', /* @ts-ignore */
3184
3652
  ...(ngDevMode ? [{ debugName: "itemLabel" }] : /* istanbul ignore next */ []));
3185
- page = signal(1, /* @ts-ignore */
3186
- ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
3187
3653
  internalPageSize = signal(null, /* @ts-ignore */
3188
3654
  ...(ngDevMode ? [{ debugName: "internalPageSize" }] : /* istanbul ignore next */ []));
3189
3655
  /** Tamaño de página efectivo: el que el usuario elija en el selector
@@ -3288,15 +3754,82 @@ class DataTable {
3288
3754
  return;
3289
3755
  }
3290
3756
  this.page.set(clamped);
3291
- this.pageChange.emit(clamped);
3292
3757
  }
3293
3758
  setPageSize(size) {
3294
3759
  if (size === null) {
3295
3760
  return;
3296
3761
  }
3297
3762
  this.internalPageSize.set(size);
3298
- 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.
3299
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;
3300
3833
  }
3301
3834
  /** Columnas fijas del lado izquierdo/derecho, con el offset acumulado
3302
3835
  * (medido en runtime, ver `measureStickyOffsets`) para `position: sticky`. */
@@ -3317,11 +3850,18 @@ class DataTable {
3317
3850
  zIndex: '1',
3318
3851
  };
3319
3852
  }
3320
- internalView = signal(null, /* @ts-ignore */
3321
- ...(ngDevMode ? [{ debugName: "internalView" }] : /* istanbul ignore next */ []));
3322
- /** Vista activa: la elegida por el usuario, o `defaultView` si no tocó el toggle. */
3323
- 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 */
3324
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 */ []));
3325
3865
  /** Primera columna de cada rol único; el resto se ignora en el encabezado. */
3326
3866
  cardMediaColumn = computed(() => this.columns().find((c) => c.cardRole === 'media'), /* @ts-ignore */
3327
3867
  ...(ngDevMode ? [{ debugName: "cardMediaColumn" }] : /* istanbul ignore next */ []));
@@ -3331,14 +3871,20 @@ class DataTable {
3331
3871
  ...(ngDevMode ? [{ debugName: "cardStatusColumns" }] : /* istanbul ignore next */ []));
3332
3872
  cardActionColumns = computed(() => this.columns().filter((c) => c.cardRole === 'action'), /* @ts-ignore */
3333
3873
  ...(ngDevMode ? [{ debugName: "cardActionColumns" }] : /* istanbul ignore next */ []));
3334
- 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 */
3335
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 */ []));
3336
3882
  /**
3337
3883
  * La card entera es clickeable solo si no tiene acciones propias: un
3338
3884
  * `role="button"` con botones adentro es inválido y deja el foco ambiguo.
3339
3885
  * Con columnas `action`, la interacción la manejan esos botones.
3340
3886
  */
3341
- cardInteractive = computed(() => this.cardActionColumns().length === 0, /* @ts-ignore */
3887
+ cardInteractive = computed(() => this.rowClickable() ?? this.cardActionColumns().length === 0, /* @ts-ignore */
3342
3888
  ...(ngDevMode ? [{ debugName: "cardInteractive" }] : /* istanbul ignore next */ []));
3343
3889
  /**
3344
3890
  * Mismo criterio que `cardInteractive`, para la vista de tabla: la fila
@@ -3351,12 +3897,23 @@ class DataTable {
3351
3897
  rowInteractive = computed(() => this.rowClickable() ?? this.cardActionColumns().length === 0, /* @ts-ignore */
3352
3898
  ...(ngDevMode ? [{ debugName: "rowInteractive" }] : /* istanbul ignore next */ []));
3353
3899
  setView(next) {
3354
- this.internalView.set(next);
3900
+ this.view.set(next);
3355
3901
  }
3356
3902
  trackRow(row, index) {
3357
3903
  const keyFn = this.rowKey();
3358
3904
  return keyFn ? keyFn(row, index) : index;
3359
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
+ }
3360
3917
  cellValue(row, column) {
3361
3918
  const renderFn = this.render();
3362
3919
  if (renderFn) {
@@ -3365,12 +3922,45 @@ class DataTable {
3365
3922
  const value = row[column.key];
3366
3923
  return value === null || value === undefined ? '' : String(value);
3367
3924
  }
3368
- onRowClick(row, index) {
3369
- 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)) {
3370
3951
  return;
3371
3952
  }
3372
3953
  this.rowClick.emit({ row, index });
3373
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
+ }
3374
3964
  /**
3375
3965
  * Ancho real de cada columna `sticky` — se mide después de cada render
3376
3966
  * porque el ancho de una columna depende de su contenido, no hay forma
@@ -3400,12 +3990,12 @@ class DataTable {
3400
3990
  this.stickyOffsets.set(offsets);
3401
3991
  }
3402
3992
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
3403
- 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"] }] });
3404
3994
  }
3405
3995
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, decorators: [{
3406
3996
  type: Component,
3407
- 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"] }]
3408
- }], 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"] }] } });
3409
3999
 
3410
4000
  /**
3411
4001
  * Signal con la vista de `kf-data-table` ('table' | 'cards') derivada de un
@@ -3525,6 +4115,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3525
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"] }]
3526
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 }] }] } });
3527
4117
 
4118
+ let nextId$2 = 0;
3528
4119
  /**
3529
4120
  * Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
3530
4121
  * ni qué es sesión, solo da tres lugares para proyectar contenido
@@ -3557,6 +4148,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3557
4148
  * <a href="/">Home</a>
3558
4149
  * <a href="/vehiculos">Vehículos</a>
3559
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.
3560
4191
  */
3561
4192
  class Drawer {
3562
4193
  /** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
@@ -3564,13 +4195,32 @@ class Drawer {
3564
4195
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
3565
4196
  /** Borde de anclaje del panel. `left` (default) es la convención dominante
3566
4197
  * (Material/iOS/Android): el botón que abre el drawer y el propio panel
3567
- * quedan del mismo lado. */
4198
+ * quedan del mismo lado. `bottom` lo convierte en hoja inferior. */
3568
4199
  side = input('left', /* @ts-ignore */
3569
4200
  ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
3570
- /** `aria-label` del `<nav>` interno — describe el propósito del panel para
3571
- * lectores de pantalla (ej. "Menú de navegación"). */
3572
- ariaLabel = input('Menú', /* @ts-ignore */
3573
- ...(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++}`;
3574
4224
  panelRef = viewChild.required('panel', /* @ts-ignore */
3575
4225
  ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
3576
4226
  focusTrapFactory = inject(FocusTrapFactory);
@@ -3587,14 +4237,26 @@ class Drawer {
3587
4237
  else {
3588
4238
  this.focusTrap?.destroy();
3589
4239
  this.focusTrap = null;
3590
- this.lastFocusedElement?.focus();
4240
+ if (this.lastFocusedElement?.isConnected) {
4241
+ this.lastFocusedElement.focus();
4242
+ }
3591
4243
  this.lastFocusedElement = null;
3592
4244
  }
3593
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
+ }
3594
4253
  inject(DestroyRef).onDestroy(() => this.focusTrap?.destroy());
3595
4254
  }
3596
4255
  /** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
3597
- onEscape() {
4256
+ onEscape(event) {
4257
+ if (!this.open() || event.defaultPrevented) {
4258
+ return;
4259
+ }
3598
4260
  this.open.set(false);
3599
4261
  }
3600
4262
  getOrCreateFocusTrap() {
@@ -3604,14 +4266,21 @@ class Drawer {
3604
4266
  return this.focusTrap;
3605
4267
  }
3606
4268
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
3607
- 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"] }] });
3608
4270
  }
3609
4271
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, decorators: [{
3610
4272
  type: Component,
3611
- 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"] }]
3612
- }], 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: [{
3613
4282
  type: HostListener,
3614
- args: ['document:keydown.escape']
4283
+ args: ['document:keydown.escape', ['$event']]
3615
4284
  }] } });
3616
4285
 
3617
4286
  /**
@@ -3782,6 +4451,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3782
4451
  * está en vuelo).
3783
4452
  *
3784
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" />
3785
4475
  */
3786
4476
  class Input {
3787
4477
  static nextId = 0;
@@ -3816,12 +4506,34 @@ class Input {
3816
4506
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
3817
4507
  placeholder = input('', /* @ts-ignore */
3818
4508
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
3819
- /** Teclado virtual sugerido en mobile (ej. `"numeric"` para montos o
3820
- * 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. */
3821
4522
  inputmode = input(/* @ts-ignore */
3822
4523
  ...(ngDevMode ? [undefined, { debugName: "inputmode" }] : /* istanbul ignore next */ []));
3823
4524
  /** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
3824
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();
3825
4537
  inputId = `kf-input-${Input.nextId++}`;
3826
4538
  hintId = `${this.inputId}-hint`;
3827
4539
  errorId = `${this.inputId}-error`;
@@ -3841,6 +4553,9 @@ class Input {
3841
4553
  /** El botón solo tiene sentido si el campo realmente es de contraseña. */
3842
4554
  showPasswordToggle = computed(() => this.toggleable() && this.type() === 'password', /* @ts-ignore */
3843
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 */ []));
3844
4559
  /**
3845
4560
  * `type` real que recibe el `<input>` nativo: igual a `type()` salvo
3846
4561
  * cuando el toggle está activo y visible, donde pasa a `text` para
@@ -3859,6 +4574,10 @@ class Input {
3859
4574
  // ya cargado (ngModel/formControl con valor inicial) se refleje siempre.
3860
4575
  this.nativeInput().nativeElement.value = this.value();
3861
4576
  }
4577
+ /** Enfoca el `<input>` nativo (mismas opciones que `HTMLElement.focus`). */
4578
+ focus(options) {
4579
+ this.nativeInput().nativeElement.focus(options);
4580
+ }
3862
4581
  writeValue(value) {
3863
4582
  const nextValue = value ?? '';
3864
4583
  this.value.set(nextValue);
@@ -3888,6 +4607,20 @@ class Input {
3888
4607
  this.focused.set(false);
3889
4608
  this.onTouched();
3890
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
+ }
3891
4624
  /**
3892
4625
  * Alterna la visibilidad del valor. No dispara `onBlur`/pierde el foco:
3893
4626
  * el botón vive dentro del mismo `.kf-input__field`, así que un click acá
@@ -3897,13 +4630,13 @@ class Input {
3897
4630
  this.passwordVisible.update((visible) => !visible);
3898
4631
  }
3899
4632
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Component });
3900
- 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: [
3901
4634
  {
3902
4635
  provide: NG_VALUE_ACCESSOR,
3903
4636
  useExisting: forwardRef(() => Input),
3904
4637
  multi: true,
3905
4638
  },
3906
- ], 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 });
3907
4640
  }
3908
4641
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, decorators: [{
3909
4642
  type: Component,
@@ -3913,10 +4646,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3913
4646
  useExisting: forwardRef(() => Input),
3914
4647
  multi: true,
3915
4648
  },
3916
- ], 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"] }]
3917
- }], 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"] }] } });
3918
4651
 
3919
- let nextId = 0;
4652
+ let nextId$1 = 0;
3920
4653
  /**
3921
4654
  * Dropdown de selección múltiple con checkboxes internos. El panel flotante
3922
4655
  * se conecta al trigger vía `@angular/cdk/overlay` — el mismo mecanismo que
@@ -3973,7 +4706,7 @@ class MultiSelect {
3973
4706
  searchThreshold = input(7, /* @ts-ignore */
3974
4707
  ...(ngDevMode ? [{ debugName: "searchThreshold" }] : /* istanbul ignore next */ []));
3975
4708
  valuesChange = output();
3976
- instanceId = `kf-multi-select-${nextId++}`;
4709
+ instanceId = `kf-multi-select-${nextId$1++}`;
3977
4710
  triggerId = `${this.instanceId}-trigger`;
3978
4711
  listboxId = `${this.instanceId}-listbox`;
3979
4712
  isOpen = signal(false, /* @ts-ignore */
@@ -4170,7 +4903,7 @@ class Filter {
4170
4903
  this.fieldChange.emit({ key, value });
4171
4904
  }
4172
4905
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, deps: [], target: i0.ɵɵFactoryTarget.Component });
4173
- 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"] }] });
4174
4907
  }
4175
4908
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, decorators: [{
4176
4909
  type: Component,
@@ -4181,19 +4914,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4181
4914
  * Fila de chips tipo "pill" para filtros de una sola selección.
4182
4915
  *
4183
4916
  * Cada opción puede pasarse como string simple (se usa como value y label
4184
- * a la vez) o como objeto { value, label } cuando el texto mostrado difiere
4185
- * 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.
4186
4919
  *
4187
4920
  * El chip activo se resalta con `--kf-color-primary` — mismo color de marca
4188
4921
  * que `kf-tabs`/`kf-select` para su propio estado activo, así el filtro
4189
4922
  * seleccionado se lee con el color del tema activo, no un negro fijo que
4190
4923
  * no cambia entre marcas.
4191
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
+ *
4192
4930
  * <kf-filter-chips [options]="['Todos', 'Activos', 'Archivados']"
4193
4931
  * [value]="filtro"
4194
4932
  * (valueChange)="filtro = $event" />
4195
4933
  *
4196
- * <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 }]"
4197
4942
  * [value]="filtro"
4198
4943
  * (valueChange)="filtro = $event" />
4199
4944
  */
@@ -4202,23 +4947,34 @@ class FilterChips {
4202
4947
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4203
4948
  value = input('', /* @ts-ignore */
4204
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 });
4205
4958
  valueChange = output();
4206
- /** Normaliza cada opción string/objeto a la forma { value, label }. */
4959
+ /** Normaliza cada opción string/objeto a la forma `{ value, label, count? }`. */
4207
4960
  normalizedOptions = computed(() => this.options().map((option) => typeof option === 'string' ? { value: option, label: option } : option), /* @ts-ignore */
4208
4961
  ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
4209
4962
  select(value) {
4210
4963
  if (value === this.value()) {
4964
+ if (this.deselectable() && value !== '') {
4965
+ this.valueChange.emit('');
4966
+ }
4211
4967
  return;
4212
4968
  }
4213
4969
  this.valueChange.emit(value);
4214
4970
  }
4215
4971
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FilterChips, deps: [], target: i0.ɵɵFactoryTarget.Component });
4216
- 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"] });
4217
4973
  }
4218
4974
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FilterChips, decorators: [{
4219
4975
  type: Component,
4220
- 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"] }]
4221
- }], 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"] }] } });
4222
4978
 
4223
4979
  /**
4224
4980
  * Pie de página general del portal (página 38): logo + copyright a la
@@ -4335,6 +5091,408 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4335
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"] }]
4336
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 }] }] } });
4337
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
+
4338
5496
  /**
4339
5497
  * Card de estadística/KPI: label, valor grande, ícono opcional en la esquina
4340
5498
  * superior derecha, badge de esquina opcional (pegado al valor) y una nota
@@ -4464,6 +5622,8 @@ function installModalAwareOverlayContainer(container) {
4464
5622
  }
4465
5623
 
4466
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"])';
4467
5627
  /**
4468
5628
  * Diálogo centrado sobre un backdrop: confirmaciones destructivas, formularios
4469
5629
  * cortos, decisiones que bloquean el flujo.
@@ -4501,6 +5661,27 @@ let modalId = 0;
4501
5661
  *
4502
5662
  * <kf-link footer class="kf-modal__footer-start" disabled>Ver documento</kf-link>
4503
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>
4504
5685
  */
4505
5686
  class Modal {
4506
5687
  title = input('', /* @ts-ignore */
@@ -4516,9 +5697,20 @@ class Modal {
4516
5697
  * angostas, mismo `min()` de siempre. */
4517
5698
  width = input(null, /* @ts-ignore */
4518
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 */ []));
4519
5709
  titleId = `kf-modal-title-${modalId++}`;
4520
5710
  dialog = viewChild('dialog', /* @ts-ignore */
4521
5711
  ...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
5712
+ panel = viewChild('panel', /* @ts-ignore */
5713
+ ...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
4522
5714
  document = inject(DOCUMENT);
4523
5715
  overlayContainer = inject(OverlayContainer);
4524
5716
  constructor() {
@@ -4542,6 +5734,7 @@ class Modal {
4542
5734
  else {
4543
5735
  el.open = true;
4544
5736
  }
5737
+ this.focusInitial();
4545
5738
  }
4546
5739
  else if (!this.open() && el.open) {
4547
5740
  if (typeof el.close === 'function') {
@@ -4573,6 +5766,24 @@ class Modal {
4573
5766
  }
4574
5767
  });
4575
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
+ }
4576
5787
  /** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
4577
5788
  onCancel(event) {
4578
5789
  if (!this.dismissible()) {
@@ -4594,12 +5805,12 @@ class Modal {
4594
5805
  }
4595
5806
  }
4596
5807
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Modal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4597
- 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"] }] });
4598
5809
  }
4599
5810
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Modal, decorators: [{
4600
5811
  type: Component,
4601
- 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"] }]
4602
- }], 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: [{
4603
5814
  type: HostListener,
4604
5815
  args: ['click', ['$event']]
4605
5816
  }] } });
@@ -4631,6 +5842,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4631
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"] }]
4632
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 }] }] } });
4633
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
+
4634
5940
  const MOBILE_STEP_DOWN = {
4635
5941
  xxl: 'xl',
4636
5942
  xl: 'lg',
@@ -4863,7 +6169,7 @@ class PortalHeader {
4863
6169
  }, /* @ts-ignore */
4864
6170
  ...(ngDevMode ? [{ debugName: "thumbStyle" }] : /* istanbul ignore next */ []));
4865
6171
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
4866
- 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"] }] });
4867
6173
  }
4868
6174
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, decorators: [{
4869
6175
  type: Component,
@@ -5378,11 +6684,11 @@ class Segmented {
5378
6684
  this.valueChange.emit(nextValue);
5379
6685
  }
5380
6686
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Segmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
5381
- 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"] });
5382
6688
  }
5383
6689
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Segmented, decorators: [{
5384
6690
  type: Component,
5385
- 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"] }]
5386
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"] }] } });
5387
6693
 
5388
6694
  /**
@@ -5444,6 +6750,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5444
6750
  * estado activo. `collapsed` oculta el label y centra el ícono, controlado
5445
6751
  * por el propio shell. No sabe nada de routing: emite `href` como
5446
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).
5447
6756
  */
5448
6757
  class SidebarNavItem {
5449
6758
  icon = input.required(/* @ts-ignore */
@@ -5455,11 +6764,11 @@ class SidebarNavItem {
5455
6764
  active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5456
6765
  collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5457
6766
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
5458
- 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"] }] });
5459
6768
  }
5460
6769
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarNavItem, decorators: [{
5461
6770
  type: Component,
5462
- 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"] }]
5463
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 }] }] } });
5464
6773
 
5465
6774
  const MOBILE_BREAKPOINT_QUERY = '(max-width: 900px)';
@@ -6342,5 +7651,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
6342
7651
  * Generated bundle index. Do not edit.
6343
7652
  */
6344
7653
 
6345
- 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 };
6346
7655
  //# sourceMappingURL=kaufmann-lab-ui-core.mjs.map