@kaufmann-lab/ui 0.1.18 → 0.1.19

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, numberAttribute, contentChildren, signal, DestroyRef, viewChild, forwardRef, TemplateRef, contentChild, ElementRef, ViewContainerRef, HostListener, afterRenderEffect, afterNextRender, ChangeDetectionStrategy, viewChildren, Injector, Input as Input$1, Directive, Injectable } 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';
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';
@@ -1385,6 +1385,304 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
1385
1385
  args: [{ selector: 'kf-bucket-progress', template: "<div class=\"kf-bucket-progress__head\">\n <span class=\"kf-bucket-progress__title\">{{ title() }}</span>\n @if (summary()) {\n <span class=\"kf-bucket-progress__summary\">{{ summary() }}</span>\n }\n</div>\n\n<div class=\"kf-bucket-progress__bar\" role=\"img\" [attr.aria-label]=\"ariaLabel()\">\n @for (item of visibleItems(); track item.key) {\n <span\n class=\"kf-bucket-progress__segment\"\n [style.flex-grow]=\"item.count\"\n [style.background]=\"item.color\"\n ></span>\n }\n</div>\n\n<div class=\"kf-bucket-progress__legend\">\n @for (item of visibleItems(); track item.key) {\n <span class=\"kf-bucket-progress__legend-item\">\n <span class=\"kf-bucket-progress__dot\" [style.background]=\"item.color\"></span>\n {{ item.count }} {{ item.label }}\n </span>\n }\n</div>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-sm);font-family:var(--kf-font-body)}.kf-bucket-progress__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);flex-wrap:wrap}.kf-bucket-progress__title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink);font-size:.95rem}.kf-bucket-progress__summary{font-size:.85rem;color:var(--kf-color-text-muted)}.kf-bucket-progress__bar{display:flex;gap:2px;height:8px}.kf-bucket-progress__segment{border-radius:2px;transition:flex-grow .3s ease}@media(prefers-reduced-motion:reduce){.kf-bucket-progress__segment{transition:none}}.kf-bucket-progress__legend{display:flex;flex-wrap:wrap;gap:var(--kf-space-md)}.kf-bucket-progress__legend-item{display:flex;align-items:center;gap:6px;font-size:.78rem;color:var(--kf-color-text-muted)}.kf-bucket-progress__dot{width:8px;height:8px;border-radius:2px;flex:none}\n"] }]
1386
1386
  }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], summary: [{ type: i0.Input, args: [{ isSignal: true, alias: "summary", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }] } });
1387
1387
 
1388
+ const DEFAULT_MONTH_NAMES = [
1389
+ 'Enero',
1390
+ 'Febrero',
1391
+ 'Marzo',
1392
+ 'Abril',
1393
+ 'Mayo',
1394
+ 'Junio',
1395
+ 'Julio',
1396
+ 'Agosto',
1397
+ 'Septiembre',
1398
+ 'Octubre',
1399
+ 'Noviembre',
1400
+ 'Diciembre',
1401
+ ];
1402
+ const DEFAULT_WEEKDAY_LABELS = ['L', 'M', 'M', 'J', 'V', 'S', 'D'];
1403
+ const DEFAULT_WEEKDAY_NAMES = [
1404
+ 'lunes',
1405
+ 'martes',
1406
+ 'miércoles',
1407
+ 'jueves',
1408
+ 'viernes',
1409
+ 'sábado',
1410
+ 'domingo',
1411
+ ];
1412
+ /**
1413
+ * Selector de fecha de un solo mes, con grilla semanal que parte en lunes.
1414
+ *
1415
+ * Las fechas viajan como strings ISO (`YYYY-MM-DD`, y `YYYY-MM` para el mes
1416
+ * visible) y se calculan en UTC, así la zona horaria del navegador nunca
1417
+ * corre una fecha un día.
1418
+ *
1419
+ * `availableDates` en `null` deja seleccionable toda fecha dentro de
1420
+ * `min`/`max`; un arreglo deja seleccionables SOLO esas fechas (el resto se
1421
+ * rinde deshabilitado). `markedDates` es independiente: agrega un punto
1422
+ * marcador sin afectar si la fecha se puede elegir.
1423
+ *
1424
+ * Patrón WAI-ARIA `grid` con roving tabindex: las flechas mueven por
1425
+ * día/semana, Home/End van al inicio/fin de la semana y PageUp/PageDown
1426
+ * cambian de mes; el foco cruza los límites del mes.
1427
+ *
1428
+ * <kf-calendar
1429
+ * [(value)]="date"
1430
+ * [(month)]="month"
1431
+ * [min]="today"
1432
+ * [availableDates]="freeDates()"
1433
+ * [markedDates]="eventDates()"
1434
+ * [loading]="loadingAvailability()"
1435
+ * >
1436
+ * <p footer>Leyenda</p>
1437
+ * </kf-calendar>
1438
+ */
1439
+ class Calendar {
1440
+ static nextId = 0;
1441
+ /** Fecha seleccionada, ISO `YYYY-MM-DD`. */
1442
+ value = model(null, /* @ts-ignore */
1443
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
1444
+ /** Mes visible, `YYYY-MM`. En `null` se deriva de `value` y, si no hay, de hoy. */
1445
+ month = model(null, /* @ts-ignore */
1446
+ ...(ngDevMode ? [{ debugName: "month" }] : /* istanbul ignore next */ []));
1447
+ /** Fecha mínima seleccionable (inclusive), ISO. */
1448
+ min = input(null, /* @ts-ignore */
1449
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
1450
+ /** Fecha máxima seleccionable (inclusive), ISO. */
1451
+ max = input(null, /* @ts-ignore */
1452
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
1453
+ /** `null`: toda fecha dentro de `min`/`max` es seleccionable. Arreglo: solo esas fechas. */
1454
+ availableDates = input(null, /* @ts-ignore */
1455
+ ...(ngDevMode ? [{ debugName: "availableDates" }] : /* istanbul ignore next */ []));
1456
+ /** Fechas con punto marcador, independiente de si son seleccionables. */
1457
+ markedDates = input([], /* @ts-ignore */
1458
+ ...(ngDevMode ? [{ debugName: "markedDates" }] : /* istanbul ignore next */ []));
1459
+ /** Reemplaza el "hoy" local (tests/SSR), ISO. */
1460
+ today = input(null, /* @ts-ignore */
1461
+ ...(ngDevMode ? [{ debugName: "today" }] : /* istanbul ignore next */ []));
1462
+ /** Atenúa la grilla y bloquea la selección mientras carga la disponibilidad. */
1463
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1464
+ ariaLabel = input('Calendario', /* @ts-ignore */
1465
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1466
+ /** Los 12 nombres de mes, partiendo en enero. */
1467
+ monthNames = input(DEFAULT_MONTH_NAMES, /* @ts-ignore */
1468
+ ...(ngDevMode ? [{ debugName: "monthNames" }] : /* istanbul ignore next */ []));
1469
+ /** Las 7 iniciales visibles de los días, partiendo en lunes. */
1470
+ weekdayLabels = input(DEFAULT_WEEKDAY_LABELS, /* @ts-ignore */
1471
+ ...(ngDevMode ? [{ debugName: "weekdayLabels" }] : /* istanbul ignore next */ []));
1472
+ /** Los 7 nombres completos de los días, partiendo en lunes — se usan en los nombres accesibles. */
1473
+ weekdayNames = input(DEFAULT_WEEKDAY_NAMES, /* @ts-ignore */
1474
+ ...(ngDevMode ? [{ debugName: "weekdayNames" }] : /* istanbul ignore next */ []));
1475
+ /** Se agrega al nombre accesible de una fecha marcada. */
1476
+ markedLabel = input('marcado', /* @ts-ignore */
1477
+ ...(ngDevMode ? [{ debugName: "markedLabel" }] : /* istanbul ignore next */ []));
1478
+ previousMonthLabel = input('Mes anterior', /* @ts-ignore */
1479
+ ...(ngDevMode ? [{ debugName: "previousMonthLabel" }] : /* istanbul ignore next */ []));
1480
+ nextMonthLabel = input('Mes siguiente', /* @ts-ignore */
1481
+ ...(ngDevMode ? [{ debugName: "nextMonthLabel" }] : /* istanbul ignore next */ []));
1482
+ host = inject(ElementRef);
1483
+ injector = inject(Injector);
1484
+ focusedDate = signal(null, /* @ts-ignore */
1485
+ ...(ngDevMode ? [{ debugName: "focusedDate" }] : /* istanbul ignore next */ []));
1486
+ titleId = `kf-calendar-title-${Calendar.nextId++}`;
1487
+ todayIso = computed(() => this.today() ?? localTodayIso(), /* @ts-ignore */
1488
+ ...(ngDevMode ? [{ debugName: "todayIso" }] : /* istanbul ignore next */ []));
1489
+ displayedMonth = computed(() => this.month() ?? this.value()?.slice(0, 7) ?? this.todayIso().slice(0, 7), /* @ts-ignore */
1490
+ ...(ngDevMode ? [{ debugName: "displayedMonth" }] : /* istanbul ignore next */ []));
1491
+ title = computed(() => {
1492
+ const [year, month] = parseParts(this.displayedMonth());
1493
+ return `${this.monthNames()[month - 1]} ${year}`;
1494
+ }, /* @ts-ignore */
1495
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1496
+ minMonth = computed(() => this.min()?.slice(0, 7) ?? null, /* @ts-ignore */
1497
+ ...(ngDevMode ? [{ debugName: "minMonth" }] : /* istanbul ignore next */ []));
1498
+ maxMonth = computed(() => this.max()?.slice(0, 7) ?? null, /* @ts-ignore */
1499
+ ...(ngDevMode ? [{ debugName: "maxMonth" }] : /* istanbul ignore next */ []));
1500
+ canGoPrevious = computed(() => this.isMonthNavigable(shiftMonth(this.displayedMonth(), -1)), /* @ts-ignore */
1501
+ ...(ngDevMode ? [{ debugName: "canGoPrevious" }] : /* istanbul ignore next */ []));
1502
+ canGoNext = computed(() => this.isMonthNavigable(shiftMonth(this.displayedMonth(), 1)), /* @ts-ignore */
1503
+ ...(ngDevMode ? [{ debugName: "canGoNext" }] : /* istanbul ignore next */ []));
1504
+ availableSet = computed(() => {
1505
+ const dates = this.availableDates();
1506
+ return dates ? new Set(dates) : null;
1507
+ }, /* @ts-ignore */
1508
+ ...(ngDevMode ? [{ debugName: "availableSet" }] : /* istanbul ignore next */ []));
1509
+ markedSet = computed(() => new Set(this.markedDates()), /* @ts-ignore */
1510
+ ...(ngDevMode ? [{ debugName: "markedSet" }] : /* istanbul ignore next */ []));
1511
+ weeks = computed(() => {
1512
+ const month = this.displayedMonth();
1513
+ const firstDay = `${month}-01`;
1514
+ const lastDay = addDays(`${shiftMonth(month, 1)}-01`, -1);
1515
+ const start = addDays(firstDay, -weekdayIndex(firstDay));
1516
+ const end = addDays(lastDay, 6 - weekdayIndex(lastDay));
1517
+ const weeks = [];
1518
+ let week = [];
1519
+ for (let iso = start; iso <= end; iso = addDays(iso, 1)) {
1520
+ week.push(this.buildDay(iso, month));
1521
+ if (week.length === 7) {
1522
+ weeks.push(week);
1523
+ week = [];
1524
+ }
1525
+ }
1526
+ return weeks;
1527
+ }, /* @ts-ignore */
1528
+ ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
1529
+ /** La única celda con `tabindex="0"` (roving tabindex). */
1530
+ activeDate = computed(() => {
1531
+ const month = this.displayedMonth();
1532
+ const inMonth = (iso) => iso?.slice(0, 7) === month;
1533
+ const focused = this.focusedDate();
1534
+ if (inMonth(focused)) {
1535
+ return focused;
1536
+ }
1537
+ const value = this.value();
1538
+ if (inMonth(value)) {
1539
+ return value;
1540
+ }
1541
+ const today = this.todayIso();
1542
+ if (inMonth(today)) {
1543
+ return today;
1544
+ }
1545
+ const firstSelectable = this.weeks()
1546
+ .flat()
1547
+ .find((day) => day.selectable);
1548
+ return firstSelectable?.iso ?? `${month}-01`;
1549
+ }, /* @ts-ignore */
1550
+ ...(ngDevMode ? [{ debugName: "activeDate" }] : /* istanbul ignore next */ []));
1551
+ previousMonth() {
1552
+ if (this.canGoPrevious()) {
1553
+ this.month.set(shiftMonth(this.displayedMonth(), -1));
1554
+ }
1555
+ }
1556
+ nextMonth() {
1557
+ if (this.canGoNext()) {
1558
+ this.month.set(shiftMonth(this.displayedMonth(), 1));
1559
+ }
1560
+ }
1561
+ selectDay(day) {
1562
+ if (!day.selectable || this.loading()) {
1563
+ return;
1564
+ }
1565
+ this.focusedDate.set(day.iso);
1566
+ if (day.iso !== this.value()) {
1567
+ this.value.set(day.iso);
1568
+ }
1569
+ }
1570
+ onDayFocus(iso) {
1571
+ this.focusedDate.set(iso);
1572
+ }
1573
+ onDayKeydown(event, iso) {
1574
+ const target = keyboardTarget(event.key, iso);
1575
+ if (target === null) {
1576
+ return;
1577
+ }
1578
+ event.preventDefault();
1579
+ const targetMonth = target.slice(0, 7);
1580
+ if (!this.isMonthNavigable(targetMonth)) {
1581
+ return;
1582
+ }
1583
+ this.focusedDate.set(target);
1584
+ if (targetMonth !== this.displayedMonth()) {
1585
+ this.month.set(targetMonth);
1586
+ }
1587
+ // Si cambió el mes, la celda destino todavía no existe en el DOM.
1588
+ afterNextRender(() => this.focusDay(target), { injector: this.injector });
1589
+ }
1590
+ focusDay(iso) {
1591
+ this.host.nativeElement.querySelector(`[data-date="${iso}"]`)?.focus();
1592
+ }
1593
+ isMonthNavigable(month) {
1594
+ const min = this.minMonth();
1595
+ const max = this.maxMonth();
1596
+ return (min === null || month >= min) && (max === null || month <= max);
1597
+ }
1598
+ buildDay(iso, displayedMonth) {
1599
+ const [year, month, day] = parseParts(iso);
1600
+ const inMonth = iso.slice(0, 7) === displayedMonth;
1601
+ const min = this.min();
1602
+ const max = this.max();
1603
+ const available = this.availableSet();
1604
+ const marked = this.markedSet().has(iso);
1605
+ const weekdayName = this.weekdayNames()[weekdayIndex(iso)];
1606
+ const monthName = this.monthNames()[month - 1].toLowerCase();
1607
+ const baseLabel = `${weekdayName} ${day} de ${monthName} de ${year}`;
1608
+ return {
1609
+ iso,
1610
+ day,
1611
+ inMonth,
1612
+ selectable: inMonth &&
1613
+ (min === null || iso >= min) &&
1614
+ (max === null || iso <= max) &&
1615
+ (available === null || available.has(iso)),
1616
+ selected: iso === this.value(),
1617
+ marked,
1618
+ today: iso === this.todayIso(),
1619
+ ariaLabel: marked ? `${baseLabel}, ${this.markedLabel()}` : baseLabel,
1620
+ };
1621
+ }
1622
+ 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 });
1624
+ }
1625
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Calendar, decorators: [{
1626
+ type: Component,
1627
+ args: [{ selector: 'kf-calendar', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Button, Text], 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"] }]
1628
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], month: [{ type: i0.Input, args: [{ isSignal: true, alias: "month", required: false }] }, { type: i0.Output, args: ["monthChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], availableDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableDates", required: false }] }], markedDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "markedDates", required: false }] }], today: [{ type: i0.Input, args: [{ isSignal: true, alias: "today", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], monthNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "monthNames", required: false }] }], weekdayLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayLabels", required: false }] }], weekdayNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayNames", required: false }] }], markedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "markedLabel", required: false }] }], previousMonthLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "previousMonthLabel", required: false }] }], nextMonthLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextMonthLabel", required: false }] }] } });
1629
+ function pad(value) {
1630
+ return String(value).padStart(2, '0');
1631
+ }
1632
+ function parseParts(iso) {
1633
+ return iso.split('-').map(Number);
1634
+ }
1635
+ function fromUtc(date) {
1636
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`;
1637
+ }
1638
+ function localTodayIso() {
1639
+ const now = new Date();
1640
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
1641
+ }
1642
+ function addDays(iso, days) {
1643
+ const [year, month, day] = parseParts(iso);
1644
+ return fromUtc(new Date(Date.UTC(year, month - 1, day + days)));
1645
+ }
1646
+ /** 0 = lunes … 6 = domingo. */
1647
+ function weekdayIndex(iso) {
1648
+ const [year, month, day] = parseParts(iso);
1649
+ return (new Date(Date.UTC(year, month - 1, day)).getUTCDay() + 6) % 7;
1650
+ }
1651
+ function shiftMonth(yearMonth, delta) {
1652
+ const [year, month] = parseParts(yearMonth);
1653
+ const date = new Date(Date.UTC(year, month - 1 + delta, 1));
1654
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}`;
1655
+ }
1656
+ /** Mismo día en otro mes, acotado al último día de ese mes. */
1657
+ function shiftDateByMonths(iso, delta) {
1658
+ const targetMonth = shiftMonth(iso.slice(0, 7), delta);
1659
+ const lastDay = parseParts(addDays(`${shiftMonth(targetMonth, 1)}-01`, -1))[2];
1660
+ const day = Math.min(parseParts(iso)[2], lastDay);
1661
+ return `${targetMonth}-${pad(day)}`;
1662
+ }
1663
+ function keyboardTarget(key, iso) {
1664
+ switch (key) {
1665
+ case 'ArrowLeft':
1666
+ return addDays(iso, -1);
1667
+ case 'ArrowRight':
1668
+ return addDays(iso, 1);
1669
+ case 'ArrowUp':
1670
+ return addDays(iso, -7);
1671
+ case 'ArrowDown':
1672
+ return addDays(iso, 7);
1673
+ case 'Home':
1674
+ return addDays(iso, -weekdayIndex(iso));
1675
+ case 'End':
1676
+ return addDays(iso, 6 - weekdayIndex(iso));
1677
+ case 'PageUp':
1678
+ return shiftDateByMonths(iso, -1);
1679
+ case 'PageDown':
1680
+ return shiftDateByMonths(iso, 1);
1681
+ default:
1682
+ return null;
1683
+ }
1684
+ }
1685
+
1388
1686
  /**
1389
1687
  * Contenedor base con esquinas redondeadas y sombra sutil — la base visual
1390
1688
  * de paneles, tarjetas de métrica, tablas y bloques de contenido de todo el
@@ -3510,6 +3808,10 @@ class Input {
3510
3808
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
3511
3809
  placeholder = input('', /* @ts-ignore */
3512
3810
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
3811
+ /** Teclado virtual sugerido en mobile (ej. `"numeric"` para montos o
3812
+ * kilometraje) — sin valor, el navegador decide según `type`. */
3813
+ inputmode = input(/* @ts-ignore */
3814
+ ...(ngDevMode ? [undefined, { debugName: "inputmode" }] : /* istanbul ignore next */ []));
3513
3815
  /** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
3514
3816
  toggleable = input(false, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3515
3817
  inputId = `kf-input-${Input.nextId++}`;
@@ -3587,13 +3889,13 @@ class Input {
3587
3889
  this.passwordVisible.update((visible) => !visible);
3588
3890
  }
3589
3891
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Component });
3590
- 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 }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
3892
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Input, isStandalone: true, selector: "kf-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
3591
3893
  {
3592
3894
  provide: NG_VALUE_ACCESSOR,
3593
3895
  useExisting: forwardRef(() => Input),
3594
3896
  multi: true,
3595
3897
  },
3596
- ], 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 [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 });
3898
+ ], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3597
3899
  }
3598
3900
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, decorators: [{
3599
3901
  type: Component,
@@ -3603,8 +3905,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3603
3905
  useExisting: forwardRef(() => Input),
3604
3906
  multi: true,
3605
3907
  },
3606
- ], 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 [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"] }]
3607
- }], 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 }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }] } });
3908
+ ], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
3909
+ }], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }] } });
3608
3910
 
3609
3911
  let nextId = 0;
3610
3912
  /**
@@ -3860,7 +4162,7 @@ class Filter {
3860
4162
  this.fieldChange.emit({ key, value });
3861
4163
  }
3862
4164
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, deps: [], target: i0.ɵɵFactoryTarget.Component });
3863
- 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", "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"] }] });
4165
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Filter, isStandalone: true, selector: "kf-filter", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mobileTitle: { classPropertyName: "mobileTitle", publicName: "mobileTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange" }, ngImport: i0, template: "<ng-template #fieldsTpl let-stacked=\"stacked\">\n @for (field of fields(); track field.key) {\n @switch (field.type) {\n @case ('select') {\n <kf-select\n [label]=\"field.label ?? ''\"\n [options]=\"optionsFor(field.key)\"\n [value]=\"singleValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valueChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('multi-select') {\n <kf-multi-select\n [label]=\"field.label ?? ''\"\n [options]=\"multiOptionsFor(field.key)\"\n [values]=\"multiValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valuesChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('search') {\n <kf-input\n [label]=\"field.label ?? ''\"\n [icon]=\"field.icon ?? 'search'\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n [ngModel]=\"singleValueFor(field.key)\"\n (ngModelChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n }\n @if (!stacked && field.showDivider) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n</ng-template>\n\n<div class=\"kf-filter kf-filter--row\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: false }\" />\n</div>\n\n<kf-collapsible class=\"kf-filter__collapsible\" [title]=\"mobileTitle()\">\n <div class=\"kf-filter kf-filter--stack\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: true }\" />\n </div>\n</kf-collapsible>\n", styles: ["@charset \"UTF-8\";:host{display:block}.kf-filter--row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--kf-space-md)}.kf-filter--stack{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-filter__collapsible{display:none}@media(max-width:768px){.kf-filter--row{display:none}.kf-filter__collapsible{display:block}}\n"], dependencies: [{ kind: "component", type: Collapsible, selector: "kf-collapsible", inputs: ["title", "expanded"], outputs: ["expandedChange"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "inputmode", "toggleable"] }, { kind: "component", type: MultiSelect, selector: "kf-multi-select", inputs: ["label", "options", "values", "placeholder", "disabled", "width", "searchable", "searchThreshold"], outputs: ["valuesChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold"], outputs: ["valueChange"] }] });
3864
4166
  }
3865
4167
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, decorators: [{
3866
4168
  type: Component,
@@ -4374,6 +4676,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4374
4676
  * <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
4375
4677
  * </kf-panel>
4376
4678
  *
4679
+ * Para apps con router: escuchar `actionClick` y llamar
4680
+ * `event.preventDefault()` antes de `router.navigateByUrl(...)`. El
4681
+ * `actionHref` se mantiene igual para que click-medio, "abrir en pestaña
4682
+ * nueva" y el hover de la barra de estado sigan funcionando.
4683
+ *
4684
+ * <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos"
4685
+ * (actionClick)="$event.preventDefault(); router.navigateByUrl('/pagos')">
4686
+ * …
4687
+ * </kf-panel>
4688
+ *
4377
4689
  * El host y el `kf-card` interno son flex column con `flex:1` de punta a
4378
4690
  * punta — si un grid/flex externo estira `kf-panel` a la altura de un
4379
4691
  * hermano más alto, el contenido proyectado hereda esa altura en vez de
@@ -4388,13 +4700,19 @@ class Panel {
4388
4700
  ...(ngDevMode ? [undefined, { debugName: "actionLabel" }] : /* istanbul ignore next */ []));
4389
4701
  actionHref = input(/* @ts-ignore */
4390
4702
  ...(ngDevMode ? [undefined, { debugName: "actionHref" }] : /* istanbul ignore next */ []));
4703
+ /**
4704
+ * Click en el link de acción. Incluye el `MouseEvent` para que un
4705
+ * consumidor con router pueda cortar la navegación nativa — mismo patrón
4706
+ * que `itemClick` de `kf-breadcrumb`, sin depender de `@angular/router`.
4707
+ */
4708
+ actionClick = output();
4391
4709
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, deps: [], target: i0.ɵɵFactoryTarget.Component });
4392
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Panel, isStandalone: true, selector: "kf-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link class=\"kf-panel__action\" [href]=\"actionHref() || '#'\" showArrow>\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
4710
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Panel, isStandalone: true, selector: "kf-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
4393
4711
  }
4394
4712
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, decorators: [{
4395
4713
  type: Component,
4396
- args: [{ selector: 'kf-panel', imports: [Card, Divider, Link], template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link class=\"kf-panel__action\" [href]=\"actionHref() || '#'\" showArrow>\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
4397
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }] } });
4714
+ args: [{ selector: 'kf-panel', imports: [Card, Divider, Link], template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
4715
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
4398
4716
 
4399
4717
  /**
4400
4718
  * Cabecera completa del portal (página 31) — franja de administrador
@@ -4570,11 +4888,11 @@ class Progress {
4570
4888
  clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100), /* @ts-ignore */
4571
4889
  ...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
4572
4890
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, deps: [], target: i0.ɵɵFactoryTarget.Component });
4573
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Progress, isStandalone: true, selector: "kf-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] });
4891
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Progress, isStandalone: true, selector: "kf-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] });
4574
4892
  }
4575
4893
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, decorators: [{
4576
4894
  type: Component,
4577
- args: [{ selector: 'kf-progress', template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] }]
4895
+ args: [{ selector: 'kf-progress', template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] }]
4578
4896
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
4579
4897
 
4580
4898
  /**
@@ -5314,6 +5632,12 @@ class StatTile {
5314
5632
  ...(ngDevMode ? [undefined, { debugName: "actionLabel" }] : /* istanbul ignore next */ []));
5315
5633
  actionHref = input(/* @ts-ignore */
5316
5634
  ...(ngDevMode ? [undefined, { debugName: "actionHref" }] : /* istanbul ignore next */ []));
5635
+ /**
5636
+ * Click en el link de la variante CTA. Incluye el `MouseEvent` para que un
5637
+ * consumidor con router pueda cortar la navegación nativa — mismo patrón
5638
+ * que `actionClick` de `kf-panel`, sin depender de `@angular/router`.
5639
+ */
5640
+ actionClick = output();
5317
5641
  /** Texto real de la nota. `null` significa "no reservar la línea": pasa
5318
5642
  * solo cuando el consumidor no declaró ni nota ni placeholder. */
5319
5643
  noteText = computed(() => this.note() || this.notePlaceholder() || null, /* @ts-ignore */
@@ -5321,15 +5645,15 @@ class StatTile {
5321
5645
  noteIsPlaceholder = computed(() => !this.note() && !!this.notePlaceholder(), /* @ts-ignore */
5322
5646
  ...(ngDevMode ? [{ debugName: "noteIsPlaceholder" }] : /* istanbul ignore next */ []));
5323
5647
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatTile, deps: [], target: i0.ɵɵFactoryTarget.Component });
5324
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: StatTile, isStandalone: true, selector: "kf-stat-tile", inputs: { cta: { classPropertyName: "cta", publicName: "cta", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, notePlaceholder: { classPropertyName: "notePlaceholder", publicName: "notePlaceholder", isSignal: true, isRequired: false, transformFunction: null }, valueMissing: { classPropertyName: "valueMissing", publicName: "valueMissing", isSignal: true, isRequired: false, transformFunction: null }, hasProjectedValue: { classPropertyName: "hasProjectedValue", publicName: "hasProjectedValue", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-stat-tile--cta": "cta()", "class.kf-stat-tile--inline": "!cta() && variant() === \"inline\"" } }, ngImport: i0, template: "@if (cta()) {\n <span class=\"kf-stat-tile__cta-title\">{{ title() }}</span>\n @if (description()) {\n <span class=\"kf-stat-tile__cta-description\">{{ description() }}</span>\n }\n @if (actionLabel()) {\n <kf-link class=\"kf-stat-tile__cta-action\" [href]=\"actionHref() || '#'\" showArrow>\n {{ actionLabel() }}\n </kf-link>\n }\n} @else {\n <div class=\"kf-stat-tile__head\">\n <!-- El label recibe la clase de tone adem\u00E1s del value: juntos forman\n una sola unidad de lectura, y pintar solo el n\u00FAmero la romper\u00EDa. -->\n <span\n class=\"kf-stat-tile__label\"\n [class.kf-stat-tile__label--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__label--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__label--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__label--info]=\"tone() === 'info'\"\n >{{ label() }}</span\n >\n @if (icon(); as iconName) {\n <span class=\"kf-stat-tile__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n </div>\n @if (hasProjectedValue()) {\n <!-- Slot para un valor que no es texto medido (ej. un `kf-badge` de\n estado) \u2014 mismo bloque `__value`, sin las clases de tono, que en ese\n caso las resuelve el propio contenido proyectado (el badge trae su\n tono). -->\n <span class=\"kf-stat-tile__value kf-stat-tile__value--projected\">\n <ng-content select=\"[valueContent]\" />\n </span>\n } @else {\n <span\n class=\"kf-stat-tile__value\"\n [class.kf-stat-tile__value--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__value--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__value--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__value--info]=\"tone() === 'info'\"\n [class.kf-stat-tile__value--missing]=\"valueMissing()\"\n >{{ value() }}</span\n >\n }\n\n <!-- La l\u00EDnea se reserva con o sin nota: un tile m\u00E1s corto desalinea la\n fila. El `title` deja disponible el texto completo, recortado a 2 l\u00EDneas. -->\n @if (noteText(); as note) {\n <span\n class=\"kf-stat-tile__note\"\n [class.kf-stat-tile__note--empty]=\"noteIsPlaceholder()\"\n [title]=\"note\"\n >{{ note }}</span\n >\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);height:100%;min-width:0}:host(.kf-stat-tile--cta){background:var(--kf-color-surface);gap:2px}:host(.kf-stat-tile--inline){flex-direction:row-reverse;align-items:baseline;height:auto;gap:6px;padding:0;border:none;border-radius:0;background:transparent}:host(.kf-stat-tile--inline) .kf-stat-tile__head{width:auto}.kf-stat-tile__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-xs)}.kf-stat-tile__label{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__icon{display:inline-flex;color:var(--kf-color-primary)}.kf-stat-tile__value{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__value--projected{display:flex;align-items:center;font-size:initial;font-weight:initial;color:initial}.kf-stat-tile__value--ok,.kf-stat-tile__label--ok{color:var(--kf-color-success-strong)}.kf-stat-tile__value--warn,.kf-stat-tile__label--warn{color:var(--kf-color-warning-strong)}.kf-stat-tile__value--error,.kf-stat-tile__label--error{color:var(--kf-color-error-strong)}.kf-stat-tile__value--info,.kf-stat-tile__label--info{color:var(--kf-color-info-strong)}.kf-stat-tile__value--missing{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);font-style:italic;color:var(--kf-color-text-muted)}.kf-stat-tile__note{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:var(--kf-font-size-caption);color:var(--kf-color-text-muted);margin-top:auto}.kf-stat-tile__note--empty{font-style:italic;opacity:.75}.kf-stat-tile__cta-title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__cta-description{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__cta-action{margin-top:var(--kf-space-xs);font-size:.8125rem;font-weight:600}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
5648
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: StatTile, isStandalone: true, selector: "kf-stat-tile", inputs: { cta: { classPropertyName: "cta", publicName: "cta", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, notePlaceholder: { classPropertyName: "notePlaceholder", publicName: "notePlaceholder", isSignal: true, isRequired: false, transformFunction: null }, valueMissing: { classPropertyName: "valueMissing", publicName: "valueMissing", isSignal: true, isRequired: false, transformFunction: null }, hasProjectedValue: { classPropertyName: "hasProjectedValue", publicName: "hasProjectedValue", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, host: { properties: { "class.kf-stat-tile--cta": "cta()", "class.kf-stat-tile--inline": "!cta() && variant() === \"inline\"" } }, ngImport: i0, template: "@if (cta()) {\n <span class=\"kf-stat-tile__cta-title\">{{ title() }}</span>\n @if (description()) {\n <span class=\"kf-stat-tile__cta-description\">{{ description() }}</span>\n }\n @if (actionLabel()) {\n <kf-link\n class=\"kf-stat-tile__cta-action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n} @else {\n <div class=\"kf-stat-tile__head\">\n <!-- El label recibe la clase de tone adem\u00E1s del value: juntos forman\n una sola unidad de lectura, y pintar solo el n\u00FAmero la romper\u00EDa. -->\n <span\n class=\"kf-stat-tile__label\"\n [class.kf-stat-tile__label--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__label--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__label--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__label--info]=\"tone() === 'info'\"\n >{{ label() }}</span\n >\n @if (icon(); as iconName) {\n <span class=\"kf-stat-tile__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n </div>\n @if (hasProjectedValue()) {\n <!-- Slot para un valor que no es texto medido (ej. un `kf-badge` de\n estado) \u2014 mismo bloque `__value`, sin las clases de tono, que en ese\n caso las resuelve el propio contenido proyectado (el badge trae su\n tono). -->\n <span class=\"kf-stat-tile__value kf-stat-tile__value--projected\">\n <ng-content select=\"[valueContent]\" />\n </span>\n } @else {\n <span\n class=\"kf-stat-tile__value\"\n [class.kf-stat-tile__value--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__value--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__value--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__value--info]=\"tone() === 'info'\"\n [class.kf-stat-tile__value--missing]=\"valueMissing()\"\n >{{ value() }}</span\n >\n }\n\n <!-- La l\u00EDnea se reserva con o sin nota: un tile m\u00E1s corto desalinea la\n fila. El `title` deja disponible el texto completo, recortado a 2 l\u00EDneas. -->\n @if (noteText(); as note) {\n <span\n class=\"kf-stat-tile__note\"\n [class.kf-stat-tile__note--empty]=\"noteIsPlaceholder()\"\n [title]=\"note\"\n >{{ note }}</span\n >\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);height:100%;min-width:0}:host(.kf-stat-tile--cta){background:var(--kf-color-surface);gap:2px}:host(.kf-stat-tile--inline){flex-direction:row-reverse;align-items:baseline;height:auto;gap:6px;padding:0;border:none;border-radius:0;background:transparent}:host(.kf-stat-tile--inline) .kf-stat-tile__head{width:auto}.kf-stat-tile__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-xs)}.kf-stat-tile__label{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__icon{display:inline-flex;color:var(--kf-color-primary)}.kf-stat-tile__value{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__value--projected{display:flex;align-items:center;font-size:initial;font-weight:initial;color:initial}.kf-stat-tile__value--ok,.kf-stat-tile__label--ok{color:var(--kf-color-success-strong)}.kf-stat-tile__value--warn,.kf-stat-tile__label--warn{color:var(--kf-color-warning-strong)}.kf-stat-tile__value--error,.kf-stat-tile__label--error{color:var(--kf-color-error-strong)}.kf-stat-tile__value--info,.kf-stat-tile__label--info{color:var(--kf-color-info-strong)}.kf-stat-tile__value--missing{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);font-style:italic;color:var(--kf-color-text-muted)}.kf-stat-tile__note{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:var(--kf-font-size-caption);color:var(--kf-color-text-muted);margin-top:auto}.kf-stat-tile__note--empty{font-style:italic;opacity:.75}.kf-stat-tile__cta-title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__cta-description{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__cta-action{margin-top:var(--kf-space-xs);font-size:.8125rem;font-weight:600}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
5325
5649
  }
5326
5650
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatTile, decorators: [{
5327
5651
  type: Component,
5328
5652
  args: [{ selector: 'kf-stat-tile', imports: [Icon, Link], host: {
5329
5653
  '[class.kf-stat-tile--cta]': 'cta()',
5330
5654
  '[class.kf-stat-tile--inline]': '!cta() && variant() === "inline"',
5331
- }, template: "@if (cta()) {\n <span class=\"kf-stat-tile__cta-title\">{{ title() }}</span>\n @if (description()) {\n <span class=\"kf-stat-tile__cta-description\">{{ description() }}</span>\n }\n @if (actionLabel()) {\n <kf-link class=\"kf-stat-tile__cta-action\" [href]=\"actionHref() || '#'\" showArrow>\n {{ actionLabel() }}\n </kf-link>\n }\n} @else {\n <div class=\"kf-stat-tile__head\">\n <!-- El label recibe la clase de tone adem\u00E1s del value: juntos forman\n una sola unidad de lectura, y pintar solo el n\u00FAmero la romper\u00EDa. -->\n <span\n class=\"kf-stat-tile__label\"\n [class.kf-stat-tile__label--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__label--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__label--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__label--info]=\"tone() === 'info'\"\n >{{ label() }}</span\n >\n @if (icon(); as iconName) {\n <span class=\"kf-stat-tile__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n </div>\n @if (hasProjectedValue()) {\n <!-- Slot para un valor que no es texto medido (ej. un `kf-badge` de\n estado) \u2014 mismo bloque `__value`, sin las clases de tono, que en ese\n caso las resuelve el propio contenido proyectado (el badge trae su\n tono). -->\n <span class=\"kf-stat-tile__value kf-stat-tile__value--projected\">\n <ng-content select=\"[valueContent]\" />\n </span>\n } @else {\n <span\n class=\"kf-stat-tile__value\"\n [class.kf-stat-tile__value--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__value--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__value--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__value--info]=\"tone() === 'info'\"\n [class.kf-stat-tile__value--missing]=\"valueMissing()\"\n >{{ value() }}</span\n >\n }\n\n <!-- La l\u00EDnea se reserva con o sin nota: un tile m\u00E1s corto desalinea la\n fila. El `title` deja disponible el texto completo, recortado a 2 l\u00EDneas. -->\n @if (noteText(); as note) {\n <span\n class=\"kf-stat-tile__note\"\n [class.kf-stat-tile__note--empty]=\"noteIsPlaceholder()\"\n [title]=\"note\"\n >{{ note }}</span\n >\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);height:100%;min-width:0}:host(.kf-stat-tile--cta){background:var(--kf-color-surface);gap:2px}:host(.kf-stat-tile--inline){flex-direction:row-reverse;align-items:baseline;height:auto;gap:6px;padding:0;border:none;border-radius:0;background:transparent}:host(.kf-stat-tile--inline) .kf-stat-tile__head{width:auto}.kf-stat-tile__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-xs)}.kf-stat-tile__label{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__icon{display:inline-flex;color:var(--kf-color-primary)}.kf-stat-tile__value{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__value--projected{display:flex;align-items:center;font-size:initial;font-weight:initial;color:initial}.kf-stat-tile__value--ok,.kf-stat-tile__label--ok{color:var(--kf-color-success-strong)}.kf-stat-tile__value--warn,.kf-stat-tile__label--warn{color:var(--kf-color-warning-strong)}.kf-stat-tile__value--error,.kf-stat-tile__label--error{color:var(--kf-color-error-strong)}.kf-stat-tile__value--info,.kf-stat-tile__label--info{color:var(--kf-color-info-strong)}.kf-stat-tile__value--missing{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);font-style:italic;color:var(--kf-color-text-muted)}.kf-stat-tile__note{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:var(--kf-font-size-caption);color:var(--kf-color-text-muted);margin-top:auto}.kf-stat-tile__note--empty{font-style:italic;opacity:.75}.kf-stat-tile__cta-title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__cta-description{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__cta-action{margin-top:var(--kf-space-xs);font-size:.8125rem;font-weight:600}\n"] }]
5332
- }], propDecorators: { cta: [{ type: i0.Input, args: [{ isSignal: true, alias: "cta", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], notePlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "notePlaceholder", required: false }] }], valueMissing: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueMissing", required: false }] }], hasProjectedValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasProjectedValue", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }] } });
5655
+ }, template: "@if (cta()) {\n <span class=\"kf-stat-tile__cta-title\">{{ title() }}</span>\n @if (description()) {\n <span class=\"kf-stat-tile__cta-description\">{{ description() }}</span>\n }\n @if (actionLabel()) {\n <kf-link\n class=\"kf-stat-tile__cta-action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n} @else {\n <div class=\"kf-stat-tile__head\">\n <!-- El label recibe la clase de tone adem\u00E1s del value: juntos forman\n una sola unidad de lectura, y pintar solo el n\u00FAmero la romper\u00EDa. -->\n <span\n class=\"kf-stat-tile__label\"\n [class.kf-stat-tile__label--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__label--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__label--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__label--info]=\"tone() === 'info'\"\n >{{ label() }}</span\n >\n @if (icon(); as iconName) {\n <span class=\"kf-stat-tile__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n </div>\n @if (hasProjectedValue()) {\n <!-- Slot para un valor que no es texto medido (ej. un `kf-badge` de\n estado) \u2014 mismo bloque `__value`, sin las clases de tono, que en ese\n caso las resuelve el propio contenido proyectado (el badge trae su\n tono). -->\n <span class=\"kf-stat-tile__value kf-stat-tile__value--projected\">\n <ng-content select=\"[valueContent]\" />\n </span>\n } @else {\n <span\n class=\"kf-stat-tile__value\"\n [class.kf-stat-tile__value--ok]=\"tone() === 'ok'\"\n [class.kf-stat-tile__value--warn]=\"tone() === 'warn'\"\n [class.kf-stat-tile__value--error]=\"tone() === 'error'\"\n [class.kf-stat-tile__value--info]=\"tone() === 'info'\"\n [class.kf-stat-tile__value--missing]=\"valueMissing()\"\n >{{ value() }}</span\n >\n }\n\n <!-- La l\u00EDnea se reserva con o sin nota: un tile m\u00E1s corto desalinea la\n fila. El `title` deja disponible el texto completo, recortado a 2 l\u00EDneas. -->\n @if (noteText(); as note) {\n <span\n class=\"kf-stat-tile__note\"\n [class.kf-stat-tile__note--empty]=\"noteIsPlaceholder()\"\n [title]=\"note\"\n >{{ note }}</span\n >\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);height:100%;min-width:0}:host(.kf-stat-tile--cta){background:var(--kf-color-surface);gap:2px}:host(.kf-stat-tile--inline){flex-direction:row-reverse;align-items:baseline;height:auto;gap:6px;padding:0;border:none;border-radius:0;background:transparent}:host(.kf-stat-tile--inline) .kf-stat-tile__head{width:auto}.kf-stat-tile__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-xs)}.kf-stat-tile__label{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__icon{display:inline-flex;color:var(--kf-color-primary)}.kf-stat-tile__value{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__value--projected{display:flex;align-items:center;font-size:initial;font-weight:initial;color:initial}.kf-stat-tile__value--ok,.kf-stat-tile__label--ok{color:var(--kf-color-success-strong)}.kf-stat-tile__value--warn,.kf-stat-tile__label--warn{color:var(--kf-color-warning-strong)}.kf-stat-tile__value--error,.kf-stat-tile__label--error{color:var(--kf-color-error-strong)}.kf-stat-tile__value--info,.kf-stat-tile__label--info{color:var(--kf-color-info-strong)}.kf-stat-tile__value--missing{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);font-style:italic;color:var(--kf-color-text-muted)}.kf-stat-tile__note{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:var(--kf-font-size-caption);color:var(--kf-color-text-muted);margin-top:auto}.kf-stat-tile__note--empty{font-style:italic;opacity:.75}.kf-stat-tile__cta-title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-stat-tile__cta-description{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-stat-tile__cta-action{margin-top:var(--kf-space-xs);font-size:.8125rem;font-weight:600}\n"] }]
5656
+ }], propDecorators: { cta: [{ type: i0.Input, args: [{ isSignal: true, alias: "cta", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], notePlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "notePlaceholder", required: false }] }], valueMissing: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueMissing", required: false }] }], hasProjectedValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasProjectedValue", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
5333
5657
 
5334
5658
  /**
5335
5659
  * Pill con borde, en mayúsculas y con letter-spacing, pensada para indicadores
@@ -5693,6 +6017,114 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5693
6017
  }, template: "<!--\n El grid vive en un div interno y no en el `:host`: el host es el CONTENEDOR\n de consulta (`container-type: inline-size`), y una `@container` dentro de\n su propia hoja medir\u00EDa el contenedor de m\u00E1s arriba, no a \u00E9l mismo.\n-->\n<div class=\"kf-tile-grid\">\n <ng-content />\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:kf-tile-grid;min-width:0}.kf-tile-grid{display:grid;gap:var(--kf-space-sm);align-items:stretch}:host(.kf-tile-grid--1) .kf-tile-grid{grid-template-columns:minmax(0,1fr)}:host(.kf-tile-grid--2) .kf-tile-grid{grid-template-columns:repeat(2,minmax(0,1fr))}:host(.kf-tile-grid--3) .kf-tile-grid{grid-template-columns:repeat(3,minmax(0,1fr))}:host(.kf-tile-grid--4) .kf-tile-grid{grid-template-columns:repeat(4,minmax(0,1fr))}:host(.kf-tile-grid--5) .kf-tile-grid{grid-template-columns:repeat(5,minmax(0,1fr))}:host(.kf-tile-grid--6) .kf-tile-grid{grid-template-columns:repeat(6,minmax(0,1fr))}@container kf-tile-grid (max-width: 620px){:host(.kf-tile-grid--3) .kf-tile-grid,:host(.kf-tile-grid--4) .kf-tile-grid,:host(.kf-tile-grid--5) .kf-tile-grid,:host(.kf-tile-grid--6) .kf-tile-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@container kf-tile-grid (max-width: 380px){:host(.kf-tile-grid--2) .kf-tile-grid,:host(.kf-tile-grid--3) .kf-tile-grid,:host(.kf-tile-grid--4) .kf-tile-grid,:host(.kf-tile-grid--5) .kf-tile-grid,:host(.kf-tile-grid--6) .kf-tile-grid{grid-template-columns:minmax(0,1fr)}}\n"] }]
5694
6018
  }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }] } });
5695
6019
 
6020
+ const SKELETON_COUNT = 8;
6021
+ /**
6022
+ * Grilla de horarios seleccionables con semántica de radiogroup: un solo tab
6023
+ * stop, las flechas mueven y seleccionan (saltando los deshabilitados) y
6024
+ * Home/End van al primer/último horario habilitado.
6025
+ *
6026
+ * Si algún horario declara `group`, se rinde un subgrupo con etiqueta por
6027
+ * cada grupo distinto; si no, una sola grilla plana.
6028
+ *
6029
+ * <kf-time-slot-picker
6030
+ * ariaLabel="Horario"
6031
+ * [slots]="[{ value: '09:00', group: 'Mañana' }, { value: '15:30', group: 'Tarde', disabled: true }]"
6032
+ * [(value)]="time"
6033
+ * [loading]="loadingSlots()"
6034
+ * />
6035
+ */
6036
+ class TimeSlotPicker {
6037
+ static nextId = 0;
6038
+ slots = input.required(/* @ts-ignore */
6039
+ ...(ngDevMode ? [{ debugName: "slots" }] : /* istanbul ignore next */ []));
6040
+ value = model(null, /* @ts-ignore */
6041
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6042
+ ariaLabel = input.required(/* @ts-ignore */
6043
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
6044
+ /** Muestra chips de carga en lugar de los horarios. */
6045
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6046
+ emptyLabel = input('No hay horarios disponibles', /* @ts-ignore */
6047
+ ...(ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
6048
+ host = inject(ElementRef);
6049
+ idPrefix = `kf-time-slot-picker-${TimeSlotPicker.nextId++}`;
6050
+ skeletons = Array.from({ length: SKELETON_COUNT }, (_, index) => index);
6051
+ groups = computed(() => {
6052
+ const slots = this.slots();
6053
+ if (!slots.some((slot) => slot.group)) {
6054
+ return [{ id: `${this.idPrefix}-group-0`, label: null, slots }];
6055
+ }
6056
+ const byGroup = new Map();
6057
+ for (const slot of slots) {
6058
+ const key = slot.group ?? '';
6059
+ byGroup.set(key, [...(byGroup.get(key) ?? []), slot]);
6060
+ }
6061
+ return [...byGroup].map(([label, groupSlots], index) => ({
6062
+ id: `${this.idPrefix}-group-${index}`,
6063
+ label: label || null,
6064
+ slots: groupSlots,
6065
+ }));
6066
+ }, /* @ts-ignore */
6067
+ ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
6068
+ /** Valores habilitados en orden visual — la secuencia de navegación por teclado. */
6069
+ enabledValues = computed(() => this.groups()
6070
+ .flatMap((group) => group.slots)
6071
+ .filter((slot) => !slot.disabled)
6072
+ .map((slot) => slot.value), /* @ts-ignore */
6073
+ ...(ngDevMode ? [{ debugName: "enabledValues" }] : /* istanbul ignore next */ []));
6074
+ /** El único chip con `tabindex="0"` (roving tabindex). */
6075
+ tabStop = computed(() => {
6076
+ const values = this.enabledValues();
6077
+ const value = this.value();
6078
+ return value !== null && values.includes(value) ? value : (values[0] ?? null);
6079
+ }, /* @ts-ignore */
6080
+ ...(ngDevMode ? [{ debugName: "tabStop" }] : /* istanbul ignore next */ []));
6081
+ select(slot) {
6082
+ if (slot.disabled || this.loading() || slot.value === this.value()) {
6083
+ return;
6084
+ }
6085
+ this.value.set(slot.value);
6086
+ }
6087
+ onKeydown(event, current) {
6088
+ const values = this.enabledValues();
6089
+ const index = values.indexOf(current);
6090
+ if (index === -1) {
6091
+ return;
6092
+ }
6093
+ let nextIndex;
6094
+ switch (event.key) {
6095
+ case 'ArrowRight':
6096
+ case 'ArrowDown':
6097
+ nextIndex = (index + 1) % values.length;
6098
+ break;
6099
+ case 'ArrowLeft':
6100
+ case 'ArrowUp':
6101
+ nextIndex = (index - 1 + values.length) % values.length;
6102
+ break;
6103
+ case 'Home':
6104
+ nextIndex = 0;
6105
+ break;
6106
+ case 'End':
6107
+ nextIndex = values.length - 1;
6108
+ break;
6109
+ default:
6110
+ return;
6111
+ }
6112
+ event.preventDefault();
6113
+ const next = values[nextIndex];
6114
+ this.value.set(next);
6115
+ // Se busca por dataset, no por id ni selector: los valores de horario son strings arbitrarios.
6116
+ Array.from(this.host.nativeElement.querySelectorAll('[data-value]'))
6117
+ .find((element) => element.dataset['value'] === next)
6118
+ ?.focus();
6119
+ }
6120
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TimeSlotPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
6121
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: TimeSlotPicker, isStandalone: true, selector: "kf-time-slot-picker", inputs: { slots: { classPropertyName: "slots", publicName: "slots", 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: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: "<div class=\"kf-time-slot-picker\" [attr.aria-busy]=\"loading() || null\">\n @if (loading()) {\n <div class=\"kf-time-slot-picker__grid\" aria-hidden=\"true\">\n @for (item of skeletons; track item) {\n <kf-skeleton variant=\"rect\" height=\"44px\" />\n }\n </div>\n } @else if (slots().length === 0) {\n <kf-text as=\"p\" variant=\"body-muted\">{{ emptyLabel() }}</kf-text>\n } @else {\n <div class=\"kf-time-slot-picker__groups\" role=\"radiogroup\" [attr.aria-label]=\"ariaLabel()\">\n @for (group of groups(); track group.id) {\n <div\n class=\"kf-time-slot-picker__group\"\n [attr.role]=\"group.label ? 'group' : null\"\n [attr.aria-labelledby]=\"group.label ? group.id : null\"\n >\n @if (group.label) {\n <span class=\"kf-time-slot-picker__group-label kf-overline-text\" [id]=\"group.id\">{{\n group.label\n }}</span>\n }\n <div class=\"kf-time-slot-picker__grid\">\n @for (slot of group.slots; track slot.value) {\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-time-slot-picker__slot\"\n [class.kf-time-slot-picker__slot--selected]=\"slot.value === value()\"\n [disabled]=\"slot.disabled\"\n [attr.aria-checked]=\"slot.value === value()\"\n [attr.tabindex]=\"slot.value === tabStop() ? 0 : -1\"\n [attr.data-value]=\"slot.value\"\n (click)=\"select(slot)\"\n (keydown)=\"onKeydown($event, slot.value)\"\n >\n {{ slot.label ?? slot.value }}\n </button>\n }\n </div>\n </div>\n }\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}.kf-time-slot-picker__groups{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-time-slot-picker__group{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-time-slot-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:var(--kf-space-sm)}.kf-time-slot-picker__slot{min-height:44px;padding:0 var(--kf-space-sm);box-sizing:border-box;border:1.5px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font:inherit;font-size:var(--kf-font-size-body-sm);font-weight:var(--kf-font-weight-medium);font-variant-numeric:tabular-nums;color:var(--kf-color-ink);cursor:pointer;transition:background-color .14s,border-color .14s,color .14s}.kf-time-slot-picker__slot:hover:not(:disabled):not(.kf-time-slot-picker__slot--selected){border-color:var(--kf-color-border-strong);background:var(--kf-color-primary-soft)}.kf-time-slot-picker__slot:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px}.kf-time-slot-picker__slot--selected{border-color:var(--kf-color-primary);background:var(--kf-color-primary);color:var(--kf-color-white)}.kf-time-slot-picker__slot:disabled{background:var(--kf-color-surface);color:var(--kf-color-text-muted);font-weight:var(--kf-font-weight-body);text-decoration:line-through;cursor:not-allowed}@media(prefers-reduced-motion:reduce){.kf-time-slot-picker__slot{transition:none}}\n"], dependencies: [{ kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6122
+ }
6123
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TimeSlotPicker, decorators: [{
6124
+ type: Component,
6125
+ args: [{ selector: 'kf-time-slot-picker', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Skeleton, Text], template: "<div class=\"kf-time-slot-picker\" [attr.aria-busy]=\"loading() || null\">\n @if (loading()) {\n <div class=\"kf-time-slot-picker__grid\" aria-hidden=\"true\">\n @for (item of skeletons; track item) {\n <kf-skeleton variant=\"rect\" height=\"44px\" />\n }\n </div>\n } @else if (slots().length === 0) {\n <kf-text as=\"p\" variant=\"body-muted\">{{ emptyLabel() }}</kf-text>\n } @else {\n <div class=\"kf-time-slot-picker__groups\" role=\"radiogroup\" [attr.aria-label]=\"ariaLabel()\">\n @for (group of groups(); track group.id) {\n <div\n class=\"kf-time-slot-picker__group\"\n [attr.role]=\"group.label ? 'group' : null\"\n [attr.aria-labelledby]=\"group.label ? group.id : null\"\n >\n @if (group.label) {\n <span class=\"kf-time-slot-picker__group-label kf-overline-text\" [id]=\"group.id\">{{\n group.label\n }}</span>\n }\n <div class=\"kf-time-slot-picker__grid\">\n @for (slot of group.slots; track slot.value) {\n <button\n type=\"button\"\n role=\"radio\"\n class=\"kf-time-slot-picker__slot\"\n [class.kf-time-slot-picker__slot--selected]=\"slot.value === value()\"\n [disabled]=\"slot.disabled\"\n [attr.aria-checked]=\"slot.value === value()\"\n [attr.tabindex]=\"slot.value === tabStop() ? 0 : -1\"\n [attr.data-value]=\"slot.value\"\n (click)=\"select(slot)\"\n (keydown)=\"onKeydown($event, slot.value)\"\n >\n {{ slot.label ?? slot.value }}\n </button>\n }\n </div>\n </div>\n }\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}.kf-time-slot-picker__groups{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-time-slot-picker__group{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-time-slot-picker__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:var(--kf-space-sm)}.kf-time-slot-picker__slot{min-height:44px;padding:0 var(--kf-space-sm);box-sizing:border-box;border:1.5px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font:inherit;font-size:var(--kf-font-size-body-sm);font-weight:var(--kf-font-weight-medium);font-variant-numeric:tabular-nums;color:var(--kf-color-ink);cursor:pointer;transition:background-color .14s,border-color .14s,color .14s}.kf-time-slot-picker__slot:hover:not(:disabled):not(.kf-time-slot-picker__slot--selected){border-color:var(--kf-color-border-strong);background:var(--kf-color-primary-soft)}.kf-time-slot-picker__slot:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px}.kf-time-slot-picker__slot--selected{border-color:var(--kf-color-primary);background:var(--kf-color-primary);color:var(--kf-color-white)}.kf-time-slot-picker__slot:disabled{background:var(--kf-color-surface);color:var(--kf-color-text-muted);font-weight:var(--kf-font-weight-body);text-decoration:line-through;cursor:not-allowed}@media(prefers-reduced-motion:reduce){.kf-time-slot-picker__slot{transition:none}}\n"] }]
6126
+ }], propDecorators: { slots: [{ type: i0.Input, args: [{ isSignal: true, alias: "slots", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", required: false }] }] } });
6127
+
5696
6128
  /** Fixed icon per tone (page 20 of the design system) — not configurable per call. */
5697
6129
  const TOAST_ICON = {
5698
6130
  success: 'check-circle',
@@ -5902,5 +6334,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5902
6334
  * Generated bundle index. Do not edit.
5903
6335
  */
5904
6336
 
5905
- export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, 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, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal };
6337
+ export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentViewer, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, Link, MetricCard, Modal, MultiSelect, NoteBlock, PageHeader, Pagination, Panel, PortalHeader, Progress, PulseDot, RowActions, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, Spinner, StatCard, StatTile, StatusPill, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal };
5906
6338
  //# sourceMappingURL=kaufmann-lab-ui-core.mjs.map