@kaufmann-lab/ui 0.1.18 → 0.1.20

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
@@ -2351,6 +2649,7 @@ const COMPACT_PANEL_MIN_WIDTH = 72;
2351
2649
  *
2352
2650
  * <kf-select label="Sede" [options]="['Lima', 'Arequipa', 'Trujillo']" placeholder="Selecciona…" />
2353
2651
  * <kf-select [options]="[{ value: 'pe', label: 'Perú' }]" (valueChange)="onCountry($event)" />
2652
+ * <kf-select [options]="[{ value: '1002099', label: 'BBVA', tag: 'Empresa', detail: 'RUC 20100130204' }]" />
2354
2653
  *
2355
2654
  * `variant="compact"` cambia el ancho de `240px` fijo a intrínseco con un
2356
2655
  * techo de `160px` (se ajusta al contenido de la opción seleccionada hasta
@@ -2414,7 +2713,7 @@ class Select {
2414
2713
  ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
2415
2714
  activeIndex = signal(0, /* @ts-ignore */
2416
2715
  ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
2417
- /** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
2716
+ /** Resolves `string | SelectOption` entries into a single shape for the template. */
2418
2717
  normalizedOptions = computed(() => this.options().map((option) => typeof option === 'string' ? { value: option, label: option } : option), /* @ts-ignore */
2419
2718
  ...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
2420
2719
  selectedOption = computed(() => this.normalizedOptions().find((option) => option.value === this.value()), /* @ts-ignore */
@@ -2427,7 +2726,7 @@ class Select {
2427
2726
  if (!query) {
2428
2727
  return this.normalizedOptions();
2429
2728
  }
2430
- return this.normalizedOptions().filter((option) => normalizeForSearch(option.label).includes(query));
2729
+ return this.normalizedOptions().filter((option) => [option.label, option.tag, option.detail].some((text) => !!text && normalizeForSearch(text).includes(query)));
2431
2730
  }, /* @ts-ignore */
2432
2731
  ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
2433
2732
  triggerRef = viewChild.required('trigger', /* @ts-ignore */
@@ -2474,6 +2773,13 @@ class Select {
2474
2773
  }
2475
2774
  this.isOpen.update((open) => !open);
2476
2775
  }
2776
+ /** Nombre accesible explícito solo para opciones con `tag`/`detail`, para separar las partes al leerlas. */
2777
+ optionAriaLabel(option) {
2778
+ if (!option.tag && !option.detail) {
2779
+ return null;
2780
+ }
2781
+ return [option.label, option.tag, option.detail].filter(Boolean).join(', ');
2782
+ }
2477
2783
  select(option) {
2478
2784
  this.isOpen.set(false);
2479
2785
  if (option.value !== this.value()) {
@@ -2505,16 +2811,16 @@ class Select {
2505
2811
  }
2506
2812
  }
2507
2813
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
2508
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Select, isStandalone: true, selector: "kf-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" }, properties: { "class.kf-select--compact": "variant() === 'compact'", "class.kf-select--width-sm": "width() === 'sm'", "class.kf-select--width-lg": "width() === 'lg'", "class.kf-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n />\n }\n <ul class=\"kf-select__list\" [id]=\"listboxId\" role=\"listbox\" [attr.aria-labelledby]=\"triggerId\">\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"option.value === value()\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"select(option)\"\n (keydown.space)=\"$event.preventDefault(); select(option)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n {{ option.label }}\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
2814
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Select, isStandalone: true, selector: "kf-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" }, properties: { "class.kf-select--compact": "variant() === 'compact'", "class.kf-select--width-sm": "width() === 'sm'", "class.kf-select--width-lg": "width() === 'lg'", "class.kf-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n />\n }\n <ul class=\"kf-select__list\" [id]=\"listboxId\" role=\"listbox\" [attr.aria-labelledby]=\"triggerId\">\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"select(option)\"\n (keydown.space)=\"$event.preventDefault(); select(option)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot"] }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
2509
2815
  }
2510
2816
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, decorators: [{
2511
2817
  type: Component,
2512
- args: [{ selector: 'kf-select', imports: [FieldLabel], host: {
2818
+ args: [{ selector: 'kf-select', imports: [Badge, FieldLabel], host: {
2513
2819
  '[class.kf-select--compact]': "variant() === 'compact'",
2514
2820
  '[class.kf-select--width-sm]': "width() === 'sm'",
2515
2821
  '[class.kf-select--width-lg]': "width() === 'lg'",
2516
2822
  '[class.kf-select--width-full]': "width() === 'full'",
2517
- }, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n />\n }\n <ul class=\"kf-select__list\" [id]=\"listboxId\" role=\"listbox\" [attr.aria-labelledby]=\"triggerId\">\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"option.value === value()\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"select(option)\"\n (keydown.space)=\"$event.preventDefault(); select(option)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n {{ option.label }}\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
2823
+ }, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n>\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n />\n }\n <ul class=\"kf-select__list\" [id]=\"listboxId\" role=\"listbox\" [attr.aria-labelledby]=\"triggerId\">\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"select(option)\"\n (keydown.space)=\"$event.preventDefault(); select(option)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
2518
2824
  }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], onDocumentEscape: [{
2519
2825
  type: HostListener,
2520
2826
  args: ['document:keydown.escape']
@@ -3510,6 +3816,10 @@ class Input {
3510
3816
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
3511
3817
  placeholder = input('', /* @ts-ignore */
3512
3818
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
3819
+ /** Teclado virtual sugerido en mobile (ej. `"numeric"` para montos o
3820
+ * kilometraje) — sin valor, el navegador decide según `type`. */
3821
+ inputmode = input(/* @ts-ignore */
3822
+ ...(ngDevMode ? [undefined, { debugName: "inputmode" }] : /* istanbul ignore next */ []));
3513
3823
  /** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
3514
3824
  toggleable = input(false, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3515
3825
  inputId = `kf-input-${Input.nextId++}`;
@@ -3587,13 +3897,13 @@ class Input {
3587
3897
  this.passwordVisible.update((visible) => !visible);
3588
3898
  }
3589
3899
  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: [
3900
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Input, isStandalone: true, selector: "kf-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
3591
3901
  {
3592
3902
  provide: NG_VALUE_ACCESSOR,
3593
3903
  useExisting: forwardRef(() => Input),
3594
3904
  multi: true,
3595
3905
  },
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 });
3906
+ ], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3597
3907
  }
3598
3908
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, decorators: [{
3599
3909
  type: Component,
@@ -3603,8 +3913,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
3603
3913
  useExisting: forwardRef(() => Input),
3604
3914
  multi: true,
3605
3915
  },
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 }] }] } });
3916
+ ], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
3917
+ }], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }] } });
3608
3918
 
3609
3919
  let nextId = 0;
3610
3920
  /**
@@ -3860,7 +4170,7 @@ class Filter {
3860
4170
  this.fieldChange.emit({ key, value });
3861
4171
  }
3862
4172
  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"] }] });
4173
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Filter, isStandalone: true, selector: "kf-filter", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mobileTitle: { classPropertyName: "mobileTitle", publicName: "mobileTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange" }, ngImport: i0, template: "<ng-template #fieldsTpl let-stacked=\"stacked\">\n @for (field of fields(); track field.key) {\n @switch (field.type) {\n @case ('select') {\n <kf-select\n [label]=\"field.label ?? ''\"\n [options]=\"optionsFor(field.key)\"\n [value]=\"singleValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valueChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('multi-select') {\n <kf-multi-select\n [label]=\"field.label ?? ''\"\n [options]=\"multiOptionsFor(field.key)\"\n [values]=\"multiValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valuesChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('search') {\n <kf-input\n [label]=\"field.label ?? ''\"\n [icon]=\"field.icon ?? 'search'\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n [ngModel]=\"singleValueFor(field.key)\"\n (ngModelChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n }\n @if (!stacked && field.showDivider) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n</ng-template>\n\n<div class=\"kf-filter kf-filter--row\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: false }\" />\n</div>\n\n<kf-collapsible class=\"kf-filter__collapsible\" [title]=\"mobileTitle()\">\n <div class=\"kf-filter kf-filter--stack\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: true }\" />\n </div>\n</kf-collapsible>\n", styles: ["@charset \"UTF-8\";:host{display:block}.kf-filter--row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--kf-space-md)}.kf-filter--stack{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-filter__collapsible{display:none}@media(max-width:768px){.kf-filter--row{display:none}.kf-filter__collapsible{display:block}}\n"], dependencies: [{ kind: "component", type: Collapsible, selector: "kf-collapsible", inputs: ["title", "expanded"], outputs: ["expandedChange"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "inputmode", "toggleable"] }, { kind: "component", type: MultiSelect, selector: "kf-multi-select", inputs: ["label", "options", "values", "placeholder", "disabled", "width", "searchable", "searchThreshold"], outputs: ["valuesChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold"], outputs: ["valueChange"] }] });
3864
4174
  }
3865
4175
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, decorators: [{
3866
4176
  type: Component,
@@ -4374,6 +4684,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
4374
4684
  * <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
4375
4685
  * </kf-panel>
4376
4686
  *
4687
+ * Para apps con router: escuchar `actionClick` y llamar
4688
+ * `event.preventDefault()` antes de `router.navigateByUrl(...)`. El
4689
+ * `actionHref` se mantiene igual para que click-medio, "abrir en pestaña
4690
+ * nueva" y el hover de la barra de estado sigan funcionando.
4691
+ *
4692
+ * <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos"
4693
+ * (actionClick)="$event.preventDefault(); router.navigateByUrl('/pagos')">
4694
+ * …
4695
+ * </kf-panel>
4696
+ *
4377
4697
  * El host y el `kf-card` interno son flex column con `flex:1` de punta a
4378
4698
  * punta — si un grid/flex externo estira `kf-panel` a la altura de un
4379
4699
  * hermano más alto, el contenido proyectado hereda esa altura en vez de
@@ -4388,13 +4708,19 @@ class Panel {
4388
4708
  ...(ngDevMode ? [undefined, { debugName: "actionLabel" }] : /* istanbul ignore next */ []));
4389
4709
  actionHref = input(/* @ts-ignore */
4390
4710
  ...(ngDevMode ? [undefined, { debugName: "actionHref" }] : /* istanbul ignore next */ []));
4711
+ /**
4712
+ * Click en el link de acción. Incluye el `MouseEvent` para que un
4713
+ * consumidor con router pueda cortar la navegación nativa — mismo patrón
4714
+ * que `itemClick` de `kf-breadcrumb`, sin depender de `@angular/router`.
4715
+ */
4716
+ actionClick = output();
4391
4717
  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"] }] });
4718
+ 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
4719
  }
4394
4720
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, decorators: [{
4395
4721
  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 }] }] } });
4722
+ 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"] }]
4723
+ }], 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
4724
 
4399
4725
  /**
4400
4726
  * Cabecera completa del portal (página 31) — franja de administrador
@@ -4570,11 +4896,11 @@ class Progress {
4570
4896
  clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100), /* @ts-ignore */
4571
4897
  ...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
4572
4898
  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"] });
4899
+ 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
4900
  }
4575
4901
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, decorators: [{
4576
4902
  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"] }]
4903
+ 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
4904
  }], 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
4905
 
4580
4906
  /**
@@ -5314,6 +5640,12 @@ class StatTile {
5314
5640
  ...(ngDevMode ? [undefined, { debugName: "actionLabel" }] : /* istanbul ignore next */ []));
5315
5641
  actionHref = input(/* @ts-ignore */
5316
5642
  ...(ngDevMode ? [undefined, { debugName: "actionHref" }] : /* istanbul ignore next */ []));
5643
+ /**
5644
+ * Click en el link de la variante CTA. Incluye el `MouseEvent` para que un
5645
+ * consumidor con router pueda cortar la navegación nativa — mismo patrón
5646
+ * que `actionClick` de `kf-panel`, sin depender de `@angular/router`.
5647
+ */
5648
+ actionClick = output();
5317
5649
  /** Texto real de la nota. `null` significa "no reservar la línea": pasa
5318
5650
  * solo cuando el consumidor no declaró ni nota ni placeholder. */
5319
5651
  noteText = computed(() => this.note() || this.notePlaceholder() || null, /* @ts-ignore */
@@ -5321,15 +5653,15 @@ class StatTile {
5321
5653
  noteIsPlaceholder = computed(() => !this.note() && !!this.notePlaceholder(), /* @ts-ignore */
5322
5654
  ...(ngDevMode ? [{ debugName: "noteIsPlaceholder" }] : /* istanbul ignore next */ []));
5323
5655
  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"] }] });
5656
+ 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
5657
  }
5326
5658
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatTile, decorators: [{
5327
5659
  type: Component,
5328
5660
  args: [{ selector: 'kf-stat-tile', imports: [Icon, Link], host: {
5329
5661
  '[class.kf-stat-tile--cta]': 'cta()',
5330
5662
  '[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 }] }] } });
5663
+ }, 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"] }]
5664
+ }], 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
5665
 
5334
5666
  /**
5335
5667
  * Pill con borde, en mayúsculas y con letter-spacing, pensada para indicadores
@@ -5693,6 +6025,114 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5693
6025
  }, 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
6026
  }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }] } });
5695
6027
 
6028
+ const SKELETON_COUNT = 8;
6029
+ /**
6030
+ * Grilla de horarios seleccionables con semántica de radiogroup: un solo tab
6031
+ * stop, las flechas mueven y seleccionan (saltando los deshabilitados) y
6032
+ * Home/End van al primer/último horario habilitado.
6033
+ *
6034
+ * Si algún horario declara `group`, se rinde un subgrupo con etiqueta por
6035
+ * cada grupo distinto; si no, una sola grilla plana.
6036
+ *
6037
+ * <kf-time-slot-picker
6038
+ * ariaLabel="Horario"
6039
+ * [slots]="[{ value: '09:00', group: 'Mañana' }, { value: '15:30', group: 'Tarde', disabled: true }]"
6040
+ * [(value)]="time"
6041
+ * [loading]="loadingSlots()"
6042
+ * />
6043
+ */
6044
+ class TimeSlotPicker {
6045
+ static nextId = 0;
6046
+ slots = input.required(/* @ts-ignore */
6047
+ ...(ngDevMode ? [{ debugName: "slots" }] : /* istanbul ignore next */ []));
6048
+ value = model(null, /* @ts-ignore */
6049
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6050
+ ariaLabel = input.required(/* @ts-ignore */
6051
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
6052
+ /** Muestra chips de carga en lugar de los horarios. */
6053
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6054
+ emptyLabel = input('No hay horarios disponibles', /* @ts-ignore */
6055
+ ...(ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
6056
+ host = inject(ElementRef);
6057
+ idPrefix = `kf-time-slot-picker-${TimeSlotPicker.nextId++}`;
6058
+ skeletons = Array.from({ length: SKELETON_COUNT }, (_, index) => index);
6059
+ groups = computed(() => {
6060
+ const slots = this.slots();
6061
+ if (!slots.some((slot) => slot.group)) {
6062
+ return [{ id: `${this.idPrefix}-group-0`, label: null, slots }];
6063
+ }
6064
+ const byGroup = new Map();
6065
+ for (const slot of slots) {
6066
+ const key = slot.group ?? '';
6067
+ byGroup.set(key, [...(byGroup.get(key) ?? []), slot]);
6068
+ }
6069
+ return [...byGroup].map(([label, groupSlots], index) => ({
6070
+ id: `${this.idPrefix}-group-${index}`,
6071
+ label: label || null,
6072
+ slots: groupSlots,
6073
+ }));
6074
+ }, /* @ts-ignore */
6075
+ ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
6076
+ /** Valores habilitados en orden visual — la secuencia de navegación por teclado. */
6077
+ enabledValues = computed(() => this.groups()
6078
+ .flatMap((group) => group.slots)
6079
+ .filter((slot) => !slot.disabled)
6080
+ .map((slot) => slot.value), /* @ts-ignore */
6081
+ ...(ngDevMode ? [{ debugName: "enabledValues" }] : /* istanbul ignore next */ []));
6082
+ /** El único chip con `tabindex="0"` (roving tabindex). */
6083
+ tabStop = computed(() => {
6084
+ const values = this.enabledValues();
6085
+ const value = this.value();
6086
+ return value !== null && values.includes(value) ? value : (values[0] ?? null);
6087
+ }, /* @ts-ignore */
6088
+ ...(ngDevMode ? [{ debugName: "tabStop" }] : /* istanbul ignore next */ []));
6089
+ select(slot) {
6090
+ if (slot.disabled || this.loading() || slot.value === this.value()) {
6091
+ return;
6092
+ }
6093
+ this.value.set(slot.value);
6094
+ }
6095
+ onKeydown(event, current) {
6096
+ const values = this.enabledValues();
6097
+ const index = values.indexOf(current);
6098
+ if (index === -1) {
6099
+ return;
6100
+ }
6101
+ let nextIndex;
6102
+ switch (event.key) {
6103
+ case 'ArrowRight':
6104
+ case 'ArrowDown':
6105
+ nextIndex = (index + 1) % values.length;
6106
+ break;
6107
+ case 'ArrowLeft':
6108
+ case 'ArrowUp':
6109
+ nextIndex = (index - 1 + values.length) % values.length;
6110
+ break;
6111
+ case 'Home':
6112
+ nextIndex = 0;
6113
+ break;
6114
+ case 'End':
6115
+ nextIndex = values.length - 1;
6116
+ break;
6117
+ default:
6118
+ return;
6119
+ }
6120
+ event.preventDefault();
6121
+ const next = values[nextIndex];
6122
+ this.value.set(next);
6123
+ // Se busca por dataset, no por id ni selector: los valores de horario son strings arbitrarios.
6124
+ Array.from(this.host.nativeElement.querySelectorAll('[data-value]'))
6125
+ .find((element) => element.dataset['value'] === next)
6126
+ ?.focus();
6127
+ }
6128
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TimeSlotPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
6129
+ 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 });
6130
+ }
6131
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TimeSlotPicker, decorators: [{
6132
+ type: Component,
6133
+ 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"] }]
6134
+ }], 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 }] }] } });
6135
+
5696
6136
  /** Fixed icon per tone (page 20 of the design system) — not configurable per call. */
5697
6137
  const TOAST_ICON = {
5698
6138
  success: 'check-circle',
@@ -5902,5 +6342,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
5902
6342
  * Generated bundle index. Do not edit.
5903
6343
  */
5904
6344
 
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 };
6345
+ export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentViewer, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, Link, MetricCard, Modal, MultiSelect, NoteBlock, PageHeader, Pagination, Panel, PortalHeader, Progress, PulseDot, RowActions, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, Spinner, StatCard, StatTile, StatusPill, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal };
5906
6346
  //# sourceMappingURL=kaufmann-lab-ui-core.mjs.map