@kaufmann-lab/ui 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, booleanAttribute, inject, computed, effect, Component, viewChild, model, output, ElementRef, Injector, signal, afterNextRender, ChangeDetectionStrategy, numberAttribute, contentChildren, DestroyRef, forwardRef, TemplateRef, contentChild, ViewContainerRef,
|
|
2
|
+
import { input, booleanAttribute, inject, computed, effect, Component, viewChild, model, output, ElementRef, Injector, signal, afterNextRender, ChangeDetectionStrategy, numberAttribute, contentChildren, DestroyRef, forwardRef, TemplateRef, contentChild, ViewContainerRef, afterRenderEffect, untracked, Directive, HostListener, isDevMode, viewChildren, Input as Input$1, Injectable } from '@angular/core';
|
|
3
3
|
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
import * as i1 from '@angular/forms';
|
|
5
5
|
import { NG_VALUE_ACCESSOR, FormsModule, NgControl } from '@angular/forms';
|
|
6
6
|
import { Overlay, OverlayContainer } from '@angular/cdk/overlay';
|
|
7
7
|
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
|
|
8
8
|
import { Subscription } from 'rxjs';
|
|
9
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
10
9
|
import { normalizeForSearch, matchMediaSignal, handleArrowKeyNavigation } from '@kaufmann-lab/ui/shared';
|
|
10
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
11
11
|
import { FocusTrapFactory } from '@angular/cdk/a11y';
|
|
12
12
|
|
|
13
13
|
/** Símbolo de trazo "activity" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
|
|
@@ -1236,10 +1236,23 @@ const TONE_ICON = {
|
|
|
1236
1236
|
*
|
|
1237
1237
|
* <kf-alert tone="warn" title="SOAT por vencer">Vence el 15/08.</kf-alert>
|
|
1238
1238
|
* <kf-alert tone="ok" [(open)]="saved" dismissible>Pago registrado.</kf-alert>
|
|
1239
|
+
*
|
|
1240
|
+
* `variant="bar"` es una franja compacta de ancho completo, sin tarjeta: sin
|
|
1241
|
+
* esquinas ni bordes laterales (solo la línea superior e inferior del tono),
|
|
1242
|
+
* ícono, título y mensaje en una sola línea que envuelve si no entra. Mismos
|
|
1243
|
+
* tonos y mismo `role` (`alert` para `error`/`warn`, `status` para el resto)
|
|
1244
|
+
* que la variante `card`. Pensada para el borde de una página o de un panel
|
|
1245
|
+
* (ej. "Estás viendo datos de otro cliente"), donde una tarjeta con margen
|
|
1246
|
+
* sobra.
|
|
1247
|
+
*
|
|
1248
|
+
* <kf-alert variant="bar" tone="warn" title="Modo suplantación">Viendo como Ana Pérez.</kf-alert>
|
|
1239
1249
|
*/
|
|
1240
1250
|
class Alert {
|
|
1241
1251
|
tone = input('info', /* @ts-ignore */
|
|
1242
1252
|
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
1253
|
+
/** `card` (default) o `bar` (franja compacta de ancho completo). */
|
|
1254
|
+
variant = input('card', /* @ts-ignore */
|
|
1255
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
1243
1256
|
/** Línea en negrita arriba del mensaje. Vacío deja solo el contenido proyectado. */
|
|
1244
1257
|
title = input('', /* @ts-ignore */
|
|
1245
1258
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
@@ -1256,12 +1269,12 @@ class Alert {
|
|
|
1256
1269
|
toneIcon = computed(() => TONE_ICON[this.tone()], /* @ts-ignore */
|
|
1257
1270
|
...(ngDevMode ? [{ debugName: "toneIcon" }] : /* istanbul ignore next */ []));
|
|
1258
1271
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Alert, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1259
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Alert, isStandalone: true, selector: "kf-alert", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, ngImport: i0, template: "@if (open()) {\n <div
|
|
1272
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Alert, isStandalone: true, selector: "kf-alert", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, ngImport: i0, template: "@if (open()) {\n <div\n class=\"kf-alert\"\n [class]=\"'kf-alert--' + tone()\"\n [class.kf-alert--bar]=\"variant() === 'bar'\"\n [attr.role]=\"ariaRole()\"\n >\n <kf-icon class=\"kf-alert__icon\" [name]=\"toneIcon()\" size=\"sm\" />\n <div class=\"kf-alert__body\">\n @if (title()) {\n <p class=\"kf-alert__title\">{{ title() }}</p>\n }\n <div class=\"kf-alert__message\"><ng-content /></div>\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-alert__close\"\n aria-label=\"Cerrar aviso\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-alert{display:flex;align-items:flex-start;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);font-size:.9rem}.kf-alert--info{background:var(--kf-color-info-soft);border-color:var(--kf-color-info);color:var(--kf-color-info-strong)}.kf-alert--ok{background:var(--kf-color-success-soft);border-color:var(--kf-color-success);color:var(--kf-color-success-strong)}.kf-alert--warn{background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning);color:var(--kf-color-warning-strong)}.kf-alert--error{background:var(--kf-color-error-soft);border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-alert__body{flex:1;min-width:0}.kf-alert__title{margin:0;font-family:var(--kf-font-title);font-weight:700}.kf-alert__message{color:inherit}.kf-alert__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:inherit;border-radius:var(--kf-radius-xs);cursor:pointer;opacity:.7}.kf-alert__close:hover{opacity:1}.kf-alert__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-alert--bar{align-items:center;padding:var(--kf-space-xs) var(--kf-space-md);border-width:1px 0;border-radius:0;font-size:.875rem}.kf-alert--bar .kf-alert__body{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--kf-space-xs)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
|
|
1260
1273
|
}
|
|
1261
1274
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Alert, decorators: [{
|
|
1262
1275
|
type: Component,
|
|
1263
|
-
args: [{ selector: 'kf-alert', imports: [Icon], template: "@if (open()) {\n <div
|
|
1264
|
-
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }] } });
|
|
1276
|
+
args: [{ selector: 'kf-alert', imports: [Icon], template: "@if (open()) {\n <div\n class=\"kf-alert\"\n [class]=\"'kf-alert--' + tone()\"\n [class.kf-alert--bar]=\"variant() === 'bar'\"\n [attr.role]=\"ariaRole()\"\n >\n <kf-icon class=\"kf-alert__icon\" [name]=\"toneIcon()\" size=\"sm\" />\n <div class=\"kf-alert__body\">\n @if (title()) {\n <p class=\"kf-alert__title\">{{ title() }}</p>\n }\n <div class=\"kf-alert__message\"><ng-content /></div>\n </div>\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-alert__close\"\n aria-label=\"Cerrar aviso\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n </div>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-alert{display:flex;align-items:flex-start;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);font-size:.9rem}.kf-alert--info{background:var(--kf-color-info-soft);border-color:var(--kf-color-info);color:var(--kf-color-info-strong)}.kf-alert--ok{background:var(--kf-color-success-soft);border-color:var(--kf-color-success);color:var(--kf-color-success-strong)}.kf-alert--warn{background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning);color:var(--kf-color-warning-strong)}.kf-alert--error{background:var(--kf-color-error-soft);border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-alert__body{flex:1;min-width:0}.kf-alert__title{margin:0;font-family:var(--kf-font-title);font-weight:700}.kf-alert__message{color:inherit}.kf-alert__close{flex:none;display:inline-flex;padding:2px;border:none;background:transparent;color:inherit;border-radius:var(--kf-radius-xs);cursor:pointer;opacity:.7}.kf-alert__close:hover{opacity:1}.kf-alert__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-alert--bar{align-items:center;padding:var(--kf-space-xs) var(--kf-space-md);border-width:1px 0;border-radius:0;font-size:.875rem}.kf-alert--bar .kf-alert__body{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--kf-space-xs)}\n"] }]
|
|
1277
|
+
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }] } });
|
|
1265
1278
|
|
|
1266
1279
|
/**
|
|
1267
1280
|
* Fila clickeable de una lista de pendientes/alertas: ícono circular con
|
|
@@ -1783,6 +1796,16 @@ function keyboardTarget(key, iso) {
|
|
|
1783
1796
|
* <kf-button>Guardar</kf-button>
|
|
1784
1797
|
* </div>
|
|
1785
1798
|
* </kf-card>
|
|
1799
|
+
*
|
|
1800
|
+
* `loading` (default `false`): cubre el cuerpo con un bloque de carga animado
|
|
1801
|
+
* sin sacar el contenido del layout (queda invisible y fuera del foco y de
|
|
1802
|
+
* lectores de pantalla), así la card conserva exactamente sus dimensiones. Los
|
|
1803
|
+
* slots `[kf-card-media]` y `[kf-card-actions]` también se ocultan. Marca el
|
|
1804
|
+
* host con `aria-busy="true"`. Para una forma más fiel (líneas separadas) usa
|
|
1805
|
+
* `kf-skeleton` dentro de la card, o la card propia del dominio con su
|
|
1806
|
+
* `loading` (`kf-stat-card`, `kf-metric-card`).
|
|
1807
|
+
*
|
|
1808
|
+
* <kf-card [loading]="isLoading()"><h3>{{ title() }}</h3><p>{{ text() }}</p></kf-card>
|
|
1786
1809
|
*/
|
|
1787
1810
|
class Card {
|
|
1788
1811
|
/** Quita la sombra — para cards anidadas dentro de otra superficie ya elevada. */
|
|
@@ -1790,20 +1813,24 @@ class Card {
|
|
|
1790
1813
|
/** Espacio entre los hijos directos del cuerpo. `md` es el default. */
|
|
1791
1814
|
gap = input('md', /* @ts-ignore */
|
|
1792
1815
|
...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
|
|
1816
|
+
/** Cubre el cuerpo con un bloque de carga conservando las dimensiones. */
|
|
1817
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1793
1818
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Card, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1794
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: Card, isStandalone: true, selector: "kf-card", inputs: { flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-card--flush": "flush()", "class.kf-card--gap-xs": "gap() === 'xs'", "class.kf-card--gap-sm": "gap() === 'sm'", "class.kf-card--gap-md": "gap() === 'md'", "class.kf-card--gap-lg": "gap() === 'lg'", "class.kf-card--gap-xl": "gap() === 'xl'" } }, ngImport: i0, template: "<ng-content select=\"[kf-card-media]\" />\n<div
|
|
1819
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: Card, isStandalone: true, selector: "kf-card", inputs: { flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-card--flush": "flush()", "class.kf-card--loading": "loading()", "attr.aria-busy": "loading() ? 'true' : null", "class.kf-card--gap-xs": "gap() === 'xs'", "class.kf-card--gap-sm": "gap() === 'sm'", "class.kf-card--gap-md": "gap() === 'md'", "class.kf-card--gap-lg": "gap() === 'lg'", "class.kf-card--gap-xl": "gap() === 'xl'" } }, ngImport: i0, template: "<ng-content select=\"[kf-card-media]\" />\n<div\n class=\"kf-card__body\"\n [class.kf-card__body--loading]=\"loading()\"\n [class.kf-bone-overlay]=\"loading()\"\n>\n <ng-content />\n</div>\n<ng-content select=\"[kf-card-actions]\" />\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;flex-direction:column;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-xs);overflow:hidden}:host.kf-card--flush{box-shadow:none}.kf-card__body{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-md);flex:1 1 auto}:host.kf-card--gap-xs .kf-card__body{gap:var(--kf-space-xs)}:host.kf-card--gap-sm .kf-card__body{gap:var(--kf-space-sm)}:host.kf-card--gap-md .kf-card__body{gap:var(--kf-space-md)}:host.kf-card--gap-lg .kf-card__body{gap:var(--kf-space-lg)}:host.kf-card--gap-xl .kf-card__body{gap:var(--kf-space-xl)}::ng-deep [kf-card-media]{display:block;inline-size:100%;block-size:auto;object-fit:cover}::ng-deep [kf-card-actions]{display:flex;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-card__body--loading{position:relative;visibility:hidden}.kf-card__body--loading:after{content:\"\";visibility:visible;position:absolute;inset:var(--kf-space-md);border-radius:var(--kf-radius-sm)}:host.kf-card--loading ::ng-deep [kf-card-media],:host.kf-card--loading ::ng-deep [kf-card-actions]{visibility:hidden}\n"] });
|
|
1795
1820
|
}
|
|
1796
1821
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Card, decorators: [{
|
|
1797
1822
|
type: Component,
|
|
1798
1823
|
args: [{ selector: 'kf-card', host: {
|
|
1799
1824
|
'[class.kf-card--flush]': 'flush()',
|
|
1825
|
+
'[class.kf-card--loading]': 'loading()',
|
|
1826
|
+
'[attr.aria-busy]': "loading() ? 'true' : null",
|
|
1800
1827
|
'[class.kf-card--gap-xs]': "gap() === 'xs'",
|
|
1801
1828
|
'[class.kf-card--gap-sm]': "gap() === 'sm'",
|
|
1802
1829
|
'[class.kf-card--gap-md]': "gap() === 'md'",
|
|
1803
1830
|
'[class.kf-card--gap-lg]': "gap() === 'lg'",
|
|
1804
1831
|
'[class.kf-card--gap-xl]': "gap() === 'xl'",
|
|
1805
|
-
}, template: "<ng-content select=\"[kf-card-media]\" />\n<div
|
|
1806
|
-
}], propDecorators: { flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }] } });
|
|
1832
|
+
}, template: "<ng-content select=\"[kf-card-media]\" />\n<div\n class=\"kf-card__body\"\n [class.kf-card__body--loading]=\"loading()\"\n [class.kf-bone-overlay]=\"loading()\"\n>\n <ng-content />\n</div>\n<ng-content select=\"[kf-card-actions]\" />\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;flex-direction:column;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-xs);overflow:hidden}:host.kf-card--flush{box-shadow:none}.kf-card__body{display:flex;flex-direction:column;gap:var(--kf-space-md);padding:var(--kf-space-md);flex:1 1 auto}:host.kf-card--gap-xs .kf-card__body{gap:var(--kf-space-xs)}:host.kf-card--gap-sm .kf-card__body{gap:var(--kf-space-sm)}:host.kf-card--gap-md .kf-card__body{gap:var(--kf-space-md)}:host.kf-card--gap-lg .kf-card__body{gap:var(--kf-space-lg)}:host.kf-card--gap-xl .kf-card__body{gap:var(--kf-space-xl)}::ng-deep [kf-card-media]{display:block;inline-size:100%;block-size:auto;object-fit:cover}::ng-deep [kf-card-actions]{display:flex;align-items:center;justify-content:flex-end;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-card__body--loading{position:relative;visibility:hidden}.kf-card__body--loading:after{content:\"\";visibility:visible;position:absolute;inset:var(--kf-space-md);border-radius:var(--kf-radius-sm)}:host.kf-card--loading ::ng-deep [kf-card-media],:host.kf-card--loading ::ng-deep [kf-card-actions]{visibility:hidden}\n"] }]
|
|
1833
|
+
}], propDecorators: { flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
|
|
1807
1834
|
|
|
1808
1835
|
/**
|
|
1809
1836
|
* Slide individual de un `kf-carousel`. Es un contenedor mínimo: solo existe
|
|
@@ -2029,7 +2056,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2029
2056
|
}, template: "@if (dot()) {\n <span\n class=\"kf-chip__dot\"\n [class.kf-chip__dot--ok]=\"tone() === 'ok'\"\n [class.kf-chip__dot--warn]=\"tone() === 'warn'\"\n [class.kf-chip__dot--error]=\"tone() === 'error'\"\n [class.kf-chip__dot--neutral]=\"tone() === 'neutral'\"\n ></span>\n}\n<span class=\"kf-chip__label\"><ng-content /></span>\n", styles: [":host{display:inline-flex;align-items:center;gap:var(--kf-space-xs);padding:var(--kf-space-xs) var(--kf-space-sm);border-radius:var(--kf-radius-pill);border:1px solid transparent;font:inherit;font-size:.875em;line-height:1;color:var(--kf-color-text);background:var(--kf-color-surface);border-color:var(--kf-color-border);white-space:nowrap}:host(.kf-chip--ok){background:var(--kf-color-success-soft);border-color:var(--kf-color-success-soft);color:var(--kf-color-text)}:host(.kf-chip--warn){background:var(--kf-color-warning-soft);border-color:var(--kf-color-warning-soft);color:var(--kf-color-text)}:host(.kf-chip--error){background:var(--kf-color-error-soft);border-color:var(--kf-color-error-soft);color:var(--kf-color-text)}:host(.kf-chip--neutral){background:var(--kf-color-surface);border-color:var(--kf-color-border);color:var(--kf-color-text)}.kf-chip__dot{inline-size:.5em;block-size:.5em;border-radius:var(--kf-radius-pill);flex:none;background:var(--kf-color-text-muted)}.kf-chip__label{display:inline-block}.kf-chip__dot--ok{background:var(--kf-color-success)}.kf-chip__dot--warn{background:var(--kf-color-warning)}.kf-chip__dot--error{background:var(--kf-color-error)}.kf-chip__dot--neutral{background:var(--kf-color-text-muted)}\n"] }]
|
|
2030
2057
|
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], dot: [{ type: i0.Input, args: [{ isSignal: true, alias: "dot", required: false }] }] } });
|
|
2031
2058
|
|
|
2032
|
-
let nextId$
|
|
2059
|
+
let nextId$5 = 0;
|
|
2033
2060
|
/**
|
|
2034
2061
|
* Bloque colapsable genérico: un header clickeable (título + chevron) que
|
|
2035
2062
|
* expande/colapsa contenido proyectado libre, sin saber nada de lo que hay
|
|
@@ -2056,7 +2083,7 @@ class Collapsible {
|
|
|
2056
2083
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
2057
2084
|
expanded = model(false, /* @ts-ignore */
|
|
2058
2085
|
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
2059
|
-
instanceId = `kf-collapsible-${nextId$
|
|
2086
|
+
instanceId = `kf-collapsible-${nextId$5++}`;
|
|
2060
2087
|
triggerId = `${this.instanceId}-trigger`;
|
|
2061
2088
|
panelId = `${this.instanceId}-panel`;
|
|
2062
2089
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Collapsible, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -2067,6 +2094,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2067
2094
|
args: [{ selector: 'kf-collapsible', template: "<button\n type=\"button\"\n class=\"kf-collapsible__trigger\"\n [id]=\"triggerId\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"panelId\"\n (click)=\"expanded.update((value) => !value)\"\n>\n <span class=\"kf-collapsible__title\">{{ title() }}</span>\n <svg\n class=\"kf-collapsible__chevron\"\n [class.kf-collapsible__chevron--expanded]=\"expanded()\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 6L8 10L12 6\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n</button>\n<div\n class=\"kf-collapsible__panel\"\n [class.kf-collapsible__panel--expanded]=\"expanded()\"\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"triggerId\"\n>\n <div class=\"kf-collapsible__body\">\n <ng-content />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface)}.kf-collapsible__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-weight:600;color:var(--kf-color-text);background:none;border:none;padding:var(--kf-space-sm) var(--kf-space-md);cursor:pointer;text-align:left}.kf-collapsible__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-collapsible__title{flex:1}.kf-collapsible__chevron{flex:none;color:var(--kf-color-text-muted);transition:transform .18s ease}.kf-collapsible__chevron--expanded{transform:rotate(180deg)}.kf-collapsible__panel{overflow:hidden;max-height:0;opacity:0;transition:max-height .24s ease,opacity .18s ease}.kf-collapsible__panel--expanded{max-height:2000px;opacity:1}.kf-collapsible__body{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md)}\n"] }]
|
|
2068
2095
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }] } });
|
|
2069
2096
|
|
|
2097
|
+
/**
|
|
2098
|
+
* Escucha `Escape` en fase de captura sobre `document`, antes que cualquier
|
|
2099
|
+
* listener de burbuja de un contenedor (`kf-drawer`, `kf-modal`).
|
|
2100
|
+
*
|
|
2101
|
+
* Un panel flotante (`kf-select`, `kf-multi-select`, `kf-dropdown`) debe
|
|
2102
|
+
* cerrarse primero y dejar su contenedor abierto. Un listener de burbuja no
|
|
2103
|
+
* basta: el orden entre dos `@HostListener('document:keydown.escape')` es el
|
|
2104
|
+
* de creación de los componentes — el contenedor se crea antes que su
|
|
2105
|
+
* contenido — así que el contenedor vería la tecla sin consumir y también se
|
|
2106
|
+
* cerraría. En captura el panel va siempre primero y marca la tecla con
|
|
2107
|
+
* `preventDefault()`, que es lo que el contenedor revisa.
|
|
2108
|
+
*
|
|
2109
|
+
* `onEscape` debe devolver `true` solo si había algo que cerrar: únicamente
|
|
2110
|
+
* entonces se consume la tecla. Devuelve la función que retira el listener.
|
|
2111
|
+
*/
|
|
2112
|
+
function listenEscapeCapture(doc, onEscape) {
|
|
2113
|
+
const handler = (event) => {
|
|
2114
|
+
if (event.key !== 'Escape' || event.defaultPrevented) {
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (onEscape()) {
|
|
2118
|
+
event.preventDefault();
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
doc.addEventListener('keydown', handler, true);
|
|
2122
|
+
return () => doc.removeEventListener('keydown', handler, true);
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2070
2125
|
/**
|
|
2071
2126
|
* Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
|
|
2072
2127
|
* (`<ng-template>`, capturada vía `TemplateRef`) — `Dropdown` la conecta al
|
|
@@ -2148,7 +2203,9 @@ class Dropdown {
|
|
|
2148
2203
|
this.detach();
|
|
2149
2204
|
}
|
|
2150
2205
|
});
|
|
2206
|
+
const stopEscape = listenEscapeCapture(inject(DOCUMENT), () => this.onDocumentEscape());
|
|
2151
2207
|
this.destroyRef.onDestroy(() => {
|
|
2208
|
+
stopEscape();
|
|
2152
2209
|
this.overlaySubscription?.unsubscribe();
|
|
2153
2210
|
this.overlayRef?.dispose();
|
|
2154
2211
|
});
|
|
@@ -2157,10 +2214,17 @@ class Dropdown {
|
|
|
2157
2214
|
* Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo
|
|
2158
2215
|
* ve teclas con el foco DENTRO del panel. Si el foco queda en el botón
|
|
2159
2216
|
* disparador (caso común), `keydownEvents()` nunca dispara — este
|
|
2160
|
-
* listener a nivel de documento cubre ese caso.
|
|
2217
|
+
* listener a nivel de documento cubre ese caso. Va en fase de captura
|
|
2218
|
+
* (ver `listenEscapeCapture`) y consume la tecla solo si había un menú
|
|
2219
|
+
* abierto, así un `kf-drawer`/`kf-modal` contenedor no se cierra con el
|
|
2220
|
+
* mismo Escape. Devuelve si cerró algo.
|
|
2161
2221
|
*/
|
|
2162
2222
|
onDocumentEscape() {
|
|
2223
|
+
if (!this.open()) {
|
|
2224
|
+
return false;
|
|
2225
|
+
}
|
|
2163
2226
|
this.open.set(false);
|
|
2227
|
+
return true;
|
|
2164
2228
|
}
|
|
2165
2229
|
attach() {
|
|
2166
2230
|
const panel = this.panel();
|
|
@@ -2231,15 +2295,12 @@ class Dropdown {
|
|
|
2231
2295
|
return this.overlayRef;
|
|
2232
2296
|
}
|
|
2233
2297
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2234
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Dropdown, isStandalone: true, selector: "kf-dropdown", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" },
|
|
2298
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Dropdown, isStandalone: true, selector: "kf-dropdown", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, anchor: { classPropertyName: "anchor", publicName: "anchor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, queries: [{ propertyName: "panel", first: true, predicate: DropdownPanel, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:inline-block}\n"] });
|
|
2235
2299
|
}
|
|
2236
2300
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dropdown, decorators: [{
|
|
2237
2301
|
type: Component,
|
|
2238
2302
|
args: [{ selector: 'kf-dropdown', template: `<ng-content />`, styles: [":host{display:inline-block}\n"] }]
|
|
2239
|
-
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], panel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DropdownPanel), { isSignal: true }] }]
|
|
2240
|
-
type: HostListener,
|
|
2241
|
-
args: ['document:keydown.escape']
|
|
2242
|
-
}] } });
|
|
2303
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], anchor: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchor", required: false }] }], panel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => DropdownPanel), { isSignal: true }] }] } });
|
|
2243
2304
|
|
|
2244
2305
|
/**
|
|
2245
2306
|
* Popover de "Datos de contacto" de una persona (asesor, ejecutivo — nombre
|
|
@@ -2442,177 +2503,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2442
2503
|
args: [{ selector: 'kf-skeleton', template: "<span\n class=\"kf-skeleton\"\n [class]=\"'kf-skeleton--' + variant()\"\n aria-hidden=\"true\"\n [style.width]=\"width()\"\n [style.height]=\"height() || null\"\n></span>\n", styles: [":host{display:block}.kf-skeleton{display:block;background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-skeleton-shimmer 1.4s ease infinite}.kf-skeleton--text{height:14px;border-radius:var(--kf-radius-xs)}.kf-skeleton--rect{height:80px;border-radius:var(--kf-radius-sm)}.kf-skeleton--circle{aspect-ratio:1 / 1;border-radius:var(--kf-radius-pill)}@keyframes kf-skeleton-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-skeleton{animation:none;background:var(--kf-color-border)}}\n"] }]
|
|
2443
2504
|
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
|
|
2444
2505
|
|
|
2445
|
-
/**
|
|
2446
|
-
* Wrapper de los cuatro estados que toda sección tiene: cargando, con datos,
|
|
2447
|
-
* vacía y con error. Un componente en vez de tres `@if` sueltos, porque es
|
|
2448
|
-
* siempre el mismo slot el que va rotando entre los cuatro.
|
|
2449
|
-
*
|
|
2450
|
-
* Con `state="ready"` proyecta el contenido real y desaparece; en los otros
|
|
2451
|
-
* tres dibuja el placeholder correspondiente y el contenido no se instancia.
|
|
2452
|
-
* No inventa visuales propios: `loading` usa `kf-skeleton` (o `kf-spinner` con
|
|
2453
|
-
* `load="spinner"`), y `empty`/`error` usan `kf-empty-state`.
|
|
2454
|
-
*
|
|
2455
|
-
* Las acciones (reintentar, crear) van en el slot `actions` — se ignoran
|
|
2456
|
-
* mientras carga, porque no hay nada que reintentar todavía.
|
|
2457
|
-
*
|
|
2458
|
-
* <kf-content-state [state]="state()" title="No pudimos cargar tus vehículos"
|
|
2459
|
-
* subtitle="Revisá tu conexión e intentá de nuevo.">
|
|
2460
|
-
* <kf-button actions (click)="retry()">Reintentar</kf-button>
|
|
2461
|
-
* <kf-data-table [columns]="cols" [rows]="rows()" />
|
|
2462
|
-
* </kf-content-state>
|
|
2463
|
-
*/
|
|
2464
|
-
class ContentState {
|
|
2465
|
-
state = input.required(/* @ts-ignore */
|
|
2466
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
2467
|
-
/** Forma del estado de carga: silueta (default) o spinner. */
|
|
2468
|
-
load = input('skeleton', /* @ts-ignore */
|
|
2469
|
-
...(ngDevMode ? [{ debugName: "load" }] : /* istanbul ignore next */ []));
|
|
2470
|
-
/** Título de `empty`/`error`. Ignorado mientras carga. */
|
|
2471
|
-
title = input('', /* @ts-ignore */
|
|
2472
|
-
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
2473
|
-
subtitle = input('', /* @ts-ignore */
|
|
2474
|
-
...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
|
|
2475
|
-
/** Texto al lado del spinner cuando `load="spinner"`. */
|
|
2476
|
-
loadingLabel = input('Cargando…', /* @ts-ignore */
|
|
2477
|
-
...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
|
|
2478
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2479
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentState, isStandalone: true, selector: "kf-content-state", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, load: { classPropertyName: "load", publicName: "load", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: EmptyState, selector: "kf-empty-state", inputs: ["icon", "tone", "variant", "title"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
|
|
2480
|
-
}
|
|
2481
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, decorators: [{
|
|
2482
|
-
type: Component,
|
|
2483
|
-
args: [{ selector: 'kf-content-state', imports: [EmptyState, Skeleton, Spinner], template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
|
|
2484
|
-
}], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], load: [{ type: i0.Input, args: [{ isSignal: true, alias: "load", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }] } });
|
|
2485
|
-
|
|
2486
|
-
const RAW_BASE64_MIME = {
|
|
2487
|
-
image: 'image/jpeg',
|
|
2488
|
-
video: 'video/mp4',
|
|
2489
|
-
pdf: 'application/pdf',
|
|
2490
|
-
};
|
|
2491
|
-
/** Solo contenido ya en memoria: un `<iframe>` con una URL remota arbitraria podría cargar cualquier página. */
|
|
2492
|
-
const LOCAL_PDF_SRC = /^(blob:|data:application\/pdf[;,])/i;
|
|
2493
|
-
let contentViewerId = 0;
|
|
2494
|
-
/**
|
|
2495
|
-
* Visor a pantalla completa para ampliar una pieza de contenido: imagen,
|
|
2496
|
-
* video o PDF (`type="pdf"`, renderizado por el visor nativo del navegador).
|
|
2497
|
-
* Mismo mecanismo que `kf-modal` (el `<dialog>` nativo resuelve backdrop,
|
|
2498
|
-
* atrapado de foco, top-layer y `Escape`), pero sin panel/título/footer:
|
|
2499
|
-
* el contenido ocupa toda la pantalla sobre un backdrop oscuro.
|
|
2500
|
-
*
|
|
2501
|
-
* `src` acepta lo que venga del backend sin que el consumidor tenga que
|
|
2502
|
-
* normalizarlo primero: una URL (`http(s)://`), un data URI ya armado
|
|
2503
|
-
* (`data:...`), o base64 crudo (se le antepone el prefijo `data:` según
|
|
2504
|
-
* `type`, asumiendo JPEG para imagen, MP4 para video y PDF para pdf). Un PDF
|
|
2505
|
-
* solo acepta contenido local (`blob:`, data URI o base64), nunca una URL remota.
|
|
2506
|
-
*
|
|
2507
|
-
* Lo proyectado con el atributo `actions` (p. ej. un botón de descarga) va en
|
|
2508
|
-
* la barra superior, junto al título.
|
|
2509
|
-
*
|
|
2510
|
-
* <kf-content-viewer [(open)]="viewerOpen" [src]="viewerSrc()" alt="Foto del vehículo" />
|
|
2511
|
-
* <kf-content-viewer [(open)]="viewerOpen" type="video" [src]="viewerSrc()" title="Inspección" />
|
|
2512
|
-
* <kf-content-viewer [(open)]="viewerOpen" type="pdf" [src]="blobUrl()" title="Contrato">
|
|
2513
|
-
* <kf-button actions variant="surface" icon="download" (click)="save()">Descargar</kf-button>
|
|
2514
|
-
* </kf-content-viewer>
|
|
2515
|
-
*/
|
|
2516
|
-
class ContentViewer {
|
|
2517
|
-
type = input('image', /* @ts-ignore */
|
|
2518
|
-
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
2519
|
-
/** URL, data URI, o base64 crudo — ver JSDoc de la clase. */
|
|
2520
|
-
src = input(null, /* @ts-ignore */
|
|
2521
|
-
...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
|
|
2522
|
-
/** Texto alternativo cuando `type="image"`. */
|
|
2523
|
-
alt = input('', /* @ts-ignore */
|
|
2524
|
-
...(ngDevMode ? [{ debugName: "alt" }] : /* istanbul ignore next */ []));
|
|
2525
|
-
title = input('', /* @ts-ignore */
|
|
2526
|
-
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
2527
|
-
/** Visibilidad, two-way: `[(open)]="viewerOpen"`. */
|
|
2528
|
-
open = model(false, /* @ts-ignore */
|
|
2529
|
-
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
2530
|
-
/** Permite cerrar con el botón, `Escape` o clic en el backdrop. */
|
|
2531
|
-
dismissible = input(true, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2532
|
-
titleId = `kf-content-viewer-title-${contentViewerId++}`;
|
|
2533
|
-
/** Detecta si `src` ya es una URL o un data URI armado; si no, asume
|
|
2534
|
-
* base64 crudo y le antepone el prefijo `data:` correspondiente a `type`
|
|
2535
|
-
* — el backend a veces manda una cosa, a veces la otra. */
|
|
2536
|
-
resolvedSrc = computed(() => {
|
|
2537
|
-
const raw = this.src();
|
|
2538
|
-
if (!raw)
|
|
2539
|
-
return null;
|
|
2540
|
-
if (/^(https?:|data:|blob:)/i.test(raw))
|
|
2541
|
-
return raw;
|
|
2542
|
-
return `data:${RAW_BASE64_MIME[this.type()]};base64,${raw}`;
|
|
2543
|
-
}, /* @ts-ignore */
|
|
2544
|
-
...(ngDevMode ? [{ debugName: "resolvedSrc" }] : /* istanbul ignore next */ []));
|
|
2545
|
-
pdfSrc = computed(() => {
|
|
2546
|
-
const src = this.resolvedSrc();
|
|
2547
|
-
if (this.type() !== 'pdf' || !src || !LOCAL_PDF_SRC.test(src))
|
|
2548
|
-
return null;
|
|
2549
|
-
return this.sanitizer.bypassSecurityTrustResourceUrl(src);
|
|
2550
|
-
}, /* @ts-ignore */
|
|
2551
|
-
...(ngDevMode ? [{ debugName: "pdfSrc" }] : /* istanbul ignore next */ []));
|
|
2552
|
-
fallbackLabel = computed(() => ({ image: 'Visor de imagen', video: 'Visor de video', pdf: 'Visor de documento' })[this.type()], /* @ts-ignore */
|
|
2553
|
-
...(ngDevMode ? [{ debugName: "fallbackLabel" }] : /* istanbul ignore next */ []));
|
|
2554
|
-
dialog = viewChild('dialog', /* @ts-ignore */
|
|
2555
|
-
...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
|
|
2556
|
-
document = inject(DOCUMENT);
|
|
2557
|
-
sanitizer = inject(DomSanitizer);
|
|
2558
|
-
constructor() {
|
|
2559
|
-
afterRenderEffect(() => {
|
|
2560
|
-
const el = this.dialog()?.nativeElement;
|
|
2561
|
-
if (!el) {
|
|
2562
|
-
return;
|
|
2563
|
-
}
|
|
2564
|
-
if (this.open() && !el.open) {
|
|
2565
|
-
// `showModal` no existe en jsdom (tests) ni en entornos sin
|
|
2566
|
-
// top-layer; el atributo `open` lo deja visible, aunque no modal.
|
|
2567
|
-
if (typeof el.showModal === 'function') {
|
|
2568
|
-
el.showModal();
|
|
2569
|
-
}
|
|
2570
|
-
else {
|
|
2571
|
-
el.open = true;
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
else if (!this.open() && el.open) {
|
|
2575
|
-
if (typeof el.close === 'function') {
|
|
2576
|
-
el.close();
|
|
2577
|
-
}
|
|
2578
|
-
else {
|
|
2579
|
-
el.open = false;
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
// `<dialog>` inertiza el resto de la página pero no frena su scroll.
|
|
2583
|
-
this.document.body.style.overflow = this.open() ? 'hidden' : '';
|
|
2584
|
-
});
|
|
2585
|
-
inject(DestroyRef).onDestroy(() => {
|
|
2586
|
-
this.document.body.style.overflow = '';
|
|
2587
|
-
});
|
|
2588
|
-
}
|
|
2589
|
-
/** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
|
|
2590
|
-
onCancel(event) {
|
|
2591
|
-
if (!this.dismissible()) {
|
|
2592
|
-
event.preventDefault();
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
/**
|
|
2596
|
-
* Clic en el backdrop. Un clic en la zona del backdrop tiene como `target` al
|
|
2597
|
-
* propio `<dialog>` (el contenido vive en el panel de adentro), así que basta
|
|
2598
|
-
* comparar contra él — no hace falta cortar la propagación en el panel.
|
|
2599
|
-
*/
|
|
2600
|
-
onHostClick(event) {
|
|
2601
|
-
if (this.dismissible() && event.target === this.dialog()?.nativeElement) {
|
|
2602
|
-
this.open.set(false);
|
|
2603
|
-
}
|
|
2604
|
-
}
|
|
2605
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2606
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentViewer, isStandalone: true, selector: "kf-content-viewer", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "click": "onHostClick($event)" } }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
|
|
2607
|
-
}
|
|
2608
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, decorators: [{
|
|
2609
|
-
type: Component,
|
|
2610
|
-
args: [{ selector: 'kf-content-viewer', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"] }]
|
|
2611
|
-
}], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], onHostClick: [{
|
|
2612
|
-
type: HostListener,
|
|
2613
|
-
args: ['click', ['$event']]
|
|
2614
|
-
}] } });
|
|
2615
|
-
|
|
2616
2506
|
/**
|
|
2617
2507
|
* Label superior compartido por todos los campos de formulario del design
|
|
2618
2508
|
* system (páginas 11-12: Text Field, Select, Multiselect) — uppercase,
|
|
@@ -2755,7 +2645,7 @@ class SelectOverlay {
|
|
|
2755
2645
|
}
|
|
2756
2646
|
}
|
|
2757
2647
|
|
|
2758
|
-
let nextId$
|
|
2648
|
+
let nextId$4 = 0;
|
|
2759
2649
|
/** Ancho mínimo del panel flotante en `variant="compact"` — el trigger de
|
|
2760
2650
|
* ese modo puede medir menos de 45px (ej. "10" + caret), y sin este piso
|
|
2761
2651
|
* la opción quedaría apretada contra ese mismo ancho angosto. 72px es lo
|
|
@@ -2873,7 +2763,7 @@ class Select {
|
|
|
2873
2763
|
panelWidth = input('trigger', /* @ts-ignore */
|
|
2874
2764
|
...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
|
|
2875
2765
|
valueChange = output();
|
|
2876
|
-
instanceId = `kf-select-${nextId$
|
|
2766
|
+
instanceId = `kf-select-${nextId$4++}`;
|
|
2877
2767
|
triggerId = `${this.instanceId}-trigger`;
|
|
2878
2768
|
listboxId = `${this.instanceId}-listbox`;
|
|
2879
2769
|
errorId = `${this.instanceId}-error`;
|
|
@@ -2959,16 +2849,28 @@ class Select {
|
|
|
2959
2849
|
untracked(() => this.focusOption(this.activeIndex()));
|
|
2960
2850
|
}
|
|
2961
2851
|
});
|
|
2962
|
-
|
|
2852
|
+
const stopEscape = listenEscapeCapture(this.document, () => this.onDocumentEscape());
|
|
2853
|
+
inject(DestroyRef).onDestroy(() => {
|
|
2854
|
+
stopEscape();
|
|
2855
|
+
this.overlay.dispose();
|
|
2856
|
+
});
|
|
2963
2857
|
}
|
|
2964
2858
|
/**
|
|
2965
|
-
*
|
|
2966
|
-
*
|
|
2967
|
-
*
|
|
2968
|
-
*
|
|
2859
|
+
* Escape con el panel abierto, escuchado en fase de captura sobre
|
|
2860
|
+
* `document` (ver `listenEscapeCapture`). Respaldo de
|
|
2861
|
+
* `overlayRef.keydownEvents()`: ese observable del CDK solo ve teclas
|
|
2862
|
+
* presionadas con el foco DENTRO del pane del overlay; si el foco sigue en
|
|
2863
|
+
* el trigger (o en cualquier otro lado) mientras el panel está abierto,
|
|
2864
|
+
* nunca dispara. Consume la tecla (`preventDefault`) solo si había un
|
|
2865
|
+
* panel que cerrar, así un `kf-drawer`/`kf-modal` que contenga al select
|
|
2866
|
+
* no se cierra con el mismo Escape. Devuelve si cerró algo.
|
|
2969
2867
|
*/
|
|
2970
2868
|
onDocumentEscape() {
|
|
2869
|
+
if (!this.isOpen()) {
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2971
2872
|
this.close();
|
|
2873
|
+
return true;
|
|
2972
2874
|
}
|
|
2973
2875
|
toggle() {
|
|
2974
2876
|
if (this.disabled()) {
|
|
@@ -3137,7 +3039,7 @@ class Select {
|
|
|
3137
3039
|
}
|
|
3138
3040
|
}
|
|
3139
3041
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3140
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Select, isStandalone: true, selector: "kf-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, panelTitle: { classPropertyName: "panelTitle", publicName: "panelTitle", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" }, properties: { "class.kf-select--compact": "variant() === 'compact'", "class.kf-select--width-sm": "width() === 'sm'", "class.kf-select--width-lg": "width() === 'lg'", "class.kf-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [class.kf-select__trigger--with-icon]=\"!!icon()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-select__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ triggerText() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div #panel class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (panelTitle(); as title) {\n <div class=\"kf-select__panel-title kf-overline-text\" aria-hidden=\"true\">{{ title }}</div>\n }\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n aria-label=\"Buscar opci\u00F3n\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"filteredOptions().length ? optionId(activeIndex()) : null\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n }\n <ul\n class=\"kf-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : triggerId\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [id]=\"optionId(i)\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown)=\"onOptionKeydown($event, option, i)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}.kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__icon{flex:none;color:var(--kf-color-text-muted)}.kf-select__trigger--with-icon .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}:host(.kf-select--compact) .kf-select__trigger--with-icon .kf-select__value{color:var(--kf-color-ink);font-weight:600}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;font-family:var(--kf-font-body);width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__panel-title{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-xs)}.kf-select__panel--compact .kf-select__panel-title{padding:var(--kf-space-xs) var(--kf-space-sm) 2px}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);outline:none;color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__option:focus-visible{background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot", "truncate"] }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
|
|
3042
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Select, isStandalone: true, selector: "kf-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, panelTitle: { classPropertyName: "panelTitle", publicName: "panelTitle", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { properties: { "class.kf-select--compact": "variant() === 'compact'", "class.kf-select--width-sm": "width() === 'sm'", "class.kf-select--width-lg": "width() === 'lg'", "class.kf-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [class.kf-select__trigger--with-icon]=\"!!icon()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-select__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ triggerText() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div #panel class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (panelTitle(); as title) {\n <div class=\"kf-select__panel-title kf-overline-text\" aria-hidden=\"true\">{{ title }}</div>\n }\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n aria-label=\"Buscar opci\u00F3n\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"filteredOptions().length ? optionId(activeIndex()) : null\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n }\n <ul\n class=\"kf-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : triggerId\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [id]=\"optionId(i)\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown)=\"onOptionKeydown($event, option, i)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}.kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__icon{flex:none;color:var(--kf-color-text-muted)}.kf-select__trigger--with-icon .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}:host(.kf-select--compact) .kf-select__trigger--with-icon .kf-select__value{color:var(--kf-color-ink);font-weight:600}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;font-family:var(--kf-font-body);width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__panel-title{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-xs)}.kf-select__panel--compact .kf-select__panel-title{padding:var(--kf-space-xs) var(--kf-space-sm) 2px}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);outline:none;color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__option:focus-visible{background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot", "truncate"] }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
|
|
3141
3043
|
}
|
|
3142
3044
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, decorators: [{
|
|
3143
3045
|
type: Component,
|
|
@@ -3147,10 +3049,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3147
3049
|
'[class.kf-select--width-lg]': "width() === 'lg'",
|
|
3148
3050
|
'[class.kf-select--width-full]': "width() === 'full'",
|
|
3149
3051
|
}, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-select__trigger\"\n [class.kf-select__trigger--disabled]=\"disabled()\"\n [class.kf-select__trigger--invalid]=\"invalid()\"\n [class.kf-select__trigger--with-icon]=\"!!icon()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [class.kf-select__trigger--tagged]=\"!!selectedOption()?.tag\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-select__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <span class=\"kf-select__value\" [class.kf-select__value--placeholder]=\"!selectedOption()\">\n {{ triggerText() }}\n </span>\n @if (selectedOption()?.tag; as tag) {\n <kf-badge class=\"kf-select__tag\">{{ tag }}</kf-badge>\n }\n <span\n class=\"kf-select__caret kf-caret\"\n [class.kf-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n@if (errorMessage(); as message) {\n <span class=\"kf-select__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n}\n<ng-template #panelTemplate>\n <div #panel class=\"kf-select__panel\" [class.kf-select__panel--compact]=\"variant() === 'compact'\">\n @if (panelTitle(); as title) {\n <div class=\"kf-select__panel-title kf-overline-text\" aria-hidden=\"true\">{{ title }}</div>\n }\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-select__search\"\n placeholder=\"Buscar\u2026\"\n aria-label=\"Buscar opci\u00F3n\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"filteredOptions().length ? optionId(activeIndex()) : null\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); selectActive()\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n }\n <ul\n class=\"kf-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabel() ? null : triggerId\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-select__option\"\n [id]=\"optionId(i)\"\n [class.kf-select__option--selected]=\"option.value === value()\"\n [class.kf-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"-1\"\n [attr.aria-selected]=\"option.value === value()\"\n [attr.aria-label]=\"optionAriaLabel(option)\"\n [class.kf-select__option--rich]=\"!!(option.tag || option.detail)\"\n (click)=\"select(option)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown)=\"onOptionKeydown($event, option, i)\"\n >\n @if (option.tag || option.detail) {\n <span class=\"kf-select__option-main\">\n <span class=\"kf-select__option-label\">{{ option.label }}</span>\n @if (option.tag) {\n <kf-badge class=\"kf-select__tag\">{{ option.tag }}</kf-badge>\n }\n </span>\n @if (option.detail) {\n <span class=\"kf-select__option-detail\">{{ option.detail }}</span>\n }\n } @else {\n {{ option.label }}\n }\n </li>\n } @empty {\n <li class=\"kf-select__empty\">\n {{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}\n </li>\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}.kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:var(--kf-select-width-md);font-family:var(--kf-font-body)}:host(.kf-select--compact){width:max-content;max-width:160px}:host(.kf-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-select--width-full){width:100%}:host(.kf-select--compact) .kf-select__trigger{padding:6px var(--kf-space-sm);font-size:.85rem;gap:var(--kf-space-xs)}:host(.kf-select--compact) .kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-select__trigger:focus-visible{outline:none;border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-select__trigger--disabled{opacity:.5;cursor:not-allowed}.kf-select__trigger--invalid{border-color:var(--kf-color-error)}.kf-select__trigger--invalid:focus-visible{border-color:var(--kf-color-error);box-shadow:var(--kf-shadow-focus-error)}.kf-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__icon{flex:none;color:var(--kf-color-text-muted)}.kf-select__trigger--with-icon .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}:host(.kf-select--compact) .kf-select__trigger--with-icon .kf-select__value{color:var(--kf-color-ink);font-weight:600}.kf-select__trigger--tagged .kf-select__value{flex:1 1 auto;min-width:0;text-align:start}.kf-select__tag{flex:none}.kf-select__trigger .kf-select__tag{margin-block:-2px}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__panel{box-sizing:border-box;font-family:var(--kf-font-body);width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__panel-title{padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-xs)}.kf-select__panel--compact .kf-select__panel-title{padding:var(--kf-space-xs) var(--kf-space-sm) 2px}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);outline:none;color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option--rich{display:flex;flex-direction:column;gap:2px}.kf-select__option-main{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-select__option-label,.kf-select__option-detail{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.kf-select__option-detail{font-size:var(--kf-font-size-body-sm);font-weight:400;color:var(--kf-color-text-muted)}.kf-select__panel--compact .kf-select__option{padding:6px var(--kf-space-sm);font-size:.85rem}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__option:focus-visible{background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
|
|
3150
|
-
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], panelTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelTitle", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }]
|
|
3151
|
-
type: HostListener,
|
|
3152
|
-
args: ['document:keydown.escape']
|
|
3153
|
-
}] } });
|
|
3052
|
+
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], panelTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelTitle", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
3154
3053
|
|
|
3155
3054
|
const SIBLINGS = 1;
|
|
3156
3055
|
/**
|
|
@@ -3327,6 +3226,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3327
3226
|
const INTERACTIVE_CONTROL_SELECTOR = 'a[href], button, input, select, textarea, summary, label, [role="button"], [role="link"], [role="menuitem"], [role="checkbox"], [role="switch"], [role="tab"], [contenteditable="true"]';
|
|
3328
3227
|
/** Piso de ancho de una columna `grow` que no declara `minWidth` ni `width`. */
|
|
3329
3228
|
const GROW_MIN_WIDTH = '8rem';
|
|
3229
|
+
/** Tope de filas (o tarjetas) esqueleto: con `loading`, una página de 50 no pinta 50. */
|
|
3230
|
+
const DATA_TABLE_MAX_SKELETON_ROWS = 12;
|
|
3330
3231
|
/** Opciones de "filas por página" estándar del portal — 10 como default
|
|
3331
3232
|
* cubre bien tanto un dataset chico (ej. Beneficios) como uno con volumen
|
|
3332
3233
|
* real (ej. 30+ cuotas en Billing), sin sentirse vacío ni insuficiente; 25
|
|
@@ -3365,12 +3266,21 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
|
|
|
3365
3266
|
* <kf-data-table [columns]="columns" [rows]="rows" [cardable]="true" defaultView="cards" />
|
|
3366
3267
|
*
|
|
3367
3268
|
* Loading nativo: con `loading` en `true`, la tabla se reemplaza por un
|
|
3368
|
-
* placeholder (`kf-skeleton` por celda/card) —
|
|
3369
|
-
* `pageSize`, o 5 si no hay paginación activa.
|
|
3269
|
+
* placeholder (`kf-skeleton` por celda/card) — `skeletonRows` filas si se
|
|
3270
|
+
* pasa; si no, las de `pageSize`, o 5 si no hay paginación activa. Nunca más
|
|
3271
|
+
* de 12 (`DATA_TABLE_MAX_SKELETON_ROWS`): con una página de 50 no se pintan
|
|
3272
|
+
* 50 placeholders. Ignora `rows()`/el slot
|
|
3370
3273
|
* `[empty]` mientras dura (no sabemos aún si el resultado real estará
|
|
3371
3274
|
* vacío), y respeta la vista activa (tabla o cards) para no cambiar el
|
|
3372
3275
|
* layout de golpe cuando los datos reales lleguen. El toggle tabla/cards y
|
|
3373
|
-
* el header de columnas siguen visibles — solo el cuerpo se reemplaza
|
|
3276
|
+
* el header de columnas siguen visibles — solo el cuerpo se reemplaza.
|
|
3277
|
+
*
|
|
3278
|
+
* El placeholder respeta la columna: en la tabla cada celda conserva su
|
|
3279
|
+
* `align`, `width`, `minWidth` y `maxWidth` (una columna numérica lleva una
|
|
3280
|
+
* barra corta alineada al final, una de acciones un cuadrado chico, el resto
|
|
3281
|
+
* barras de anchos distintos) y la fila reserva el alto de una línea de texto.
|
|
3282
|
+
* En tarjetas reproduce la estructura según `cardRole`: encabezado (miniatura,
|
|
3283
|
+
* título y estado), campos con su rótulo, fila de `meta` y pie de acciones:
|
|
3374
3284
|
*
|
|
3375
3285
|
* <kf-data-table [columns]="columns" [rows]="rows()" [loading]="isLoading()">
|
|
3376
3286
|
* <kf-empty-state empty>No hay resultados para mostrar.</kf-empty-state>
|
|
@@ -3463,6 +3373,30 @@ const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
|
|
|
3463
3373
|
* <kf-data-table [columns]="columns" [rows]="quotes" cardable [(view)]="view"
|
|
3464
3374
|
* [showViewToggle]="false" cardMinWidth="300px" [rowClickable]="true"
|
|
3465
3375
|
* (rowClick)="open($event.row)" />
|
|
3376
|
+
*
|
|
3377
|
+
* Celdas interactivas (modo aditivo, ej. una matriz unidad × etapa donde cada
|
|
3378
|
+
* celda de etapa es un indicador de estado): una columna con
|
|
3379
|
+
* `interactive: true` renderiza su contenido dentro de un `<button>` real
|
|
3380
|
+
* —focusable, con foco visible y activación con Enter o Espacio— en la vista
|
|
3381
|
+
* de tabla y en la de tarjetas, y emite `cellClick` con `{ row, column, index }`.
|
|
3382
|
+
* `cellAriaLabel` da el nombre accesible de cada celda cuando el contenido no
|
|
3383
|
+
* lo aporta por sí solo (un indicador sin texto); en tarjetas conviene incluir
|
|
3384
|
+
* el contexto de la fila, porque no hay encabezado de columna que lo dé:
|
|
3385
|
+
*
|
|
3386
|
+
* columns = [
|
|
3387
|
+
* { key: 'unit', label: 'Unidad', cardRole: 'title', sticky: 'left' },
|
|
3388
|
+
* { key: 'stage1', label: 'Recepción', interactive: true, align: 'center' },
|
|
3389
|
+
* { key: 'stage2', label: 'Entrega', interactive: true, align: 'center' },
|
|
3390
|
+
* ];
|
|
3391
|
+
* <kf-data-table [columns]="columns" [rows]="units" cardable [rowKey]="unitId"
|
|
3392
|
+
* [cellTemplates]="{ stage1: stageCell, stage2: stageCell }"
|
|
3393
|
+
* [cellAriaLabel]="stageLabel" (cellClick)="openStage($event.row, $event.column)" />
|
|
3394
|
+
*
|
|
3395
|
+
* Convive con `rowClick`: la fila sigue emitiéndolo desde cualquier punto que
|
|
3396
|
+
* no sea una celda interactiva. En tarjetas, la tarjeta deja de ser un botón
|
|
3397
|
+
* (un botón dentro de un `role="button"` es inválido) salvo que se fuerce con
|
|
3398
|
+
* `[rowClickable]="true"`. `sticky`, `cellTemplates`, `currentRowKey`, la
|
|
3399
|
+
* ordenación y la paginación no cambian.
|
|
3466
3400
|
*/
|
|
3467
3401
|
class DataTable {
|
|
3468
3402
|
columns = input.required(/* @ts-ignore */
|
|
@@ -3473,6 +3407,11 @@ class DataTable {
|
|
|
3473
3407
|
* ver el doc-comment de la clase para el detalle. */
|
|
3474
3408
|
loading = input(false, /* @ts-ignore */
|
|
3475
3409
|
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
3410
|
+
/** Filas (o tarjetas) esqueleto mientras `loading` es `true`. Sin valor: el
|
|
3411
|
+
* `pageSize` con paginación activa, o 5. Máximo
|
|
3412
|
+
* {@link DATA_TABLE_MAX_SKELETON_ROWS}. */
|
|
3413
|
+
skeletonRows = input(undefined, /* @ts-ignore */
|
|
3414
|
+
...(ngDevMode ? [{ debugName: "skeletonRows" }] : /* istanbul ignore next */ []));
|
|
3476
3415
|
scrollEl = viewChild('scroll', /* @ts-ignore */
|
|
3477
3416
|
...(ngDevMode ? [{ debugName: "scrollEl" }] : /* istanbul ignore next */ []));
|
|
3478
3417
|
destroyRef = inject(DestroyRef);
|
|
@@ -3593,7 +3532,9 @@ class DataTable {
|
|
|
3593
3532
|
*/
|
|
3594
3533
|
render = input(/* @ts-ignore */
|
|
3595
3534
|
...(ngDevMode ? [undefined, { debugName: "render" }] : /* istanbul ignore next */ []));
|
|
3596
|
-
/** Mapa `columnKey -> TemplateRef` para celdas con contenido custom.
|
|
3535
|
+
/** Mapa `columnKey -> TemplateRef` para celdas con contenido custom. El
|
|
3536
|
+
* contexto trae `$implicit` (la fila), `index` y `column` (la columna), así
|
|
3537
|
+
* que un mismo template puede servir a varias columnas. */
|
|
3597
3538
|
cellTemplates = input(/* @ts-ignore */
|
|
3598
3539
|
...(ngDevMode ? [undefined, { debugName: "cellTemplates" }] : /* istanbul ignore next */ []));
|
|
3599
3540
|
/** Densidad de fila — ver `DataTableDensity`. */
|
|
@@ -3604,6 +3545,19 @@ class DataTable {
|
|
|
3604
3545
|
rowClickable = input(null, /* @ts-ignore */
|
|
3605
3546
|
...(ngDevMode ? [{ debugName: "rowClickable" }] : /* istanbul ignore next */ []));
|
|
3606
3547
|
rowClick = output();
|
|
3548
|
+
/**
|
|
3549
|
+
* Se emite al activar (clic, Enter o Espacio) una celda de una columna
|
|
3550
|
+
* `interactive`. `index` es el de la fila dentro de las filas mostradas
|
|
3551
|
+
* (misma convención que `rowClick`). No emite `rowClick`.
|
|
3552
|
+
*/
|
|
3553
|
+
cellClick = output();
|
|
3554
|
+
/**
|
|
3555
|
+
* Nombre accesible (`aria-label`) del botón de cada celda interactiva:
|
|
3556
|
+
* `(row, column, index) => string`. Sin esta función, el botón se nombra
|
|
3557
|
+
* por su contenido. Se usa solo en columnas `interactive`.
|
|
3558
|
+
*/
|
|
3559
|
+
cellAriaLabel = input(/* @ts-ignore */
|
|
3560
|
+
...(ngDevMode ? [undefined, { debugName: "cellAriaLabel" }] : /* istanbul ignore next */ []));
|
|
3607
3561
|
/** Nombre accesible del contenedor con scroll horizontal (solo se aplica cuando hay overflow). */
|
|
3608
3562
|
ariaLabel = input('Tabla de datos', /* @ts-ignore */
|
|
3609
3563
|
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
@@ -3657,11 +3611,42 @@ class DataTable {
|
|
|
3657
3611
|
effectivePageSize = computed(() => this.internalPageSize() ?? this.pageSize(), /* @ts-ignore */
|
|
3658
3612
|
...(ngDevMode ? [{ debugName: "effectivePageSize" }] : /* istanbul ignore next */ []));
|
|
3659
3613
|
/** Cantidad de filas/cards placeholder mientras `loading` es true — el
|
|
3660
|
-
* `
|
|
3661
|
-
* "adivina" cuántas filas reales
|
|
3662
|
-
* o 5
|
|
3663
|
-
|
|
3614
|
+
* `skeletonRows` explícito; si no hay, el `pageSize` efectivo con
|
|
3615
|
+
* paginación activa (así el placeholder ya "adivina" cuántas filas reales
|
|
3616
|
+
* van a aparecer, sin salto de layout) o 5 sin paginación. Siempre entre 1 y
|
|
3617
|
+
* {@link DATA_TABLE_MAX_SKELETON_ROWS}: con una página de 50 filas no se
|
|
3618
|
+
* pintan 50 placeholders. */
|
|
3619
|
+
skeletonRowIndexes = computed(() => {
|
|
3620
|
+
const wanted = this.skeletonRows() ?? this.effectivePageSize() ?? 5;
|
|
3621
|
+
const total = Math.min(Math.max(Math.floor(wanted) || 0, 1), DATA_TABLE_MAX_SKELETON_ROWS);
|
|
3622
|
+
return Array.from({ length: total }, (_, i) => i);
|
|
3623
|
+
}, /* @ts-ignore */
|
|
3664
3624
|
...(ngDevMode ? [{ debugName: "skeletonRowIndexes" }] : /* istanbul ignore next */ []));
|
|
3625
|
+
/** Las tarjetas esqueleto llevan encabezado cuando la real lo tendría. */
|
|
3626
|
+
skeletonCardHeader = computed(() => !!this.cardMediaColumn() || !!this.cardTitleColumn() || this.cardStatusColumns().length > 0, /* @ts-ignore */
|
|
3627
|
+
...(ngDevMode ? [{ debugName: "skeletonCardHeader" }] : /* istanbul ignore next */ []));
|
|
3628
|
+
/**
|
|
3629
|
+
* Ancho (%) de la barra de una celda esqueleto: no todas iguales — una
|
|
3630
|
+
* columna numérica (alineada al final) es corta, un identificador
|
|
3631
|
+
* (`mono`) mediano, una de acciones (`hideLabel`) un cuadrado chico, y el
|
|
3632
|
+
* resto alterna entre tres anchos según su posición para que la tabla no
|
|
3633
|
+
* parezca un patrón repetido.
|
|
3634
|
+
*/
|
|
3635
|
+
skeletonBarWidth(column, row, index) {
|
|
3636
|
+
if (column.hideLabel) {
|
|
3637
|
+
return '2rem';
|
|
3638
|
+
}
|
|
3639
|
+
if (this.isEndAligned(column)) {
|
|
3640
|
+
return ['55%', '45%', '60%'][(row + index) % 3];
|
|
3641
|
+
}
|
|
3642
|
+
if (column.mono) {
|
|
3643
|
+
return '70%';
|
|
3644
|
+
}
|
|
3645
|
+
if (column.truncate) {
|
|
3646
|
+
return '90%';
|
|
3647
|
+
}
|
|
3648
|
+
return ['85%', '65%', '75%'][(row + index) % 3];
|
|
3649
|
+
}
|
|
3665
3650
|
totalPages = computed(() => {
|
|
3666
3651
|
const size = this.effectivePageSize();
|
|
3667
3652
|
if (!size) {
|
|
@@ -3884,8 +3869,12 @@ class DataTable {
|
|
|
3884
3869
|
* `role="button"` con botones adentro es inválido y deja el foco ambiguo.
|
|
3885
3870
|
* Con columnas `action`, la interacción la manejan esos botones.
|
|
3886
3871
|
*/
|
|
3887
|
-
cardInteractive = computed(() => this.rowClickable() ??
|
|
3872
|
+
cardInteractive = computed(() => this.rowClickable() ??
|
|
3873
|
+
(this.cardActionColumns().length === 0 && !this.hasInteractiveColumns()), /* @ts-ignore */
|
|
3888
3874
|
...(ngDevMode ? [{ debugName: "cardInteractive" }] : /* istanbul ignore next */ []));
|
|
3875
|
+
/** Hay al menos una columna `interactive` (celdas con botón propio). */
|
|
3876
|
+
hasInteractiveColumns = computed(() => this.columns().some((column) => column.interactive), /* @ts-ignore */
|
|
3877
|
+
...(ngDevMode ? [{ debugName: "hasInteractiveColumns" }] : /* istanbul ignore next */ []));
|
|
3889
3878
|
/**
|
|
3890
3879
|
* Mismo criterio que `cardInteractive`, para la vista de tabla: la fila
|
|
3891
3880
|
* entera dispara `rowClick` solo si no hay una columna `action` que ya
|
|
@@ -3952,6 +3941,14 @@ class DataTable {
|
|
|
3952
3941
|
}
|
|
3953
3942
|
this.rowClick.emit({ row, index });
|
|
3954
3943
|
}
|
|
3944
|
+
/** Activación de una celda interactiva (clic, o Enter/Espacio sobre su botón). */
|
|
3945
|
+
onCellClick(row, column, index) {
|
|
3946
|
+
this.cellClick.emit({ row, column, index });
|
|
3947
|
+
}
|
|
3948
|
+
/** `aria-label` del botón de una celda interactiva, o `null` si lo da su contenido. */
|
|
3949
|
+
cellLabel(row, column, index) {
|
|
3950
|
+
return this.cellAriaLabel()?.(row, column, index) || null;
|
|
3951
|
+
}
|
|
3955
3952
|
originatesInInteractiveControl(event) {
|
|
3956
3953
|
const target = event?.target;
|
|
3957
3954
|
const host = event?.currentTarget;
|
|
@@ -3990,43 +3987,545 @@ class DataTable {
|
|
|
3990
3987
|
this.stickyOffsets.set(offsets);
|
|
3991
3988
|
}
|
|
3992
3989
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3993
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DataTable, isStandalone: true, selector: "kf-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, cardable: { classPropertyName: "cardable", publicName: "cardable", isSignal: true, isRequired: false, transformFunction: null }, defaultView: { classPropertyName: "defaultView", publicName: "defaultView", isSignal: true, isRequired: false, transformFunction: null }, showViewToggle: { classPropertyName: "showViewToggle", publicName: "showViewToggle", isSignal: true, isRequired: false, transformFunction: null }, cardMinWidth: { classPropertyName: "cardMinWidth", publicName: "cardMinWidth", isSignal: true, isRequired: false, transformFunction: null }, render: { classPropertyName: "render", publicName: "render", isSignal: true, isRequired: false, transformFunction: null }, cellTemplates: { classPropertyName: "cellTemplates", publicName: "cellTemplates", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, rowClickable: { classPropertyName: "rowClickable", publicName: "rowClickable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentRowKey: { classPropertyName: "currentRowKey", publicName: "currentRowKey", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, keepPageOnRowsChange: { classPropertyName: "keepPageOnRowsChange", publicName: "keepPageOnRowsChange", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", page: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", view: "viewChange" }, viewQueries: [{ propertyName: "scrollEl", first: true, predicate: ["scroll"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\">\n <kf-skeleton variant=\"rect\" height=\"120px\" />\n </div>\n }\n </div>\n } @else {\n <div class=\"kf-data-table__scroll\" aria-busy=\"true\">\n <table class=\"kf-data-table__table\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [style.width]=\"column.width\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n >\n <kf-skeleton />\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.enter)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.space)=\"cardInteractive() && onRowSpace($event, row, i)\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto) \u2014 compartido por la celda normal y la `truncate`. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Pagination, selector: "kf-pagination", inputs: ["page", "pages", "pageSizeOptions", "pageSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: ViewToggle, selector: "kf-view-toggle", inputs: ["view", "iconOnly", "tableLabel", "cardsLabel", "ariaLabel"], outputs: ["viewChange"] }] });
|
|
3990
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DataTable, isStandalone: true, selector: "kf-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, skeletonRows: { classPropertyName: "skeletonRows", publicName: "skeletonRows", isSignal: true, isRequired: false, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, cardable: { classPropertyName: "cardable", publicName: "cardable", isSignal: true, isRequired: false, transformFunction: null }, defaultView: { classPropertyName: "defaultView", publicName: "defaultView", isSignal: true, isRequired: false, transformFunction: null }, showViewToggle: { classPropertyName: "showViewToggle", publicName: "showViewToggle", isSignal: true, isRequired: false, transformFunction: null }, cardMinWidth: { classPropertyName: "cardMinWidth", publicName: "cardMinWidth", isSignal: true, isRequired: false, transformFunction: null }, render: { classPropertyName: "render", publicName: "render", isSignal: true, isRequired: false, transformFunction: null }, cellTemplates: { classPropertyName: "cellTemplates", publicName: "cellTemplates", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, rowClickable: { classPropertyName: "rowClickable", publicName: "rowClickable", isSignal: true, isRequired: false, transformFunction: null }, cellAriaLabel: { classPropertyName: "cellAriaLabel", publicName: "cellAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, currentRowKey: { classPropertyName: "currentRowKey", publicName: "currentRowKey", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, keepPageOnRowsChange: { classPropertyName: "keepPageOnRowsChange", publicName: "keepPageOnRowsChange", isSignal: true, isRequired: false, transformFunction: null }, itemLabel: { classPropertyName: "itemLabel", publicName: "itemLabel", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClick: "rowClick", cellClick: "cellClick", page: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", view: "viewChange" }, viewQueries: [{ propertyName: "scrollEl", first: true, predicate: ["scroll"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\" aria-hidden=\"true\">\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn()) {\n <div class=\"kf-data-table__card-media\">\n <span class=\"kf-data-table__sk-media kf-bone-fill\"></span>\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n <span\n class=\"kf-data-table__card-title kf-bone\"\n [class.kf-data-table__card-title--mono]=\"cardTitleColumn()?.mono\"\n style=\"--kf-bone-width: 8rem\"\n >COT-2026-0000</span\n >\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n <span class=\"kf-data-table__sk-pill kf-bone-fill\"></span>\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n >\n <span class=\"kf-data-table__sk-lead kf-bone\" style=\"--kf-bone-width: 90%\"\n >Texto</span\n >\n </div>\n }\n @for (column of cardFieldColumns(); track column.key; let first = $first) {\n <div class=\"kf-data-table__card-field\">\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >{{ column.label }}</span\n >\n @if (first && !skeletonCardHeader()) {\n <kf-skeleton class=\"kf-data-table__sk-value\" width=\"100%\" />\n } @else {\n <span class=\"kf-data-table__sk-value kf-bone\" style=\"--kf-bone-width: 40%\"\n >Valor</span\n >\n }\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 4rem\"\n >{{ column.label }}</span\n >\n <span\n class=\"kf-data-table__card-meta-value kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >Valor</span\n >\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n <span class=\"kf-data-table__sk-action kf-bone-fill\"></span>\n </div>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n aria-busy=\"true\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key; let c = $index) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n <div\n class=\"kf-data-table__sk-cell\"\n [class.kf-data-table__sk-cell--end]=\"isEndAligned(column)\"\n [class.kf-data-table__sk-cell--center]=\"column.align === 'center'\"\n >\n <kf-skeleton\n class=\"kf-data-table__sk-bar\"\n [style.width]=\"skeletonBarWidth(column, i, c)\"\n />\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.enter)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.space)=\"cardInteractive() ? onRowSpace($event, row, i) : undefined\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: media }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Celda de la vista tabla \u2014 compartida por la celda normal y la `truncate`.\n Una columna `interactive` envuelve su contenido en un bot\u00F3n real. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto). -->\n<ng-template #tableCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Zona de la tarjeta: igual que la celda de tabla, con bot\u00F3n si la columna es `interactive`. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);max-width:100%;min-width:32px;min-height:32px;padding:var(--kf-space-xs);border:0;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;text-align:inherit;cursor:pointer}.kf-data-table__cell-button:hover{box-shadow:inset 0 0 0 1px var(--kf-color-border-strong)}.kf-data-table__cell-button:focus-visible{outline:2px solid transparent;outline-offset:-2px;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}@media(max-width:640px),(pointer:coarse){.kf-data-table__cell-button{min-width:44px;min-height:44px}}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}.kf-data-table__sk-cell{display:flex;align-items:center;justify-content:flex-start;min-height:1lh}.kf-data-table__sk-cell--center{justify-content:center}.kf-data-table__sk-cell--end{justify-content:flex-end}.kf-data-table__sk-bar{flex:none}.kf-data-table__sk-media{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs)}.kf-data-table__sk-pill{display:block;width:4.5rem;height:1.25rem;border-radius:var(--kf-radius-pill)}.kf-data-table__sk-value{flex:none;width:40%}.kf-data-table__sk-lead{line-height:1.35}.kf-data-table__sk-action{display:block;width:5rem;height:2rem;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Pagination, selector: "kf-pagination", inputs: ["page", "pages", "pageSizeOptions", "pageSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: ViewToggle, selector: "kf-view-toggle", inputs: ["view", "iconOnly", "tableLabel", "cardsLabel", "ariaLabel"], outputs: ["viewChange"] }] });
|
|
3994
3991
|
}
|
|
3995
3992
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DataTable, decorators: [{
|
|
3996
3993
|
type: Component,
|
|
3997
|
-
args: [{ selector: 'kf-data-table', imports: [Icon, NgTemplateOutlet, Pagination, Skeleton, ViewToggle], template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\">\n <kf-skeleton variant=\"rect\" height=\"120px\" />\n </div>\n }\n </div>\n } @else {\n <div class=\"kf-data-table__scroll\" aria-busy=\"true\">\n <table class=\"kf-data-table__table\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [style.width]=\"column.width\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n >\n <kf-skeleton />\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.enter)=\"cardInteractive() && onRowClick(row, i, $event)\"\n (keydown.space)=\"cardInteractive() && onRowSpace($event, row, i)\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto) \u2014 compartido por la celda normal y la `truncate`. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}\n"] }]
|
|
3998
|
-
}], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], scrollEl: [{ type: i0.ViewChild, args: ['scroll', { isSignal: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], cardable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardable", required: false }] }], defaultView: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultView", required: false }] }], showViewToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showViewToggle", required: false }] }], cardMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardMinWidth", required: false }] }], render: [{ type: i0.Input, args: [{ isSignal: true, alias: "render", required: false }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], rowClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClickable", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentRowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentRowKey", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }], keepPageOnRowsChange: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepPageOnRowsChange", required: false }] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }] } });
|
|
3994
|
+
args: [{ selector: 'kf-data-table', imports: [Icon, NgTemplateOutlet, Pagination, Skeleton, ViewToggle], template: "<div\n class=\"kf-data-table\"\n [class.kf-data-table--flush]=\"flush()\"\n [class.kf-data-table--compact]=\"density() === 'compact'\"\n>\n @if (cardable() && showViewToggle()) {\n <div class=\"kf-data-table__toolbar\">\n <kf-view-toggle [view]=\"activeView()\" (viewChange)=\"setView($event)\" />\n </div>\n }\n\n @if (loading()) {\n @if (cardable() && activeView() === 'cards') {\n <div\n class=\"kf-data-table__cards\"\n aria-busy=\"true\"\n [style.--kf-data-table-card-min]=\"cardMinWidth()\"\n >\n @for (i of skeletonRowIndexes(); track i) {\n <div class=\"kf-data-table__card\" aria-hidden=\"true\">\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn()) {\n <div class=\"kf-data-table__card-media\">\n <span class=\"kf-data-table__sk-media kf-bone-fill\"></span>\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n <span\n class=\"kf-data-table__card-title kf-bone\"\n [class.kf-data-table__card-title--mono]=\"cardTitleColumn()?.mono\"\n style=\"--kf-bone-width: 8rem\"\n >COT-2026-0000</span\n >\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n <span class=\"kf-data-table__sk-pill kf-bone-fill\"></span>\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n >\n <span class=\"kf-data-table__sk-lead kf-bone\" style=\"--kf-bone-width: 90%\"\n >Texto</span\n >\n </div>\n }\n @for (column of cardFieldColumns(); track column.key; let first = $first) {\n <div class=\"kf-data-table__card-field\">\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >{{ column.label }}</span\n >\n @if (first && !skeletonCardHeader()) {\n <kf-skeleton class=\"kf-data-table__sk-value\" width=\"100%\" />\n } @else {\n <span class=\"kf-data-table__sk-value kf-bone\" style=\"--kf-bone-width: 40%\"\n >Valor</span\n >\n }\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span\n class=\"kf-data-table__card-label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 4rem\"\n >{{ column.label }}</span\n >\n <span\n class=\"kf-data-table__card-meta-value kf-bone\"\n style=\"--kf-bone-width: 5rem\"\n >Valor</span\n >\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n <span class=\"kf-data-table__sk-action kf-bone-fill\"></span>\n </div>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n aria-busy=\"true\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (i of skeletonRowIndexes(); track i) {\n <tr class=\"kf-data-table__row\">\n @for (column of columns(); track column.key; let c = $index) {\n <td\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n <div\n class=\"kf-data-table__sk-cell\"\n [class.kf-data-table__sk-cell--end]=\"isEndAligned(column)\"\n [class.kf-data-table__sk-cell--center]=\"column.align === 'center'\"\n >\n <kf-skeleton\n class=\"kf-data-table__sk-bar\"\n [style.width]=\"skeletonBarWidth(column, i, c)\"\n />\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (rows().length === 0) {\n <div class=\"kf-data-table__empty\">\n <ng-content select=\"[empty]\" />\n </div>\n } @else if (activeView() === 'table') {\n <div\n #scroll\n class=\"kf-data-table__scroll\"\n [class.kf-data-table__scroll--fade-left]=\"canScrollLeft()\"\n [class.kf-data-table__scroll--fade-right]=\"canScrollRight()\"\n [class.kf-data-table__scroll--weighted]=\"weighted()\"\n [attr.tabindex]=\"hasOverflow() ? 0 : null\"\n [attr.role]=\"hasOverflow() ? 'region' : null\"\n [attr.aria-label]=\"hasOverflow() ? ariaLabel() : null\"\n >\n <table class=\"kf-data-table__table\" [class.kf-data-table__table--weighted]=\"weighted()\">\n <thead>\n <tr>\n @for (column of columns(); track column.key) {\n <th\n class=\"kf-overline-text\"\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--sortable]=\"column.sortable\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [class.kf-data-table__cell--sort-active]=\"sortDirectionFor(column) !== null\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n (click)=\"toggleSort(column)\"\n >\n <span class=\"kf-data-table__th-content\">\n @if (column.hideLabel) {\n <span class=\"kf-data-table__sr-only\">{{ column.label }}</span>\n } @else {\n {{ column.label }}\n }\n @if (column.sortable) {\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n [rotate]=\"sortDirectionFor(column) === 'asc' ? 180 : 0\"\n class=\"kf-data-table__sort-icon\"\n [class.kf-data-table__sort-icon--active]=\"sortDirectionFor(column) !== null\"\n />\n }\n </span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <tr\n class=\"kf-data-table__row\"\n [class.kf-data-table__row--interactive]=\"rowInteractive()\"\n [class.kf-data-table__row--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.tabindex]=\"rowInteractive() ? 0 : null\"\n (click)=\"onRowClick(row, i, $event)\"\n (keydown.enter)=\"onRowClick(row, i, $event)\"\n (keydown.space)=\"onRowSpace($event, row, i)\"\n >\n @for (column of columns(); track column.key) {\n <td\n [attr.data-column-key]=\"column.key\"\n [class]=\"column.className\"\n [class.kf-data-table__cell--right]=\"isEndAligned(column)\"\n [class.kf-data-table__cell--center]=\"column.align === 'center'\"\n [class.kf-data-table__cell--truncate]=\"column.truncate\"\n [class.kf-data-table__cell--mono]=\"column.mono\"\n [class.kf-data-table__cell--emphasis]=\"column.emphasis\"\n [class.kf-data-table__cell--sticky]=\"column.sticky\"\n [style]=\"stickyStyle(column)\"\n [style.min-width]=\"minWidthFor(column)\"\n [style.width]=\"widthFor(column)\"\n [style.max-width]=\"maxWidthFor(column)\"\n >\n @if (column.truncate) {\n <span class=\"kf-data-table__truncate\" [attr.title]=\"truncateTitle(row, column)\">\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"kf-data-table__cards\" [style.--kf-data-table-card-min]=\"cardMinWidth()\">\n @for (row of displayedRows(); track trackRow(row, $index); let i = $index) {\n <div\n class=\"kf-data-table__card\"\n [class.kf-data-table__card--interactive]=\"cardInteractive()\"\n [class.kf-data-table__card--current]=\"isCurrentRow(row, i)\"\n [attr.aria-current]=\"isCurrentRow(row, i) ? 'true' : null\"\n [attr.role]=\"cardInteractive() ? 'button' : null\"\n [attr.tabindex]=\"cardInteractive() ? 0 : null\"\n (click)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.enter)=\"cardInteractive() ? onRowClick(row, i, $event) : undefined\"\n (keydown.space)=\"cardInteractive() ? onRowSpace($event, row, i) : undefined\"\n >\n @if (cardMediaColumn() || cardTitleColumn() || cardStatusColumns().length) {\n <div class=\"kf-data-table__card-header\">\n @if (cardMediaColumn(); as media) {\n <div class=\"kf-data-table__card-media\">\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: media }\"\n />\n } @else {\n <img [src]=\"cellValue(row, media)\" alt=\"\" />\n }\n </div>\n }\n <div class=\"kf-data-table__card-heading\">\n @if (cardTitleColumn(); as title) {\n <span\n class=\"kf-data-table__card-title\"\n [class.kf-data-table__card-title--mono]=\"title.mono\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: title, index: i }\"\n />\n </span>\n }\n @if (cardStatusColumns().length) {\n <span class=\"kf-data-table__card-statuses\">\n @for (column of cardStatusColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </span>\n }\n </div>\n </div>\n }\n\n @if (cardLeadColumns().length || cardFieldColumns().length) {\n <div class=\"kf-data-table__card-fields\">\n @for (column of cardLeadColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-lead\"\n [style.--kf-data-table-card-clamp]=\"column.cardClamp\"\n [attr.title]=\"cellValue(row, column) || null\"\n >\n <span class=\"kf-data-table__sr-only\">{{ column.label }}: </span>\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </div>\n }\n @for (column of cardFieldColumns(); track column.key) {\n <div class=\"kf-data-table__card-field\">\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span\n class=\"kf-data-table__card-value\"\n [class.kf-data-table__card-value--emphasis]=\"column.emphasis\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardMetaColumns().length) {\n <div class=\"kf-data-table__card-meta\">\n @for (column of cardMetaColumns(); track column.key) {\n <div\n class=\"kf-data-table__card-meta-item\"\n [class.kf-data-table__card-meta-item--end]=\"\n isEndAligned(column) || column.emphasis\n \"\n [class.kf-data-table__card-meta-item--emphasis]=\"column.emphasis\"\n [style.flex-basis]=\"column.width\"\n >\n <span class=\"kf-data-table__card-label kf-overline-text\">{{ column.label }}</span>\n <span class=\"kf-data-table__card-meta-value\">\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </span>\n </div>\n }\n </div>\n }\n\n @if (cardActionColumns().length) {\n <div class=\"kf-data-table__card-actions\">\n @for (column of cardActionColumns(); track column.key) {\n <ng-container\n [ngTemplateOutlet]=\"cardCell\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (!loading() && rows().length > 0) {\n <div class=\"kf-data-table__footer\">\n @if (effectivePageSize()) {\n <kf-pagination\n [page]=\"page()\"\n [pages]=\"totalPages()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [pageSize]=\"effectivePageSize()\"\n (pageChange)=\"goToPage($event)\"\n (pageSizeChange)=\"setPageSize($event)\"\n >\n {{ paginationSummary() }}\n </kf-pagination>\n }\n <ng-content select=\"[footer]\" />\n </div>\n }\n</div>\n\n<!-- Celda de la vista tabla \u2014 compartida por la celda normal y la `truncate`.\n Una columna `interactive` envuelve su contenido en un bot\u00F3n real. -->\n<ng-template #tableCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"tableCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una celda de la vista tabla (template del consumidor,\n thumbnail o texto). -->\n<ng-template #tableCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else if (column.cardRole === 'media') {\n <img class=\"kf-data-table__thumb\" [src]=\"cellValue(row, column)\" alt=\"\" />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n\n<!-- Zona de la tarjeta: igual que la celda de tabla, con bot\u00F3n si la columna es `interactive`. -->\n<ng-template #cardCell let-row let-column=\"column\" let-i=\"index\">\n @if (column.interactive) {\n <button\n type=\"button\"\n class=\"kf-data-table__cell-button\"\n [attr.aria-label]=\"cellLabel(row, column, i)\"\n (click)=\"onCellClick(row, column, i)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n </button>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"cardCellContent\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column, index: i }\"\n />\n }\n</ng-template>\n\n<!-- Contenido de una zona de la tarjeta: template del consumidor o el valor como texto. -->\n<ng-template #cardCellContent let-row let-column=\"column\" let-i=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: row, index: i, column: column }\"\n />\n } @else {\n {{ cellValue(row, column) }}\n }\n</ng-template>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block;font-family:var(--kf-font-body)}.kf-data-table{background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);overflow:hidden}.kf-data-table--flush{border:none;box-shadow:none;border-radius:0}.kf-data-table__toolbar{display:flex;justify-content:flex-end;padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-data-table__scroll{position:relative;overflow-x:auto}.kf-data-table__scroll--weighted{container-type:inline-size}.kf-data-table__scroll:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__scroll:before,.kf-data-table__scroll:after{content:\"\";position:absolute;top:0;bottom:0;width:24px;pointer-events:none;opacity:0;transition:opacity .14s}.kf-data-table__scroll:before{left:0;background:linear-gradient(to right,var(--kf-color-white),transparent)}.kf-data-table__scroll:after{right:0;background:linear-gradient(to left,var(--kf-color-white),transparent)}.kf-data-table__scroll--fade-left:before{opacity:1}.kf-data-table__scroll--fade-right:after{opacity:1}.kf-data-table__table{width:100%;border-collapse:collapse;text-align:left}.kf-data-table__table th,.kf-data-table__table td{padding:var(--kf-space-sm) var(--kf-space-md);border-bottom:1px solid var(--kf-color-border);white-space:nowrap}.kf-data-table--compact .kf-data-table__table th,.kf-data-table--compact .kf-data-table__table td{padding:var(--kf-space-xs) var(--kf-space-md)}.kf-data-table__table th{background:var(--kf-color-surface)}.kf-data-table__table td{color:var(--kf-color-text)}.kf-data-table__table tbody tr:last-child td{border-bottom:none}.kf-data-table__row{transition:background .14s}.kf-data-table__row--interactive{cursor:pointer}.kf-data-table__row--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__row--interactive:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-data-table__row--current{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__cell--center{text-align:center}.kf-data-table__table .kf-data-table__cell--truncate{max-width:0}.kf-data-table__truncate{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-data-table__table td.kf-data-table__cell--mono{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm)}.kf-data-table__table td.kf-data-table__cell--emphasis{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-data-table__cell-button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);max-width:100%;min-width:32px;min-height:32px;padding:var(--kf-space-xs);border:0;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;text-align:inherit;cursor:pointer}.kf-data-table__cell-button:hover{box-shadow:inset 0 0 0 1px var(--kf-color-border-strong)}.kf-data-table__cell-button:focus-visible{outline:2px solid transparent;outline-offset:-2px;box-shadow:inset 0 0 0 2px var(--kf-color-primary)}@media(max-width:640px),(pointer:coarse){.kf-data-table__cell-button{min-width:44px;min-height:44px}}.kf-data-table__cell--sticky{background:var(--kf-color-white)}.kf-data-table__table th.kf-data-table__cell--sticky{background:var(--kf-color-surface);z-index:2}.kf-data-table__cell--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.kf-data-table__cell--sortable:hover{color:var(--kf-color-ink)}.kf-data-table__table th.kf-data-table__cell--sort-active{color:var(--kf-color-primary);font-weight:700}.kf-data-table__th-content{display:inline-flex;align-items:center;gap:var(--kf-space-xs)}.kf-data-table__sort-icon{transition:opacity .14s}.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon{opacity:.5}.kf-data-table__sort-icon--active,.kf-data-table__cell--sortable:hover .kf-data-table__sort-icon--active{opacity:1}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--kf-data-table-card-min, 220px),100%),1fr));grid-auto-rows:1fr;gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{position:relative;display:flex;flex-direction:column;min-width:0;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);overflow:hidden;transition:border-color .14s,box-shadow .14s,background .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:hover{background:var(--kf-color-primary-soft)}.kf-data-table__card--interactive:focus-visible{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px var(--kf-color-primary)}.kf-data-table__card--current{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;padding:var(--kf-space-md) var(--kf-space-md) var(--kf-space-sm)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex:1;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-title{flex:0 1 auto;min-width:0;font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-title--mono{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);white-space:nowrap}.kf-data-table__card-statuses{display:flex;flex:0 1 auto;flex-wrap:nowrap;justify-content:flex-end;gap:var(--kf-space-xs);min-width:0;overflow:hidden}.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0;padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-lead{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--kf-data-table-card-clamp, 2);line-clamp:var(--kf-data-table-card-clamp, 2);min-width:0;overflow:hidden;overflow-wrap:anywhere;color:var(--kf-color-ink);font-weight:500;line-height:1.35}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm);min-width:0}.kf-data-table__card-label{flex:none}.kf-data-table__card-value{min-width:0;color:var(--kf-color-text);text-align:right;overflow-wrap:anywhere}.kf-data-table__card-value--emphasis,.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-weight:700;color:var(--kf-color-ink);font-variant-numeric:tabular-nums}.kf-data-table__card-meta{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:var(--kf-space-xs) var(--kf-space-md);margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__card-meta-item{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-data-table__card-meta-item--end{margin-left:auto;align-items:flex-end;text-align:right}.kf-data-table__card-meta-value{color:var(--kf-color-text);font-variant-numeric:tabular-nums}.kf-data-table__card-meta-item--emphasis .kf-data-table__card-meta-value{font-size:var(--kf-font-size-body)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__card-meta~.kf-data-table__card-actions{margin-top:0}.kf-data-table__thumb{width:32px;height:32px}@media(prefers-reduced-motion:reduce){.kf-data-table__card,.kf-data-table__row{transition:none}}.kf-data-table__sk-cell{display:flex;align-items:center;justify-content:flex-start;min-height:1lh}.kf-data-table__sk-cell--center{justify-content:center}.kf-data-table__sk-cell--end{justify-content:flex-end}.kf-data-table__sk-bar{flex:none}.kf-data-table__sk-media{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs)}.kf-data-table__sk-pill{display:block;width:4.5rem;height:1.25rem;border-radius:var(--kf-radius-pill)}.kf-data-table__sk-value{flex:none;width:40%}.kf-data-table__sk-lead{line-height:1.35}.kf-data-table__sk-action{display:block;width:5rem;height:2rem;border-radius:var(--kf-radius-sm)}\n"] }]
|
|
3995
|
+
}], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], skeletonRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonRows", required: false }] }], scrollEl: [{ type: i0.ViewChild, args: ['scroll', { isSignal: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], cardable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardable", required: false }] }], defaultView: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultView", required: false }] }], showViewToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showViewToggle", required: false }] }], cardMinWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardMinWidth", required: false }] }], render: [{ type: i0.Input, args: [{ isSignal: true, alias: "render", required: false }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], rowClickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClickable", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], cellClick: [{ type: i0.Output, args: ["cellClick"] }], cellAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], currentRowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentRowKey", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }], keepPageOnRowsChange: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepPageOnRowsChange", required: false }] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], itemLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabel", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }, { type: i0.Output, args: ["viewChange"] }] } });
|
|
3999
3996
|
|
|
4000
3997
|
/**
|
|
4001
|
-
*
|
|
4002
|
-
*
|
|
4003
|
-
*
|
|
4004
|
-
* se fuerza `cards`. Centraliza el patrón `matchMediaSignal` +
|
|
4005
|
-
* `computed<DataTableView>` que cada tabla del portal repetía igual.
|
|
3998
|
+
* Acción de bajo énfasis con apariencia de texto — "¿Olvidaste tu
|
|
3999
|
+
* contraseña?", "Volver a iniciar sesión", o un link real de navegación
|
|
4000
|
+
* como "Ir a pagos →".
|
|
4006
4001
|
*
|
|
4007
|
-
*
|
|
4008
|
-
*
|
|
4009
|
-
*
|
|
4010
|
-
|
|
4011
|
-
function dataTableViewSignal(query = '(max-width: 900px)') {
|
|
4012
|
-
const isMobile = matchMediaSignal(query);
|
|
4013
|
-
return computed(() => (isMobile() ? 'cards' : 'table'));
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
/**
|
|
4017
|
-
* Separador visual simple entre bloques de contenido. `orientation`
|
|
4018
|
-
* controla si es una línea horizontal (para separar secciones apiladas) o
|
|
4019
|
-
* vertical (para separar elementos en una fila, ej. acciones en una toolbar,
|
|
4020
|
-
* o las columnas de metadata en `kf-company-selector`).
|
|
4002
|
+
* Sin `href` (default) es un `<button type="button">`: dispara una acción
|
|
4003
|
+
* en la misma pantalla, vía `(click)`. Con `href`, es un `<a>` real: navega
|
|
4004
|
+
* a otra URL. `showArrow` agrega un ícono `chevron-right` al final — para
|
|
4005
|
+
* el patrón "texto + flecha" de un link que lleva a otro lado.
|
|
4021
4006
|
*
|
|
4022
|
-
*
|
|
4023
|
-
*
|
|
4024
|
-
*
|
|
4025
|
-
* none` evita que el propio divisor se achique a 0 dentro de ese contenedor
|
|
4026
|
-
* flex cuando hay contenido hermano compitiendo por espacio.
|
|
4007
|
+
* `variant="default"` (color primario, subrayado en hover) es para la
|
|
4008
|
+
* acción de mayor jerarquía relativa de la pantalla; `variant="muted"`
|
|
4009
|
+
* (gris que pasa a primario en hover) es para acciones de retroceso/salida.
|
|
4027
4010
|
*
|
|
4028
|
-
* <kf-
|
|
4029
|
-
* <kf-
|
|
4011
|
+
* <kf-link (click)="onForgotPassword()">¿Olvidaste tu contraseña?</kf-link>
|
|
4012
|
+
* <kf-link variant="muted" (click)="onBack()">← Volver a iniciar sesión</kf-link>
|
|
4013
|
+
* <kf-link href="/pagos" showArrow>Ir a pagos</kf-link>
|
|
4014
|
+
*/
|
|
4015
|
+
class Link {
|
|
4016
|
+
variant = input('default', /* @ts-ignore */
|
|
4017
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
4018
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4019
|
+
/** Si se pasa, renderiza un `<a>` real en vez de `<button>` — navega en vez de disparar `(click)`. */
|
|
4020
|
+
href = input(/* @ts-ignore */
|
|
4021
|
+
...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
|
|
4022
|
+
/** Ícono `chevron-right` al final — para el patrón "texto + flecha" de un link que lleva a otro lado. */
|
|
4023
|
+
showArrow = input(false, { ...(ngDevMode ? { debugName: "showArrow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4024
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4025
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Link, isStandalone: true, selector: "kf-link", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, showArrow: { classPropertyName: "showArrow", publicName: "showArrow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
4026
|
+
}
|
|
4027
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, decorators: [{
|
|
4028
|
+
type: Component,
|
|
4029
|
+
args: [{ selector: 'kf-link', imports: [Icon, NgTemplateOutlet], template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"] }]
|
|
4030
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], showArrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArrow", required: false }] }] } });
|
|
4031
|
+
|
|
4032
|
+
/**
|
|
4033
|
+
* Fila de un documento descargable: chip de ícono coloreado por tipo,
|
|
4034
|
+
* título, metadata y la acción de descarga al final. Sin `href` rinde
|
|
4035
|
+
* `pendingLabel` en vez de un link roto. El contenido proyectado cae entre
|
|
4036
|
+
* la metadata y la acción, como slot para un `kf-badge` de estado.
|
|
4037
|
+
*
|
|
4038
|
+
* `loading` (default `false`): dibuja el esqueleto de la fila — chip, título,
|
|
4039
|
+
* metadata y acción como bloques del alto real de cada texto — con el mismo
|
|
4040
|
+
* cascarón, así que al llegar los datos no hay salto de layout. Pasa los
|
|
4041
|
+
* mismos inputs que usarás cargada (`meta` incluido, para reservar su línea).
|
|
4042
|
+
* Marca el host con `aria-busy="true"` y oculta el contenido proyectado.
|
|
4043
|
+
*
|
|
4044
|
+
* <kf-document-row title="Factura" meta="PDF · 120 KB" [loading]="isLoading()" />
|
|
4045
|
+
*/
|
|
4046
|
+
class DocumentRow {
|
|
4047
|
+
icon = input('file', /* @ts-ignore */
|
|
4048
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
4049
|
+
tone = input('neutral', /* @ts-ignore */
|
|
4050
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
4051
|
+
/** Qué documento es. El nombre humano del tipo, no el del archivo. */
|
|
4052
|
+
title = input.required(/* @ts-ignore */
|
|
4053
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
4054
|
+
/** Línea secundaria — archivo, fecha, tamaño. El consumidor la arma ya
|
|
4055
|
+
* formateada: la librería no decide en qué unidad se lee un tamaño ni en
|
|
4056
|
+
* qué formato una fecha. */
|
|
4057
|
+
meta = input(/* @ts-ignore */
|
|
4058
|
+
...(ngDevMode ? [undefined, { debugName: "meta" }] : /* istanbul ignore next */ []));
|
|
4059
|
+
/** Ausente rinde `pendingLabel` en vez del link de descarga. */
|
|
4060
|
+
href = input(/* @ts-ignore */
|
|
4061
|
+
...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
|
|
4062
|
+
downloadLabel = input('Descargar', /* @ts-ignore */
|
|
4063
|
+
...(ngDevMode ? [{ debugName: "downloadLabel" }] : /* istanbul ignore next */ []));
|
|
4064
|
+
/** Qué se dice cuando no hay `href`. */
|
|
4065
|
+
pendingLabel = input('En generación', /* @ts-ignore */
|
|
4066
|
+
...(ngDevMode ? [{ debugName: "pendingLabel" }] : /* istanbul ignore next */ []));
|
|
4067
|
+
/** Dibuja el esqueleto en lugar del contenido, conservando las dimensiones. */
|
|
4068
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4069
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4070
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DocumentRow, isStandalone: true, selector: "kf-document-row", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, downloadLabel: { classPropertyName: "downloadLabel", publicName: "downloadLabel", isSignal: true, isRequired: false, transformFunction: null }, pendingLabel: { classPropertyName: "pendingLabel", publicName: "pendingLabel", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "\"kf-document-row--\" + tone()", "class.kf-document-row--loading": "loading()", "attr.aria-busy": "loading() ? 'true' : null" } }, ngImport: i0, template: "@if (loading()) {\n <span class=\"kf-document-row__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n\n <span class=\"kf-document-row__body\" aria-hidden=\"true\">\n <span class=\"kf-document-row__title kf-bone\" style=\"--kf-bone-width: 55%\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text kf-bone\" style=\"--kf-bone-width: 35%\">{{\n line\n }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions kf-document-row__actions--loading\" aria-hidden=\"true\">\n <ng-content />\n <span class=\"kf-document-row__pending kf-caption-text kf-bone\" style=\"--kf-bone-width: 5rem\">{{\n href() ? downloadLabel() : pendingLabel()\n }}</span>\n </span>\n} @else {\n <span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n </span>\n\n <span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n </span>\n}\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}.kf-document-row__actions--loading>:not(.kf-bone){visibility:hidden}.kf-document-row__icon.kf-bone-fill{border-radius:var(--kf-radius-xs)}:host(.kf-document-row--loading) .kf-document-row__icon{background:var(--kf-color-border);animation:none}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
|
|
4071
|
+
}
|
|
4072
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, decorators: [{
|
|
4073
|
+
type: Component,
|
|
4074
|
+
args: [{ selector: 'kf-document-row', imports: [Icon, Link], host: {
|
|
4075
|
+
'[class]': '"kf-document-row--" + tone()',
|
|
4076
|
+
'[class.kf-document-row--loading]': 'loading()',
|
|
4077
|
+
'[attr.aria-busy]': "loading() ? 'true' : null",
|
|
4078
|
+
}, template: "@if (loading()) {\n <span class=\"kf-document-row__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n\n <span class=\"kf-document-row__body\" aria-hidden=\"true\">\n <span class=\"kf-document-row__title kf-bone\" style=\"--kf-bone-width: 55%\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text kf-bone\" style=\"--kf-bone-width: 35%\">{{\n line\n }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions kf-document-row__actions--loading\" aria-hidden=\"true\">\n <ng-content />\n <span class=\"kf-document-row__pending kf-caption-text kf-bone\" style=\"--kf-bone-width: 5rem\">{{\n href() ? downloadLabel() : pendingLabel()\n }}</span>\n </span>\n} @else {\n <span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n </span>\n\n <span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n </span>\n\n <span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n </span>\n}\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}.kf-document-row__actions--loading>:not(.kf-bone){visibility:hidden}.kf-document-row__icon.kf-bone-fill{border-radius:var(--kf-radius-xs)}:host(.kf-document-row--loading) .kf-document-row__icon{background:var(--kf-color-border);animation:none}\n"] }]
|
|
4079
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], downloadLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadLabel", required: false }] }], pendingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingLabel", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
|
|
4080
|
+
|
|
4081
|
+
/**
|
|
4082
|
+
* Barra de progreso de un solo valor (0-100%) con color por severidad —
|
|
4083
|
+
* para un porcentaje simple, a diferencia de `kf-bucket-progress`, que
|
|
4084
|
+
* dibuja varias categorías proporcionales en la misma barra.
|
|
4085
|
+
*
|
|
4086
|
+
* <kf-progress [value]="80" tone="ok" label="80%" />
|
|
4087
|
+
* <kf-progress [value]="ratio()" [tone]="severityTone()" />
|
|
4088
|
+
*
|
|
4089
|
+
* `value` se clampea a [0, 100] — un consumidor que calcule un ratio fuera
|
|
4090
|
+
* de rango (ej. tiempo transcurrido sobre un SLA ya vencido) no rompe el
|
|
4091
|
+
* ancho de la barra. `size` cambia el grosor de la pista (`sm` 4px, `md` 8px,
|
|
4092
|
+
* `lg` 12px; sin él, 4px como siempre); el `role="progressbar"` y sus valores
|
|
4093
|
+
* accesibles no cambian. `label` es texto libre opcional sobre la barra (ej.
|
|
4094
|
+
* "80%"); sin él, no se renderiza ningún texto, solo la barra.
|
|
4095
|
+
*/
|
|
4096
|
+
class Progress {
|
|
4097
|
+
value = input.required(/* @ts-ignore */
|
|
4098
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
4099
|
+
tone = input('neutral', /* @ts-ignore */
|
|
4100
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
4101
|
+
/** Grosor de la pista. Default `sm` (4px). */
|
|
4102
|
+
size = input('sm', /* @ts-ignore */
|
|
4103
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
4104
|
+
label = input(/* @ts-ignore */
|
|
4105
|
+
...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4106
|
+
/** Nombre accesible de la barra (`aria-label`). Sin él, el `progressbar`
|
|
4107
|
+
* queda sin nombre: dalo siempre que el contexto no lo nombre ya (por
|
|
4108
|
+
* ejemplo con un `label` visible asociado). */
|
|
4109
|
+
ariaLabel = input(/* @ts-ignore */
|
|
4110
|
+
...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
4111
|
+
clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100), /* @ts-ignore */
|
|
4112
|
+
...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
|
|
4113
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4114
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Progress, isStandalone: true, selector: "kf-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track kf-progress__track--{{ size() }}\"\n role=\"progressbar\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__track--md{height:8px}.kf-progress__track--lg{height:12px}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}.kf-progress--purple{color:var(--kf-color-purple)}.kf-progress__fill.kf-progress--purple{background:var(--kf-color-purple)}\n"] });
|
|
4115
|
+
}
|
|
4116
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, decorators: [{
|
|
4117
|
+
type: Component,
|
|
4118
|
+
args: [{ selector: 'kf-progress', template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track kf-progress__track--{{ size() }}\"\n role=\"progressbar\"\n [attr.aria-label]=\"ariaLabel() ?? null\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__track--md{height:8px}.kf-progress__track--lg{height:12px}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}.kf-progress--purple{color:var(--kf-color-purple)}.kf-progress__fill.kf-progress--purple{background:var(--kf-color-purple)}\n"] }]
|
|
4119
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
4120
|
+
|
|
4121
|
+
/** Tono de `kf-progress` que acompaña a cada tono de la card. */
|
|
4122
|
+
const BAR_TONE = {
|
|
4123
|
+
primary: 'primary',
|
|
4124
|
+
success: 'ok',
|
|
4125
|
+
error: 'error',
|
|
4126
|
+
warn: 'warn',
|
|
4127
|
+
purple: 'primary',
|
|
4128
|
+
info: 'primary',
|
|
4129
|
+
neutral: 'primary',
|
|
4130
|
+
};
|
|
4131
|
+
/**
|
|
4132
|
+
* Card de estadística horizontal: ícono circular grande a la izquierda,
|
|
4133
|
+
* valor + label + nota apilados al centro, chevron opcional al final si es
|
|
4134
|
+
* clickeable. Distinta de `kf-metric-card` (layout vertical, label arriba +
|
|
4135
|
+
* ícono a la derecha) — este es el patrón real del dashboard de OTD ("Bonos
|
|
4136
|
+
* otorgados", "Cortesías otorgadas"), no una variante de la misma card.
|
|
4137
|
+
*
|
|
4138
|
+
* <kf-stat-card icon="gift" tone="purple" value="4" label="Bonos otorgados" note="$15,000.00 en beneficios" (cardClick)="onNavigate('beneficios')" />
|
|
4139
|
+
*
|
|
4140
|
+
* Extensiones opcionales (la card sin ellas se ve y se comporta como siempre):
|
|
4141
|
+
* - `tone`: además de `primary|success|error|purple`, `warn`, `info` y `neutral`.
|
|
4142
|
+
* - `bar` (0-100): barra de progreso accesible (`kf-progress`, `role="progressbar"`
|
|
4143
|
+
* con `aria-valuenow`/`min`/`max` y el `label` como nombre) bajo la nota. Se
|
|
4144
|
+
* acota a [0, 100]; `null`/`undefined` no la dibuja. Es un refuerzo visual:
|
|
4145
|
+
* el valor exacto conviene darlo también en texto (`hint`), porque el relleno
|
|
4146
|
+
* de algunos tonos de `kf-progress` tiene poco contraste con su pista.
|
|
4147
|
+
* - `hint`: texto secundario bajo la barra (ej. "12 de 20 completadas").
|
|
4148
|
+
* - `pressed` (`boolean | null`): con `clickable`, la card es un botón de
|
|
4149
|
+
* alternancia (`aria-pressed`), útil como filtro. `null` (default) = no
|
|
4150
|
+
* aplica y no emite el atributo. Sin `clickable` no tiene efecto.
|
|
4151
|
+
*
|
|
4152
|
+
* <kf-stat-card icon="clock" tone="warn" value="7" label="Por vencer" [bar]="35" hint="7 de 20"
|
|
4153
|
+
* clickable [pressed]="filter() === 'due'" (cardClick)="toggle('due')" />
|
|
4154
|
+
*
|
|
4155
|
+
* `loading` (default `false`): mientras es `true` la card dibuja su esqueleto
|
|
4156
|
+
* en vez del contenido, con el MISMO cascarón (borde, padding, ícono de 50px)
|
|
4157
|
+
* y líneas del alto real de cada texto — valor, etiqueta y, si se pasaron,
|
|
4158
|
+
* nota, barra y `hint` —, así que al llegar los datos no hay salto de
|
|
4159
|
+
* layout. Pasa los mismos inputs que usarás cargada para que el esqueleto
|
|
4160
|
+
* reserve las mismas líneas. Marca la card con `aria-busy="true"`, no es
|
|
4161
|
+
* clicable ni expone su texto a lectores de pantalla mientras carga.
|
|
4162
|
+
*
|
|
4163
|
+
* <kf-stat-card icon="clock" value="7" label="Por vencer" note="Esta semana" [loading]="isLoading()" />
|
|
4164
|
+
*/
|
|
4165
|
+
class StatCard {
|
|
4166
|
+
icon = input.required(/* @ts-ignore */
|
|
4167
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
4168
|
+
tone = input('primary', /* @ts-ignore */
|
|
4169
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
4170
|
+
value = input.required(/* @ts-ignore */
|
|
4171
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
4172
|
+
label = input.required(/* @ts-ignore */
|
|
4173
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4174
|
+
note = input(/* @ts-ignore */
|
|
4175
|
+
...(ngDevMode ? [undefined, { debugName: "note" }] : /* istanbul ignore next */ []));
|
|
4176
|
+
/** Avance 0-100 (barra de progreso accesible); `null`/`undefined` = sin barra. */
|
|
4177
|
+
bar = input(undefined, /* @ts-ignore */
|
|
4178
|
+
...(ngDevMode ? [{ debugName: "bar" }] : /* istanbul ignore next */ []));
|
|
4179
|
+
/** Texto secundario bajo la barra (o bajo la nota si no hay barra). */
|
|
4180
|
+
hint = input(/* @ts-ignore */
|
|
4181
|
+
...(ngDevMode ? [undefined, { debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
4182
|
+
/**
|
|
4183
|
+
* Estado de alternancia (`aria-pressed`) cuando la card es `clickable`.
|
|
4184
|
+
* `null` (default) = no aplica: el atributo no se emite.
|
|
4185
|
+
*/
|
|
4186
|
+
pressed = input(null, /* @ts-ignore */
|
|
4187
|
+
...(ngDevMode ? [{ debugName: "pressed" }] : /* istanbul ignore next */ []));
|
|
4188
|
+
/** Dibuja el esqueleto en lugar del contenido, conservando las dimensiones. */
|
|
4189
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4190
|
+
/** Si se escucha `cardClick`, la card se renderiza como botón clickeable con chevron. */
|
|
4191
|
+
clickable = input(false, { ...(ngDevMode ? { debugName: "clickable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4192
|
+
cardClick = output();
|
|
4193
|
+
barTone = computed(() => BAR_TONE[this.tone()], /* @ts-ignore */
|
|
4194
|
+
...(ngDevMode ? [{ debugName: "barTone" }] : /* istanbul ignore next */ []));
|
|
4195
|
+
hasBar = computed(() => {
|
|
4196
|
+
const value = this.bar();
|
|
4197
|
+
return value !== null && value !== undefined && Number.isFinite(value);
|
|
4198
|
+
}, /* @ts-ignore */
|
|
4199
|
+
...(ngDevMode ? [{ debugName: "hasBar" }] : /* istanbul ignore next */ []));
|
|
4200
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4201
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: StatCard, isStandalone: true, selector: "kf-stat-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, bar: { classPropertyName: "bar", publicName: "bar", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, pressed: { classPropertyName: "pressed", publicName: "pressed", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "@if (loading()) {\n <div class=\"kf-stat-card\" aria-busy=\"true\">\n <span\n class=\"kf-stat-card__icon kf-stat-card__icon--bone kf-bone-fill\"\n aria-hidden=\"true\"\n ></span>\n <span class=\"kf-stat-card__body\" aria-hidden=\"true\">\n <span class=\"kf-stat-card__value kf-bone\" style=\"--kf-bone-width: 40%\">{{ value() }}</span>\n <span class=\"kf-stat-card__label kf-bone\" style=\"--kf-bone-width: 70%\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note kf-bone\" style=\"--kf-bone-width: 55%\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <span class=\"kf-stat-card__bar\">\n <span class=\"kf-stat-card__bar-bone kf-bone-fill\"></span>\n </span>\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint kf-bone\" style=\"--kf-bone-width: 45%\">{{ hint() }}</span>\n }\n </span>\n </div>\n} @else if (clickable()) {\n <button\n type=\"button\"\n class=\"kf-stat-card kf-stat-card--clickable\"\n [class.kf-stat-card--pressed]=\"pressed() === true\"\n [attr.aria-pressed]=\"pressed()\"\n (click)=\"cardClick.emit()\"\n >\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <kf-progress\n class=\"kf-stat-card__bar\"\n [value]=\"bar()!\"\n [tone]=\"barTone()\"\n [ariaLabel]=\"label()\"\n />\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint\">{{ hint() }}</span>\n }\n </span>\n</ng-template>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:2px solid transparent;box-shadow:var(--kf-shadow-focus)}.kf-stat-card--pressed{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 1px var(--kf-color-primary)}.kf-stat-card--pressed:focus-visible{box-shadow:inset 0 0 0 1px var(--kf-color-primary),var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__icon--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}.kf-stat-card__icon--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}.kf-stat-card__icon--neutral{background:var(--kf-color-gray-100);color:var(--kf-color-text)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__bar{padding-block-start:var(--kf-space-xs)}.kf-stat-card__hint{font-size:.78rem;color:var(--kf-color-text)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}.kf-stat-card__icon--bone{border-radius:var(--kf-radius-md)}.kf-stat-card__bar-bone{display:block;height:4px;border-radius:var(--kf-radius-pill)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Progress, selector: "kf-progress", inputs: ["value", "tone", "size", "label", "ariaLabel"] }] });
|
|
4202
|
+
}
|
|
4203
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, decorators: [{
|
|
4204
|
+
type: Component,
|
|
4205
|
+
args: [{ selector: 'kf-stat-card', imports: [Icon, NgTemplateOutlet, Progress], template: "@if (loading()) {\n <div class=\"kf-stat-card\" aria-busy=\"true\">\n <span\n class=\"kf-stat-card__icon kf-stat-card__icon--bone kf-bone-fill\"\n aria-hidden=\"true\"\n ></span>\n <span class=\"kf-stat-card__body\" aria-hidden=\"true\">\n <span class=\"kf-stat-card__value kf-bone\" style=\"--kf-bone-width: 40%\">{{ value() }}</span>\n <span class=\"kf-stat-card__label kf-bone\" style=\"--kf-bone-width: 70%\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note kf-bone\" style=\"--kf-bone-width: 55%\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <span class=\"kf-stat-card__bar\">\n <span class=\"kf-stat-card__bar-bone kf-bone-fill\"></span>\n </span>\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint kf-bone\" style=\"--kf-bone-width: 45%\">{{ hint() }}</span>\n }\n </span>\n </div>\n} @else if (clickable()) {\n <button\n type=\"button\"\n class=\"kf-stat-card kf-stat-card--clickable\"\n [class.kf-stat-card--pressed]=\"pressed() === true\"\n [attr.aria-pressed]=\"pressed()\"\n (click)=\"cardClick.emit()\"\n >\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n @if (hasBar()) {\n <kf-progress\n class=\"kf-stat-card__bar\"\n [value]=\"bar()!\"\n [tone]=\"barTone()\"\n [ariaLabel]=\"label()\"\n />\n }\n @if (hint()) {\n <span class=\"kf-stat-card__hint\">{{ hint() }}</span>\n }\n </span>\n</ng-template>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:2px solid transparent;box-shadow:var(--kf-shadow-focus)}.kf-stat-card--pressed{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft);box-shadow:inset 0 0 0 1px var(--kf-color-primary)}.kf-stat-card--pressed:focus-visible{box-shadow:inset 0 0 0 1px var(--kf-color-primary),var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__icon--warn{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}.kf-stat-card__icon--info{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}.kf-stat-card__icon--neutral{background:var(--kf-color-gray-100);color:var(--kf-color-text)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__bar{padding-block-start:var(--kf-space-xs)}.kf-stat-card__hint{font-size:.78rem;color:var(--kf-color-text)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}.kf-stat-card__icon--bone{border-radius:var(--kf-radius-md)}.kf-stat-card__bar-bone{display:block;height:4px;border-radius:var(--kf-radius-pill)}\n"] }]
|
|
4206
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], bar: [{ type: i0.Input, args: [{ isSignal: true, alias: "bar", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], pressed: [{ type: i0.Input, args: [{ isSignal: true, alias: "pressed", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
|
|
4207
|
+
|
|
4208
|
+
/** Cantidad de elementos por defecto de cada variante (`count` sin valor). */
|
|
4209
|
+
const DEFAULT_COUNT = {
|
|
4210
|
+
'kpi-grid': 4,
|
|
4211
|
+
'card-grid': 6,
|
|
4212
|
+
form: 4,
|
|
4213
|
+
list: 4,
|
|
4214
|
+
table: 5,
|
|
4215
|
+
timeline: 4,
|
|
4216
|
+
'detail-header': 1,
|
|
4217
|
+
};
|
|
4218
|
+
/** Tope de elementos: un `count` mayor se acota, no tiene sentido pintar más. */
|
|
4219
|
+
const SKELETON_PRESET_MAX_COUNT = 12;
|
|
4220
|
+
/**
|
|
4221
|
+
* Esqueletos listos para usar que reproducen la FORMA del contenido real, para
|
|
4222
|
+
* que al llegar los datos nada se mueva. Cada variante se arma con la misma
|
|
4223
|
+
* geometría que su equivalente cargado (mismos cascarones, alturas, radios y
|
|
4224
|
+
* gaps, todo por tokens):
|
|
4225
|
+
*
|
|
4226
|
+
* | `variant` | Equivalente cargado | `count` | `columns` |
|
|
4227
|
+
* | ---------------- | ---------------------------- | -------------- | ------------------ |
|
|
4228
|
+
* | `kpi-grid` | `kf-stat-card` | tarjetas (4) | columnas de la grilla |
|
|
4229
|
+
* | `card-grid` | `kf-card` | tarjetas (6) | columnas de la grilla |
|
|
4230
|
+
* | `form` | `kf-input` con label | campos (4) | `1` o `2` |
|
|
4231
|
+
* | `list` | `kf-document-row` | filas (4) | — |
|
|
4232
|
+
* | `table` | `kf-data-table` | filas (5) | columnas (4) |
|
|
4233
|
+
* | `timeline` | `kf-milestone-timeline` | etapas (4) | — |
|
|
4234
|
+
* | `detail-header` | `kf-page-header` con ícono | — | — |
|
|
4235
|
+
*
|
|
4236
|
+
* Sin `columns`, las grillas (`kpi-grid`, `card-grid`) se reparten solas con un
|
|
4237
|
+
* ancho mínimo por tarjeta. `count` se acota a 12.
|
|
4238
|
+
*
|
|
4239
|
+
* El preset es decorativo (`aria-hidden` por dentro) y marca su contenedor con
|
|
4240
|
+
* `aria-busy="true"`. Con `label` agrega un texto visualmente oculto con
|
|
4241
|
+
* `role="status"` que lo anuncia a lectores de pantalla; dentro de
|
|
4242
|
+
* `kf-content-state` no hace falta (ya lo anuncia él).
|
|
4243
|
+
*
|
|
4244
|
+
* <kf-skeleton-preset variant="kpi-grid" [count]="4" />
|
|
4245
|
+
* <kf-skeleton-preset variant="form" [count]="3" [columns]="2" label="Cargando formulario" />
|
|
4246
|
+
* <kf-content-state [state]="state()" loadingPreset="table">…</kf-content-state>
|
|
4247
|
+
*/
|
|
4248
|
+
class SkeletonPreset {
|
|
4249
|
+
variant = input('list', /* @ts-ignore */
|
|
4250
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
4251
|
+
/** Cantidad de elementos. Sin valor, el de la variante. Máximo 12. */
|
|
4252
|
+
count = input(undefined, /* @ts-ignore */
|
|
4253
|
+
...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
4254
|
+
/** Columnas: de la grilla (`kpi-grid`, `card-grid`), del formulario (`1`|`2`) o de la tabla. */
|
|
4255
|
+
columns = input(undefined, /* @ts-ignore */
|
|
4256
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
4257
|
+
/** Texto accesible (visualmente oculto, `role="status"`). Vacío, no se emite. */
|
|
4258
|
+
label = input('', /* @ts-ignore */
|
|
4259
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4260
|
+
total = computed(() => {
|
|
4261
|
+
const requested = this.count() ?? DEFAULT_COUNT[this.variant()];
|
|
4262
|
+
return Math.min(Math.max(Math.floor(requested) || 0, 1), SKELETON_PRESET_MAX_COUNT);
|
|
4263
|
+
}, /* @ts-ignore */
|
|
4264
|
+
...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
|
|
4265
|
+
items = computed(() => Array.from({ length: this.total() }, (_, i) => i), /* @ts-ignore */
|
|
4266
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
4267
|
+
/** Columnas de la tabla sintética (encabezados en blanco: lo real aún no llegó). */
|
|
4268
|
+
tableColumns = computed(() => Array.from({ length: Math.min(Math.max(this.columns() ?? 4, 1), 8) }, (_, i) => ({
|
|
4269
|
+
key: `c${i}`,
|
|
4270
|
+
label: ' ',
|
|
4271
|
+
})), /* @ts-ignore */
|
|
4272
|
+
...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
|
|
4273
|
+
formColumns = computed(() => (this.columns() === 2 ? 2 : 1), /* @ts-ignore */
|
|
4274
|
+
...(ngDevMode ? [{ debugName: "formColumns" }] : /* istanbul ignore next */ []));
|
|
4275
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SkeletonPreset, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4276
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SkeletonPreset, isStandalone: true, selector: "kf-skeleton-preset", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n [class]=\"'kf-skeleton-preset kf-skeleton-preset--' + variant()\"\n aria-busy=\"true\"\n [style.--kf-skeleton-preset-columns]=\"columns() ?? null\"\n>\n @if (label()) {\n <span class=\"kf-sr-only\" role=\"status\">{{ label() }}</span>\n }\n @switch (variant()) {\n @case ('kpi-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-stat-card [loading]=\"true\" icon=\"bell\" value=\"0\" label=\"Etiqueta\" note=\"Nota\" />\n }\n </div>\n }\n @case ('card-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-card gap=\"sm\" aria-hidden=\"true\">\n <kf-skeleton variant=\"rect\" height=\"7.5rem\" />\n <kf-skeleton width=\"60%\" height=\"1.25rem\" />\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n </kf-card>\n }\n </div>\n }\n @case ('form') {\n <div\n class=\"kf-skeleton-preset__form\"\n [class.kf-skeleton-preset__form--two]=\"formColumns() === 2\"\n aria-hidden=\"true\"\n >\n @for (i of items(); track i) {\n <div class=\"kf-skeleton-preset__field\">\n <span\n class=\"kf-skeleton-preset__label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Etiqueta\n </span>\n <span class=\"kf-skeleton-preset__control\">\n <span class=\"kf-skeleton-preset__value kf-bone\" style=\"--kf-bone-width: 55%\"\n >Valor</span\n >\n </span>\n </div>\n }\n </div>\n }\n @case ('list') {\n <div class=\"kf-skeleton-preset__stack\">\n @for (i of items(); track i) {\n <kf-document-row [loading]=\"true\" title=\"Documento\" meta=\"Archivo \u00B7 120 KB\" />\n }\n </div>\n }\n @case ('table') {\n <kf-data-table\n [columns]=\"tableColumns()\"\n [rows]=\"[]\"\n [loading]=\"true\"\n [skeletonRows]=\"total()\"\n />\n }\n @case ('timeline') {\n <div class=\"kf-skeleton-preset__timeline\" aria-hidden=\"true\">\n <div class=\"kf-skeleton-preset__timeline-toolbar\">\n <span class=\"kf-skeleton-preset__timeline-heading kf-bone\" style=\"--kf-bone-width: 30%\">\n Etapas\n </span>\n <span class=\"kf-skeleton-preset__timeline-toggle kf-bone\" style=\"--kf-bone-width: 7rem\">\n Expandir todo\n </span>\n </div>\n <ol class=\"kf-skeleton-preset__timeline-list\">\n @for (i of items(); track i) {\n <li class=\"kf-skeleton-preset__timeline-group\">\n <span class=\"kf-skeleton-preset__timeline-node kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__timeline-text\">\n <span\n class=\"kf-skeleton-preset__timeline-title kf-bone\"\n style=\"--kf-bone-width: 45%\"\n >\n Etapa\n </span>\n <span\n class=\"kf-skeleton-preset__timeline-meta kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Detalle\n </span>\n </span>\n </li>\n }\n </ol>\n </div>\n }\n @case ('detail-header') {\n <div class=\"kf-skeleton-preset__header\" aria-hidden=\"true\">\n <span class=\"kf-skeleton-preset__header-icon kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__header-heading\">\n <span class=\"kf-skeleton-preset__header-title kf-bone\" style=\"--kf-bone-width: 40%\">\n T\u00EDtulo\n </span>\n <span class=\"kf-skeleton-preset__header-subtitle kf-bone\" style=\"--kf-bone-width: 60%\">\n Subt\u00EDtulo\n </span>\n </span>\n </div>\n }\n }\n</div>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}.kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-skeleton-preset__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,15rem),1fr));gap:var(--kf-space-md)}.kf-skeleton-preset__grid--fixed{grid-template-columns:repeat(var(--kf-skeleton-preset-columns),minmax(0,1fr))}.kf-skeleton-preset__stack{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__form{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--kf-space-md)}.kf-skeleton-preset__form--two{grid-template-columns:repeat(2,minmax(0,1fr))}@media(max-width:640px){.kf-skeleton-preset__form--two{grid-template-columns:minmax(0,1fr)}}.kf-skeleton-preset__field{display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-skeleton-preset__control{display:block;padding:var(--kf-space-sm) var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-skeleton-preset__timeline{--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-heading{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title)}.kf-skeleton-preset__timeline-toggle{box-sizing:border-box;padding:6px var(--kf-space-sm);border:1px solid transparent;font-size:.85rem;font-weight:600;border-radius:var(--kf-radius-sm)}@media(max-width:640px),(pointer:coarse){.kf-skeleton-preset__timeline-toggle{min-height:44px}}.kf-skeleton-preset__timeline-list{list-style:none;margin:0;padding:0}.kf-skeleton-preset__timeline-group{position:relative;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter)}.kf-skeleton-preset__timeline-group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border)}.kf-skeleton-preset__timeline-node{flex:none;width:var(--_node);height:var(--_node);border-radius:50%}.kf-skeleton-preset__timeline-text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-skeleton-preset__timeline-title{font-weight:700;font-size:var(--kf-font-size-body)}.kf-skeleton-preset__timeline-meta{font-size:var(--kf-font-size-body-sm)}.kf-skeleton-preset__header{display:flex;align-items:center;gap:var(--kf-space-md);padding-bottom:var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-skeleton-preset__header-icon{flex:none;width:52px;height:52px;border:1px solid transparent;border-radius:var(--kf-radius-md)}.kf-skeleton-preset__header-heading{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}.kf-skeleton-preset__header-title{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);line-height:1.2}.kf-skeleton-preset__header-subtitle{font-size:.9375rem}@media(max-width:900px){.kf-skeleton-preset__header-icon{width:40px;height:40px}.kf-skeleton-preset__header-title{font-size:var(--kf-font-size-title-sm)}.kf-skeleton-preset__header-subtitle{font-size:.8125rem}}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap", "loading"] }, { kind: "component", type: DataTable, selector: "kf-data-table", inputs: ["columns", "rows", "loading", "skeletonRows", "rowKey", "flush", "cardable", "defaultView", "showViewToggle", "cardMinWidth", "render", "cellTemplates", "density", "rowClickable", "cellAriaLabel", "ariaLabel", "currentRowKey", "pageSize", "pageSizeOptions", "page", "keepPageOnRowsChange", "itemLabel", "sort", "view"], outputs: ["rowClick", "cellClick", "pageChange", "pageSizeChange", "sortChange", "viewChange"] }, { kind: "component", type: DocumentRow, selector: "kf-document-row", inputs: ["icon", "tone", "title", "meta", "href", "downloadLabel", "pendingLabel", "loading"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: StatCard, selector: "kf-stat-card", inputs: ["icon", "tone", "value", "label", "note", "bar", "hint", "pressed", "loading", "clickable"], outputs: ["cardClick"] }] });
|
|
4277
|
+
}
|
|
4278
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SkeletonPreset, decorators: [{
|
|
4279
|
+
type: Component,
|
|
4280
|
+
args: [{ selector: 'kf-skeleton-preset', imports: [Card, DataTable, DocumentRow, Skeleton, StatCard], template: "<div\n [class]=\"'kf-skeleton-preset kf-skeleton-preset--' + variant()\"\n aria-busy=\"true\"\n [style.--kf-skeleton-preset-columns]=\"columns() ?? null\"\n>\n @if (label()) {\n <span class=\"kf-sr-only\" role=\"status\">{{ label() }}</span>\n }\n @switch (variant()) {\n @case ('kpi-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-stat-card [loading]=\"true\" icon=\"bell\" value=\"0\" label=\"Etiqueta\" note=\"Nota\" />\n }\n </div>\n }\n @case ('card-grid') {\n <div class=\"kf-skeleton-preset__grid\" [class.kf-skeleton-preset__grid--fixed]=\"!!columns()\">\n @for (i of items(); track i) {\n <kf-card gap=\"sm\" aria-hidden=\"true\">\n <kf-skeleton variant=\"rect\" height=\"7.5rem\" />\n <kf-skeleton width=\"60%\" height=\"1.25rem\" />\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n </kf-card>\n }\n </div>\n }\n @case ('form') {\n <div\n class=\"kf-skeleton-preset__form\"\n [class.kf-skeleton-preset__form--two]=\"formColumns() === 2\"\n aria-hidden=\"true\"\n >\n @for (i of items(); track i) {\n <div class=\"kf-skeleton-preset__field\">\n <span\n class=\"kf-skeleton-preset__label kf-overline-text kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Etiqueta\n </span>\n <span class=\"kf-skeleton-preset__control\">\n <span class=\"kf-skeleton-preset__value kf-bone\" style=\"--kf-bone-width: 55%\"\n >Valor</span\n >\n </span>\n </div>\n }\n </div>\n }\n @case ('list') {\n <div class=\"kf-skeleton-preset__stack\">\n @for (i of items(); track i) {\n <kf-document-row [loading]=\"true\" title=\"Documento\" meta=\"Archivo \u00B7 120 KB\" />\n }\n </div>\n }\n @case ('table') {\n <kf-data-table\n [columns]=\"tableColumns()\"\n [rows]=\"[]\"\n [loading]=\"true\"\n [skeletonRows]=\"total()\"\n />\n }\n @case ('timeline') {\n <div class=\"kf-skeleton-preset__timeline\" aria-hidden=\"true\">\n <div class=\"kf-skeleton-preset__timeline-toolbar\">\n <span class=\"kf-skeleton-preset__timeline-heading kf-bone\" style=\"--kf-bone-width: 30%\">\n Etapas\n </span>\n <span class=\"kf-skeleton-preset__timeline-toggle kf-bone\" style=\"--kf-bone-width: 7rem\">\n Expandir todo\n </span>\n </div>\n <ol class=\"kf-skeleton-preset__timeline-list\">\n @for (i of items(); track i) {\n <li class=\"kf-skeleton-preset__timeline-group\">\n <span class=\"kf-skeleton-preset__timeline-node kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__timeline-text\">\n <span\n class=\"kf-skeleton-preset__timeline-title kf-bone\"\n style=\"--kf-bone-width: 45%\"\n >\n Etapa\n </span>\n <span\n class=\"kf-skeleton-preset__timeline-meta kf-bone\"\n style=\"--kf-bone-width: 30%\"\n >\n Detalle\n </span>\n </span>\n </li>\n }\n </ol>\n </div>\n }\n @case ('detail-header') {\n <div class=\"kf-skeleton-preset__header\" aria-hidden=\"true\">\n <span class=\"kf-skeleton-preset__header-icon kf-bone-fill\"></span>\n <span class=\"kf-skeleton-preset__header-heading\">\n <span class=\"kf-skeleton-preset__header-title kf-bone\" style=\"--kf-bone-width: 40%\">\n T\u00EDtulo\n </span>\n <span class=\"kf-skeleton-preset__header-subtitle kf-bone\" style=\"--kf-bone-width: 60%\">\n Subt\u00EDtulo\n </span>\n </span>\n </div>\n }\n }\n</div>\n", styles: [".kf-overline-text{font-family:var(--kf-font-title);font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-medium);letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}.kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-skeleton-preset__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,15rem),1fr));gap:var(--kf-space-md)}.kf-skeleton-preset__grid--fixed{grid-template-columns:repeat(var(--kf-skeleton-preset-columns),minmax(0,1fr))}.kf-skeleton-preset__stack{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__form{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--kf-space-md)}.kf-skeleton-preset__form--two{grid-template-columns:repeat(2,minmax(0,1fr))}@media(max-width:640px){.kf-skeleton-preset__form--two{grid-template-columns:minmax(0,1fr)}}.kf-skeleton-preset__field{display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-skeleton-preset__control{display:block;padding:var(--kf-space-sm) var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-skeleton-preset__timeline{--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-skeleton-preset__timeline-heading{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title)}.kf-skeleton-preset__timeline-toggle{box-sizing:border-box;padding:6px var(--kf-space-sm);border:1px solid transparent;font-size:.85rem;font-weight:600;border-radius:var(--kf-radius-sm)}@media(max-width:640px),(pointer:coarse){.kf-skeleton-preset__timeline-toggle{min-height:44px}}.kf-skeleton-preset__timeline-list{list-style:none;margin:0;padding:0}.kf-skeleton-preset__timeline-group{position:relative;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter)}.kf-skeleton-preset__timeline-group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border)}.kf-skeleton-preset__timeline-node{flex:none;width:var(--_node);height:var(--_node);border-radius:50%}.kf-skeleton-preset__timeline-text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-skeleton-preset__timeline-title{font-weight:700;font-size:var(--kf-font-size-body)}.kf-skeleton-preset__timeline-meta{font-size:var(--kf-font-size-body-sm)}.kf-skeleton-preset__header{display:flex;align-items:center;gap:var(--kf-space-md);padding-bottom:var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-skeleton-preset__header-icon{flex:none;width:52px;height:52px;border:1px solid transparent;border-radius:var(--kf-radius-md)}.kf-skeleton-preset__header-heading{flex:1 1 auto;display:flex;flex-direction:column;gap:2px;min-width:0}.kf-skeleton-preset__header-title{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);line-height:1.2}.kf-skeleton-preset__header-subtitle{font-size:.9375rem}@media(max-width:900px){.kf-skeleton-preset__header-icon{width:40px;height:40px}.kf-skeleton-preset__header-title{font-size:var(--kf-font-size-title-sm)}.kf-skeleton-preset__header-subtitle{font-size:.8125rem}}\n"] }]
|
|
4281
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
4282
|
+
|
|
4283
|
+
/**
|
|
4284
|
+
* Marca el `ng-template` que reemplaza el esqueleto genérico de
|
|
4285
|
+
* `kf-content-state` mientras carga (`state="loading"`, `load="skeleton"`).
|
|
4286
|
+
*
|
|
4287
|
+
* <kf-content-state [state]="state()">
|
|
4288
|
+
* <ng-template kfContentStateLoading><kf-skeleton-preset variant="kpi-grid" /></ng-template>
|
|
4289
|
+
* …
|
|
4290
|
+
* </kf-content-state>
|
|
4291
|
+
*/
|
|
4292
|
+
class ContentStateLoading {
|
|
4293
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentStateLoading, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4294
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: ContentStateLoading, isStandalone: true, selector: "ng-template[kfContentStateLoading]", ngImport: i0 });
|
|
4295
|
+
}
|
|
4296
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentStateLoading, decorators: [{
|
|
4297
|
+
type: Directive,
|
|
4298
|
+
args: [{ selector: 'ng-template[kfContentStateLoading]' }]
|
|
4299
|
+
}] });
|
|
4300
|
+
/**
|
|
4301
|
+
* Wrapper de los cuatro estados que toda sección tiene: cargando, con datos,
|
|
4302
|
+
* vacía y con error. Un componente en vez de tres `@if` sueltos, porque es
|
|
4303
|
+
* siempre el mismo slot el que va rotando entre los cuatro.
|
|
4304
|
+
*
|
|
4305
|
+
* Con `state="ready"` proyecta el contenido real y desaparece; en los otros
|
|
4306
|
+
* tres dibuja el placeholder correspondiente y el contenido no se instancia.
|
|
4307
|
+
* No inventa visuales propios: `loading` usa `kf-skeleton` (o `kf-spinner` con
|
|
4308
|
+
* `load="spinner"`), y `empty`/`error` usan `kf-empty-state`.
|
|
4309
|
+
*
|
|
4310
|
+
* Las acciones (reintentar, crear) van en el slot `actions` — se ignoran
|
|
4311
|
+
* mientras carga, porque no hay nada que reintentar todavía.
|
|
4312
|
+
*
|
|
4313
|
+
* <kf-content-state [state]="state()" title="No pudimos cargar tus vehículos"
|
|
4314
|
+
* subtitle="Revisá tu conexión e intentá de nuevo.">
|
|
4315
|
+
* <kf-button actions (click)="retry()">Reintentar</kf-button>
|
|
4316
|
+
* <kf-data-table [columns]="cols" [rows]="rows()" />
|
|
4317
|
+
* </kf-content-state>
|
|
4318
|
+
*
|
|
4319
|
+
* Esqueleto de carga: por defecto son tres líneas genéricas. Para que la
|
|
4320
|
+
* carga se parezca a lo que va a aparecer, hay tres vías (de la más corta a la
|
|
4321
|
+
* más libre): `loadingPreset` (una variante de `kf-skeleton-preset`), el input
|
|
4322
|
+
* `loadingTemplate` (un `TemplateRef`) o un `ng-template` marcado con
|
|
4323
|
+
* `kfContentStateLoading` dentro del contenido. Si hay más de una, gana la
|
|
4324
|
+
* plantilla proyectada, luego `loadingTemplate` y por último `loadingPreset`.
|
|
4325
|
+
*
|
|
4326
|
+
* <kf-content-state [state]="state()" loadingPreset="kpi-grid">…</kf-content-state>
|
|
4327
|
+
*
|
|
4328
|
+
* Accesibilidad: en modo skeleton el contenedor lleva `aria-busy="true"` y un
|
|
4329
|
+
* texto visualmente oculto con `role="status"` (`loadingLabel`, "Cargando…" por
|
|
4330
|
+
* defecto) que anuncia el estado una vez. Con `load="spinner"` el texto es el
|
|
4331
|
+
* del propio `kf-spinner`. El contenedor reserva un ancho mínimo, así que el
|
|
4332
|
+
* esqueleto no colapsa a 0 dentro de un padre que se ajusta a su contenido.
|
|
4333
|
+
*/
|
|
4334
|
+
class ContentState {
|
|
4335
|
+
state = input.required(/* @ts-ignore */
|
|
4336
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
4337
|
+
/** Forma del estado de carga: silueta (default) o spinner. */
|
|
4338
|
+
load = input('skeleton', /* @ts-ignore */
|
|
4339
|
+
...(ngDevMode ? [{ debugName: "load" }] : /* istanbul ignore next */ []));
|
|
4340
|
+
/** Título de `empty`/`error`. Ignorado mientras carga. */
|
|
4341
|
+
title = input('', /* @ts-ignore */
|
|
4342
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
4343
|
+
subtitle = input('', /* @ts-ignore */
|
|
4344
|
+
...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
|
|
4345
|
+
/** Texto de carga: al lado del spinner con `load="spinner"` y, en modo skeleton,
|
|
4346
|
+
* texto visualmente oculto con `role="status"` para lectores de pantalla. */
|
|
4347
|
+
loadingLabel = input('Cargando…', /* @ts-ignore */
|
|
4348
|
+
...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
|
|
4349
|
+
/** Plantilla propia del esqueleto (reemplaza las tres líneas genéricas). */
|
|
4350
|
+
loadingTemplate = input(null, /* @ts-ignore */
|
|
4351
|
+
...(ngDevMode ? [{ debugName: "loadingTemplate" }] : /* istanbul ignore next */ []));
|
|
4352
|
+
/** Variante de `kf-skeleton-preset` como esqueleto de carga. */
|
|
4353
|
+
loadingPreset = input(null, /* @ts-ignore */
|
|
4354
|
+
...(ngDevMode ? [{ debugName: "loadingPreset" }] : /* istanbul ignore next */ []));
|
|
4355
|
+
/** `ng-template[kfContentStateLoading]` proyectado como contenido. */
|
|
4356
|
+
projectedLoading = contentChild(ContentStateLoading, { ...(ngDevMode ? { debugName: "projectedLoading" } : /* istanbul ignore next */ {}), read: TemplateRef });
|
|
4357
|
+
customLoading = computed(() => this.projectedLoading() ?? this.loadingTemplate(), /* @ts-ignore */
|
|
4358
|
+
...(ngDevMode ? [{ debugName: "customLoading" }] : /* istanbul ignore next */ []));
|
|
4359
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4360
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentState, isStandalone: true, selector: "kf-content-state", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, load: { classPropertyName: "load", publicName: "load", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null }, loadingTemplate: { classPropertyName: "loadingTemplate", publicName: "loadingTemplate", isSignal: true, isRequired: false, transformFunction: null }, loadingPreset: { classPropertyName: "loadingPreset", publicName: "loadingPreset", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "projectedLoading", first: true, predicate: ContentStateLoading, descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <span class=\"kf-sr-only\" role=\"status\">{{ loadingLabel() }}</span>\n @if (customLoading(); as template) {\n <ng-container [ngTemplateOutlet]=\"template\" />\n } @else if (loadingPreset(); as preset) {\n <kf-skeleton-preset [variant]=\"preset\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm);min-width:min(12rem,100vw);box-sizing:border-box}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: EmptyState, selector: "kf-empty-state", inputs: ["icon", "tone", "variant", "title"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: SkeletonPreset, selector: "kf-skeleton-preset", inputs: ["variant", "count", "columns", "label"] }, { kind: "component", type: Spinner, selector: "kf-spinner", inputs: ["size", "label", "onDark"] }] });
|
|
4361
|
+
}
|
|
4362
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentState, decorators: [{
|
|
4363
|
+
type: Component,
|
|
4364
|
+
args: [{ selector: 'kf-content-state', imports: [EmptyState, NgTemplateOutlet, Skeleton, SkeletonPreset, Spinner], template: "@switch (state()) {\n @case ('ready') {\n <ng-content />\n }\n @case ('loading') {\n <div class=\"kf-content-state__loading\" aria-busy=\"true\">\n @if (load() === 'spinner') {\n <kf-spinner [label]=\"loadingLabel()\" />\n } @else {\n <span class=\"kf-sr-only\" role=\"status\">{{ loadingLabel() }}</span>\n @if (customLoading(); as template) {\n <ng-container [ngTemplateOutlet]=\"template\" />\n } @else if (loadingPreset(); as preset) {\n <kf-skeleton-preset [variant]=\"preset\" />\n } @else {\n <kf-skeleton />\n <kf-skeleton width=\"80%\" />\n <kf-skeleton width=\"60%\" />\n }\n }\n </div>\n }\n @default {\n <kf-empty-state [tone]=\"state() === 'error' ? 'error' : 'neutral'\">\n @if (title()) {\n <p class=\"kf-content-state__title\">{{ title() }}</p>\n }\n @if (subtitle()) {\n <p class=\"kf-content-state__subtitle\">{{ subtitle() }}</p>\n }\n <div class=\"kf-content-state__actions\"><ng-content select=\"[actions]\" /></div>\n </kf-empty-state>\n }\n}\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;font-family:var(--kf-font-body)}.kf-content-state__loading{display:flex;flex-direction:column;gap:var(--kf-space-sm);min-width:min(12rem,100vw);box-sizing:border-box}.kf-content-state__title{margin:0;font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-content-state__subtitle{margin:0;color:var(--kf-color-text-muted)}.kf-content-state__actions:empty{display:none}.kf-content-state__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
|
|
4365
|
+
}], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], load: [{ type: i0.Input, args: [{ isSignal: true, alias: "load", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], loadingTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingTemplate", required: false }] }], loadingPreset: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingPreset", required: false }] }], projectedLoading: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ContentStateLoading), { ...{
|
|
4366
|
+
read: TemplateRef,
|
|
4367
|
+
}, isSignal: true }] }] } });
|
|
4368
|
+
|
|
4369
|
+
const RAW_BASE64_MIME = {
|
|
4370
|
+
image: 'image/jpeg',
|
|
4371
|
+
video: 'video/mp4',
|
|
4372
|
+
pdf: 'application/pdf',
|
|
4373
|
+
};
|
|
4374
|
+
/** Solo contenido ya en memoria: un `<iframe>` con una URL remota arbitraria podría cargar cualquier página. */
|
|
4375
|
+
const LOCAL_PDF_SRC = /^(blob:|data:application\/pdf[;,])/i;
|
|
4376
|
+
let contentViewerId = 0;
|
|
4377
|
+
/**
|
|
4378
|
+
* Visor a pantalla completa para ampliar una pieza de contenido: imagen,
|
|
4379
|
+
* video o PDF (`type="pdf"`, renderizado por el visor nativo del navegador).
|
|
4380
|
+
* Mismo mecanismo que `kf-modal` (el `<dialog>` nativo resuelve backdrop,
|
|
4381
|
+
* atrapado de foco, top-layer y `Escape`), pero sin panel/título/footer:
|
|
4382
|
+
* el contenido ocupa toda la pantalla sobre un backdrop oscuro.
|
|
4383
|
+
*
|
|
4384
|
+
* `src` acepta lo que venga del backend sin que el consumidor tenga que
|
|
4385
|
+
* normalizarlo primero: una URL (`http(s)://`), un data URI ya armado
|
|
4386
|
+
* (`data:...`), o base64 crudo (se le antepone el prefijo `data:` según
|
|
4387
|
+
* `type`, asumiendo JPEG para imagen, MP4 para video y PDF para pdf). Un PDF
|
|
4388
|
+
* solo acepta contenido local (`blob:`, data URI o base64), nunca una URL remota.
|
|
4389
|
+
*
|
|
4390
|
+
* Lo proyectado con el atributo `actions` (p. ej. un botón de descarga) va en
|
|
4391
|
+
* la barra superior, junto al título.
|
|
4392
|
+
*
|
|
4393
|
+
* <kf-content-viewer [(open)]="viewerOpen" [src]="viewerSrc()" alt="Foto del vehículo" />
|
|
4394
|
+
* <kf-content-viewer [(open)]="viewerOpen" type="video" [src]="viewerSrc()" title="Inspección" />
|
|
4395
|
+
* <kf-content-viewer [(open)]="viewerOpen" type="pdf" [src]="blobUrl()" title="Contrato">
|
|
4396
|
+
* <kf-button actions variant="surface" icon="download" (click)="save()">Descargar</kf-button>
|
|
4397
|
+
* </kf-content-viewer>
|
|
4398
|
+
*/
|
|
4399
|
+
class ContentViewer {
|
|
4400
|
+
type = input('image', /* @ts-ignore */
|
|
4401
|
+
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
4402
|
+
/** URL, data URI, o base64 crudo — ver JSDoc de la clase. */
|
|
4403
|
+
src = input(null, /* @ts-ignore */
|
|
4404
|
+
...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
|
|
4405
|
+
/** Texto alternativo cuando `type="image"`. */
|
|
4406
|
+
alt = input('', /* @ts-ignore */
|
|
4407
|
+
...(ngDevMode ? [{ debugName: "alt" }] : /* istanbul ignore next */ []));
|
|
4408
|
+
title = input('', /* @ts-ignore */
|
|
4409
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
4410
|
+
/** Visibilidad, two-way: `[(open)]="viewerOpen"`. */
|
|
4411
|
+
open = model(false, /* @ts-ignore */
|
|
4412
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
4413
|
+
/** Permite cerrar con el botón, `Escape` o clic en el backdrop. */
|
|
4414
|
+
dismissible = input(true, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4415
|
+
titleId = `kf-content-viewer-title-${contentViewerId++}`;
|
|
4416
|
+
/** Detecta si `src` ya es una URL o un data URI armado; si no, asume
|
|
4417
|
+
* base64 crudo y le antepone el prefijo `data:` correspondiente a `type`
|
|
4418
|
+
* — el backend a veces manda una cosa, a veces la otra. */
|
|
4419
|
+
resolvedSrc = computed(() => {
|
|
4420
|
+
const raw = this.src();
|
|
4421
|
+
if (!raw)
|
|
4422
|
+
return null;
|
|
4423
|
+
if (/^(https?:|data:|blob:)/i.test(raw))
|
|
4424
|
+
return raw;
|
|
4425
|
+
return `data:${RAW_BASE64_MIME[this.type()]};base64,${raw}`;
|
|
4426
|
+
}, /* @ts-ignore */
|
|
4427
|
+
...(ngDevMode ? [{ debugName: "resolvedSrc" }] : /* istanbul ignore next */ []));
|
|
4428
|
+
pdfSrc = computed(() => {
|
|
4429
|
+
const src = this.resolvedSrc();
|
|
4430
|
+
if (this.type() !== 'pdf' || !src || !LOCAL_PDF_SRC.test(src))
|
|
4431
|
+
return null;
|
|
4432
|
+
return this.sanitizer.bypassSecurityTrustResourceUrl(src);
|
|
4433
|
+
}, /* @ts-ignore */
|
|
4434
|
+
...(ngDevMode ? [{ debugName: "pdfSrc" }] : /* istanbul ignore next */ []));
|
|
4435
|
+
fallbackLabel = computed(() => ({ image: 'Visor de imagen', video: 'Visor de video', pdf: 'Visor de documento' })[this.type()], /* @ts-ignore */
|
|
4436
|
+
...(ngDevMode ? [{ debugName: "fallbackLabel" }] : /* istanbul ignore next */ []));
|
|
4437
|
+
dialog = viewChild('dialog', /* @ts-ignore */
|
|
4438
|
+
...(ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []));
|
|
4439
|
+
document = inject(DOCUMENT);
|
|
4440
|
+
sanitizer = inject(DomSanitizer);
|
|
4441
|
+
constructor() {
|
|
4442
|
+
afterRenderEffect(() => {
|
|
4443
|
+
const el = this.dialog()?.nativeElement;
|
|
4444
|
+
if (!el) {
|
|
4445
|
+
return;
|
|
4446
|
+
}
|
|
4447
|
+
if (this.open() && !el.open) {
|
|
4448
|
+
// `showModal` no existe en jsdom (tests) ni en entornos sin
|
|
4449
|
+
// top-layer; el atributo `open` lo deja visible, aunque no modal.
|
|
4450
|
+
if (typeof el.showModal === 'function') {
|
|
4451
|
+
el.showModal();
|
|
4452
|
+
}
|
|
4453
|
+
else {
|
|
4454
|
+
el.open = true;
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
else if (!this.open() && el.open) {
|
|
4458
|
+
if (typeof el.close === 'function') {
|
|
4459
|
+
el.close();
|
|
4460
|
+
}
|
|
4461
|
+
else {
|
|
4462
|
+
el.open = false;
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
// `<dialog>` inertiza el resto de la página pero no frena su scroll.
|
|
4466
|
+
this.document.body.style.overflow = this.open() ? 'hidden' : '';
|
|
4467
|
+
});
|
|
4468
|
+
inject(DestroyRef).onDestroy(() => {
|
|
4469
|
+
this.document.body.style.overflow = '';
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4472
|
+
/** `Escape` dispara `cancel`; cancelarlo lo deja abierto. */
|
|
4473
|
+
onCancel(event) {
|
|
4474
|
+
if (!this.dismissible()) {
|
|
4475
|
+
event.preventDefault();
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
/**
|
|
4479
|
+
* Clic en el backdrop. Un clic en la zona del backdrop tiene como `target` al
|
|
4480
|
+
* propio `<dialog>` (el contenido vive en el panel de adentro), así que basta
|
|
4481
|
+
* comparar contra él — no hace falta cortar la propagación en el panel.
|
|
4482
|
+
*/
|
|
4483
|
+
onHostClick(event) {
|
|
4484
|
+
if (this.dismissible() && event.target === this.dialog()?.nativeElement) {
|
|
4485
|
+
this.open.set(false);
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4489
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ContentViewer, isStandalone: true, selector: "kf-content-viewer", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "click": "onHostClick($event)" } }, viewQueries: [{ propertyName: "dialog", first: true, predicate: ["dialog"], descendants: true, isSignal: true }], ngImport: i0, template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }] });
|
|
4490
|
+
}
|
|
4491
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ContentViewer, decorators: [{
|
|
4492
|
+
type: Component,
|
|
4493
|
+
args: [{ selector: 'kf-content-viewer', imports: [Icon], template: "<dialog\n #dialog\n class=\"kf-content-viewer\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-label]=\"title() ? null : fallbackLabel()\"\n (cancel)=\"onCancel($event)\"\n (close)=\"open.set(false)\"\n>\n @if (open()) {\n <div class=\"kf-content-viewer__panel\">\n @if (dismissible()) {\n <button\n type=\"button\"\n class=\"kf-content-viewer__close\"\n aria-label=\"Cerrar visor\"\n (click)=\"open.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n }\n <div class=\"kf-content-viewer__bar\">\n @if (title()) {\n <span class=\"kf-content-viewer__title\" [id]=\"titleId\">{{ title() }}</span>\n }\n <div class=\"kf-content-viewer__actions\"><ng-content select=\"[actions]\" /></div>\n </div>\n <div class=\"kf-content-viewer__stage\">\n @if (type() === 'pdf') {\n @if (pdfSrc(); as src) {\n <iframe\n class=\"kf-content-viewer__document\"\n [src]=\"src\"\n [title]=\"title() || fallbackLabel()\"\n ></iframe>\n }\n } @else if (resolvedSrc(); as src) {\n @if (type() === 'video') {\n <video class=\"kf-content-viewer__media\" [src]=\"src\" controls autoplay></video>\n } @else {\n <img class=\"kf-content-viewer__media\" [src]=\"src\" [alt]=\"alt()\" />\n }\n }\n </div>\n </div>\n }\n</dialog>\n", styles: [".kf-content-viewer{width:100vw;height:100vh;max-width:100vw;max-height:100vh;margin:0;padding:0;border:none;background:transparent;overflow:visible;font-family:var(--kf-font-body)}.kf-content-viewer::backdrop{background:#0a0c10eb}.kf-content-viewer__panel{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kf-space-md);width:100%;height:100%;padding:var(--kf-space-xl);box-sizing:border-box}.kf-content-viewer__close{position:absolute;top:var(--kf-space-lg);right:var(--kf-space-lg);display:inline-flex;padding:var(--kf-space-xs);border:none;border-radius:var(--kf-radius-pill);background:#ffffff1f;color:var(--kf-color-white);cursor:pointer}.kf-content-viewer__close:hover{background:#ffffff38}.kf-content-viewer__close:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-content-viewer__bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:var(--kf-space-md);max-width:calc(100% - 2 * var(--kf-space-xl))}.kf-content-viewer__actions{display:contents}.kf-content-viewer__title{font-size:.9rem;font-weight:600;color:var(--kf-color-white);text-align:center}.kf-content-viewer__stage{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;width:100%}.kf-content-viewer__document{width:min(100%,1100px);height:100%;border:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-white)}.kf-content-viewer__media{max-width:100%;max-height:100%;object-fit:contain;border-radius:var(--kf-radius-sm)}\n"] }]
|
|
4494
|
+
}], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dialog: [{ type: i0.ViewChild, args: ['dialog', { isSignal: true }] }], onHostClick: [{
|
|
4495
|
+
type: HostListener,
|
|
4496
|
+
args: ['click', ['$event']]
|
|
4497
|
+
}] } });
|
|
4498
|
+
|
|
4499
|
+
/**
|
|
4500
|
+
* Signal con la vista de `kf-data-table` ('table' | 'cards') derivada de un
|
|
4501
|
+
* breakpoint — mismo criterio que cualquier tabla con muchas columnas: bajo
|
|
4502
|
+
* el ancho dado, la vista de tabla no entra sin scroll horizontal, así que
|
|
4503
|
+
* se fuerza `cards`. Centraliza el patrón `matchMediaSignal` +
|
|
4504
|
+
* `computed<DataTableView>` que cada tabla del portal repetía igual.
|
|
4505
|
+
*
|
|
4506
|
+
* protected readonly tableView = dataTableViewSignal();
|
|
4507
|
+
* // con un breakpoint distinto al default de 900px:
|
|
4508
|
+
* protected readonly tableView = dataTableViewSignal('(max-width: 768px)');
|
|
4509
|
+
*/
|
|
4510
|
+
function dataTableViewSignal(query = '(max-width: 900px)') {
|
|
4511
|
+
const isMobile = matchMediaSignal(query);
|
|
4512
|
+
return computed(() => (isMobile() ? 'cards' : 'table'));
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
/**
|
|
4516
|
+
* Separador visual simple entre bloques de contenido. `orientation`
|
|
4517
|
+
* controla si es una línea horizontal (para separar secciones apiladas) o
|
|
4518
|
+
* vertical (para separar elementos en una fila, ej. acciones en una toolbar,
|
|
4519
|
+
* o las columnas de metadata en `kf-company-selector`).
|
|
4520
|
+
*
|
|
4521
|
+
* En `vertical`, el divisor necesita una altura del contexto donde vive —
|
|
4522
|
+
* `:host { align-self: stretch }` lo hace ocupar la altura del contenedor
|
|
4523
|
+
* flex/grid padre sin que el consumidor tenga que fijarla a mano. `flex:
|
|
4524
|
+
* none` evita que el propio divisor se achique a 0 dentro de ese contenedor
|
|
4525
|
+
* flex cuando hay contenido hermano compitiendo por espacio.
|
|
4526
|
+
*
|
|
4527
|
+
* <kf-divider />
|
|
4528
|
+
* <kf-divider orientation="vertical" />
|
|
4030
4529
|
*/
|
|
4031
4530
|
class Divider {
|
|
4032
4531
|
orientation = input('horizontal', /* @ts-ignore */
|
|
@@ -4044,78 +4543,126 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4044
4543
|
}], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }] } });
|
|
4045
4544
|
|
|
4046
4545
|
/**
|
|
4047
|
-
*
|
|
4048
|
-
*
|
|
4049
|
-
*
|
|
4546
|
+
* Contenedores modales abiertos actualmente: un `<dialog>` abierto vía
|
|
4547
|
+
* `showModal()` (`kf-modal`) o el panel de un `kf-drawer` con
|
|
4548
|
+
* `role="dialog"` (`aria-modal="true"`), en orden de apertura. Con diálogos
|
|
4549
|
+
* anidados el navegador apila el top layer: el más reciente queda arriba e
|
|
4550
|
+
* inertiza a los anteriores (incluidos otros `<dialog>` ya abiertos), así
|
|
4551
|
+
* que el ÚLTIMO de esta lista es el único candidato válido para recibir el
|
|
4552
|
+
* overlay container — nunca "cualquier `dialog[open]` de la página", que
|
|
4553
|
+
* rompería con dos `kf-modal` anidados o con dos independientes donde solo
|
|
4554
|
+
* uno está abierto.
|
|
4555
|
+
*/
|
|
4556
|
+
const openModalDialogs = [];
|
|
4557
|
+
/**
|
|
4558
|
+
* Reubicadores de los `OverlayContainer` parchados. Cada cambio en el
|
|
4559
|
+
* registro los ejecuta, así el contenedor vuelve a `<body>` (o pasa al
|
|
4560
|
+
* siguiente contenedor modal) al cerrar, sin esperar a que otro overlay
|
|
4561
|
+
* llame a `getContainerElement()`: un panel con el `overlayRef` ya creado
|
|
4562
|
+
* que se vuelve a abrir no pasa por esa llamada, y si el contenedor seguía
|
|
4563
|
+
* dentro de un diálogo cerrado quedaba invisible.
|
|
4564
|
+
*/
|
|
4565
|
+
const relocators = new Set();
|
|
4566
|
+
function relocateContainers() {
|
|
4567
|
+
for (const relocate of [...relocators]) {
|
|
4568
|
+
if (!relocate()) {
|
|
4569
|
+
relocators.delete(relocate);
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
/** Llamado por `Modal` cuando su `<dialog>` pasa a estar abierto, y por `Drawer` (`role="dialog"`) con su panel. */
|
|
4574
|
+
function registerOpenModalDialog(dialog) {
|
|
4575
|
+
if (!openModalDialogs.includes(dialog)) {
|
|
4576
|
+
openModalDialogs.push(dialog);
|
|
4577
|
+
relocateContainers();
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
/** Llamado por `Modal`/`Drawer` cuando su contenedor se cierra o el componente se destruye. */
|
|
4581
|
+
function unregisterOpenModalDialog(dialog) {
|
|
4582
|
+
const index = openModalDialogs.indexOf(dialog);
|
|
4583
|
+
if (index !== -1) {
|
|
4584
|
+
openModalDialogs.splice(index, 1);
|
|
4585
|
+
relocateContainers();
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
function topmostOpenModalDialog() {
|
|
4589
|
+
return openModalDialogs.at(-1);
|
|
4590
|
+
}
|
|
4591
|
+
/**
|
|
4592
|
+
* Instancias de `OverlayContainer` ya parchadas — evita parchar dos veces
|
|
4593
|
+
* el mismo objeto (varios `kf-modal` en el árbol llaman a esta función) y,
|
|
4594
|
+
* a la vez, no interfiere con instancias nuevas que arma un `TestBed` fresco
|
|
4595
|
+
* en cada spec (root providers se recrean por test).
|
|
4596
|
+
*/
|
|
4597
|
+
const patchedContainers = new WeakSet();
|
|
4598
|
+
/**
|
|
4599
|
+
* `showModal()` promueve el `<dialog>` al top layer del navegador y vuelve
|
|
4600
|
+
* `inert` todo lo que quede fuera de su subárbol — pero Angular CDK
|
|
4601
|
+
* (`Overlay`/`OverlayContainer`, usado por `kf-select`, `kf-multi-select`,
|
|
4602
|
+
* `kf-dropdown`, `kf-tooltip` y el selector de tamaño de `kf-pagination`)
|
|
4603
|
+
* siempre appendea `.cdk-overlay-container` como hijo de `<body>`, que
|
|
4604
|
+
* queda fuera del `<dialog>` y por lo tanto inerte: el overlay se ve pero
|
|
4605
|
+
* no recibe clicks.
|
|
4050
4606
|
*
|
|
4051
|
-
*
|
|
4052
|
-
*
|
|
4053
|
-
*
|
|
4054
|
-
*
|
|
4607
|
+
* No existe una forma de reemplazar, sin que el consumidor configure nada,
|
|
4608
|
+
* QUÉ IMPLEMENTACIÓN resuelve Angular DI para el token `OverlayContainer`:
|
|
4609
|
+
* es `providedIn: 'root'`, y la instancia se crea una sola vez contra el
|
|
4610
|
+
* inyector RAÍZ de la app la primera vez que algo la necesita — incluida
|
|
4611
|
+
* la propia `Overlay`, que la inyecta puertas adentro de SU construcción,
|
|
4612
|
+
* no en el inyector del componente que gatilló esa construcción. Un
|
|
4613
|
+
* `providers` a nivel de componente (`kf-modal`, o incluso `kf-select`)
|
|
4614
|
+
* nunca llega a interceptar esa creación; solo un `providers` explícito en
|
|
4615
|
+
* el bootstrap de cada app lo haría, y eso es justo la configuración manual
|
|
4616
|
+
* que se quiere evitar.
|
|
4055
4617
|
*
|
|
4056
|
-
*
|
|
4057
|
-
*
|
|
4058
|
-
*
|
|
4618
|
+
* Por eso el fix no reemplaza la clase vía DI: parcha, una sola vez, el
|
|
4619
|
+
* MISMO objeto singleton que ya usa toda la app (la instancia real que
|
|
4620
|
+
* `Modal` recibe con un `inject(OverlayContainer)` normal — automático
|
|
4621
|
+
* apenas se usa `kf-modal`, sin tocar ningún `providers`). `getContainerElement`
|
|
4622
|
+
* sigue delegando en el original para crear/cachear el div la primera vez;
|
|
4623
|
+
* lo único que se agrega es reubicarlo, en cada llamada, dentro del
|
|
4624
|
+
* `<dialog>` abierto más reciente (o de vuelta en `<body>` si no hay
|
|
4625
|
+
* ninguno) antes de devolverlo — mover el div se lleva puestos a sus hijos,
|
|
4626
|
+
* así que overlays ya adjuntos antes de abrir el modal quedan igual de
|
|
4627
|
+
* bien ubicados que los que se abren después.
|
|
4059
4628
|
*
|
|
4060
|
-
*
|
|
4061
|
-
*
|
|
4062
|
-
*
|
|
4063
|
-
|
|
4064
|
-
class Link {
|
|
4065
|
-
variant = input('default', /* @ts-ignore */
|
|
4066
|
-
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
4067
|
-
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4068
|
-
/** Si se pasa, renderiza un `<a>` real en vez de `<button>` — navega en vez de disparar `(click)`. */
|
|
4069
|
-
href = input(/* @ts-ignore */
|
|
4070
|
-
...(ngDevMode ? [undefined, { debugName: "href" }] : /* istanbul ignore next */ []));
|
|
4071
|
-
/** Ícono `chevron-right` al final — para el patrón "texto + flecha" de un link que lleva a otro lado. */
|
|
4072
|
-
showArrow = input(false, { ...(ngDevMode ? { debugName: "showArrow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4073
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4074
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Link, isStandalone: true, selector: "kf-link", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, showArrow: { classPropertyName: "showArrow", publicName: "showArrow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
4075
|
-
}
|
|
4076
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Link, decorators: [{
|
|
4077
|
-
type: Component,
|
|
4078
|
-
args: [{ selector: 'kf-link', imports: [Icon, NgTemplateOutlet], template: "<!--\n Una sola proyecci\u00F3n compartida (ng-template + ngTemplateOutlet), no un\n <ng-content> por rama: Angular solo conecta la proyecci\u00F3n real del\n consumidor a la PRIMERA instancia de <ng-content> que encuentra en el\n \u00E1rbol de compilaci\u00F3n \u2014 con un <ng-content> en cada rama del @if, la\n que no se activa en runtime igual \"roba\" la proyecci\u00F3n (mismo bug ya\n resuelto en Text.ts).\n-->\n<ng-template #content>\n <ng-content />\n @if (showArrow()) {\n <kf-icon name=\"chevron-right\" size=\"sm\" />\n }\n</ng-template>\n@if (href(); as url) {\n <a class=\"kf-link\" [class.kf-link--muted]=\"variant() === 'muted'\" [href]=\"url\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </a>\n} @else {\n <button\n type=\"button\"\n class=\"kf-link\"\n [class.kf-link--muted]=\"variant() === 'muted'\"\n [disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"content\" />\n </button>\n}\n", styles: [":host{display:inline-block}.kf-link{display:inline-flex;align-items:center;gap:2px;padding:0;border:none;background:transparent;font:inherit;font-weight:500;color:var(--kf-color-primary);text-decoration:none;cursor:pointer}.kf-link:hover:not(:disabled){text-decoration:underline}.kf-link:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px;border-radius:var(--kf-radius-xs)}.kf-link:disabled{opacity:.5;cursor:not-allowed}.kf-link--muted{color:var(--kf-color-text-muted)}.kf-link--muted:hover:not(:disabled){color:var(--kf-color-primary);text-decoration:none}\n"] }]
|
|
4079
|
-
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], showArrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArrow", required: false }] }] } });
|
|
4080
|
-
|
|
4081
|
-
/**
|
|
4082
|
-
* Fila de un documento descargable: chip de ícono coloreado por tipo,
|
|
4083
|
-
* título, metadata y la acción de descarga al final. Sin `href` rinde
|
|
4084
|
-
* `pendingLabel` en vez de un link roto. El contenido proyectado cae entre
|
|
4085
|
-
* la metadata y la acción, como slot para un `kf-badge` de estado.
|
|
4629
|
+
* Un `kf-drawer` con `role="dialog"` no usa el top layer, pero declara
|
|
4630
|
+
* `aria-modal="true"`: un overlay fuera de su subárbol queda, para un lector
|
|
4631
|
+
* de pantalla, fuera del diálogo. Por eso también registra su panel acá y
|
|
4632
|
+
* el contenedor se reubica dentro de él mientras está abierto.
|
|
4086
4633
|
*/
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4634
|
+
function installModalAwareOverlayContainer(container) {
|
|
4635
|
+
if (patchedContainers.has(container)) {
|
|
4636
|
+
return;
|
|
4637
|
+
}
|
|
4638
|
+
patchedContainers.add(container);
|
|
4639
|
+
const originalGetContainerElement = container.getContainerElement.bind(container);
|
|
4640
|
+
let knownElement = null;
|
|
4641
|
+
container.getContainerElement = () => {
|
|
4642
|
+
const element = originalGetContainerElement();
|
|
4643
|
+
knownElement = element;
|
|
4644
|
+
const targetParent = topmostOpenModalDialog() ?? element.ownerDocument.body;
|
|
4645
|
+
if (element.parentElement !== targetParent) {
|
|
4646
|
+
targetParent.appendChild(element);
|
|
4647
|
+
}
|
|
4648
|
+
return element;
|
|
4649
|
+
};
|
|
4650
|
+
// Devuelve `false` cuando el contenedor ya no existe (la app se destruyó):
|
|
4651
|
+
// se descarta del conjunto en vez de recrearlo. Mientras nadie lo haya
|
|
4652
|
+
// pedido todavía no hay nada que reubicar.
|
|
4653
|
+
relocators.add(() => {
|
|
4654
|
+
if (!knownElement) {
|
|
4655
|
+
return true;
|
|
4656
|
+
}
|
|
4657
|
+
if (!knownElement.isConnected) {
|
|
4658
|
+
return false;
|
|
4659
|
+
}
|
|
4660
|
+
container.getContainerElement();
|
|
4661
|
+
return true;
|
|
4662
|
+
});
|
|
4110
4663
|
}
|
|
4111
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DocumentRow, decorators: [{
|
|
4112
|
-
type: Component,
|
|
4113
|
-
args: [{ selector: 'kf-document-row', imports: [Icon, Link], host: {
|
|
4114
|
-
'[class]': '"kf-document-row--" + tone()',
|
|
4115
|
-
}, template: "<span class=\"kf-document-row__icon\" aria-hidden=\"true\">\n <kf-icon [name]=\"icon()\" size=\"sm\" />\n</span>\n\n<span class=\"kf-document-row__body\">\n <span class=\"kf-document-row__title\">{{ title() }}</span>\n @if (meta(); as line) {\n <span class=\"kf-document-row__meta kf-caption-text\">{{ line }}</span>\n }\n</span>\n\n<span class=\"kf-document-row__actions\">\n <ng-content />\n @if (href(); as url) {\n <kf-link [href]=\"url\">{{ downloadLabel() }}</kf-link>\n } @else {\n <span class=\"kf-document-row__pending kf-caption-text\">{{ pendingLabel() }}</span>\n }\n</span>\n", styles: [".kf-caption-text{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}:host{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-document-row__icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:32px;height:32px;border-radius:var(--kf-radius-xs);background:var(--kf-color-gray-200);color:var(--kf-color-text)}:host(.kf-document-row--info) .kf-document-row__icon{background:var(--kf-color-info-soft);color:var(--kf-color-info-strong)}:host(.kf-document-row--ok) .kf-document-row__icon{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}:host(.kf-document-row--warn) .kf-document-row__icon{background:var(--kf-color-warning-soft);color:var(--kf-color-warning-strong)}:host(.kf-document-row--error) .kf-document-row__icon{background:var(--kf-color-error-soft);color:var(--kf-color-error-strong)}.kf-document-row__body{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-width:0}.kf-document-row__title{font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-medium);color:var(--kf-color-ink)}.kf-document-row__meta{overflow-wrap:anywhere}.kf-document-row__actions{display:flex;align-items:center;gap:var(--kf-space-sm);flex:0 0 auto;margin-left:auto}@container (max-width: 420px){:host{flex-wrap:wrap}}\n"] }]
|
|
4116
|
-
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], meta: [{ type: i0.Input, args: [{ isSignal: true, alias: "meta", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], downloadLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadLabel", required: false }] }], pendingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingLabel", required: false }] }] } });
|
|
4117
4664
|
|
|
4118
|
-
let nextId$
|
|
4665
|
+
let nextId$3 = 0;
|
|
4119
4666
|
/**
|
|
4120
4667
|
* Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
|
|
4121
4668
|
* ni qué es sesión, solo da tres lugares para proyectar contenido
|
|
@@ -4169,8 +4716,9 @@ let nextId$2 = 0;
|
|
|
4169
4716
|
* redondeadas, alto máximo 80vh) — pensado para un resumen en mobile.
|
|
4170
4717
|
*
|
|
4171
4718
|
* Escape cierra el panel salvo que un control interno ya haya consumido la
|
|
4172
|
-
* tecla (`event.defaultPrevented
|
|
4173
|
-
* drawer cierra
|
|
4719
|
+
* tecla (`event.defaultPrevented`): un `kf-select`, `kf-multi-select` o
|
|
4720
|
+
* `kf-dropdown` abierto dentro del drawer cierra primero su propio panel y
|
|
4721
|
+
* un segundo Escape cierra el drawer.
|
|
4174
4722
|
*
|
|
4175
4723
|
* Nombres por defecto: con `role="navigation"` el panel se llama "Menú" y
|
|
4176
4724
|
* el cierre "Cerrar menú" (comportamiento histórico). Con `role="dialog"`
|
|
@@ -4178,12 +4726,25 @@ let nextId$2 = 0;
|
|
|
4178
4726
|
* avisa por consola si no hay `title` ni `ariaLabel`: un diálogo sin nombre
|
|
4179
4727
|
* propio no le dice nada al lector de pantalla.
|
|
4180
4728
|
*
|
|
4181
|
-
*
|
|
4182
|
-
*
|
|
4183
|
-
*
|
|
4184
|
-
*
|
|
4185
|
-
*
|
|
4186
|
-
*
|
|
4729
|
+
* ## Selectores y otros paneles flotantes dentro del drawer
|
|
4730
|
+
*
|
|
4731
|
+
* `kf-select`, `kf-multi-select` y `kf-dropdown` funcionan
|
|
4732
|
+
* dentro de un drawer (incluido el pie `[kf-drawer-footer]`): sus paneles
|
|
4733
|
+
* quedan por encima del drawer, se abren hacia arriba si abajo no hay lugar
|
|
4734
|
+
* y no se cortan con el borde del panel ni con el scroll del cuerpo. Con
|
|
4735
|
+
* `role="dialog"` el contenedor de overlays de CDK se reubica dentro del
|
|
4736
|
+
* panel mientras está abierto: el listbox queda dentro del diálogo
|
|
4737
|
+
* `aria-modal`, no fuera de él, y el atrapado de foco lo incluye. Al cerrar
|
|
4738
|
+
* el drawer vuelve a `<body>`. Escape cierra primero el panel flotante
|
|
4739
|
+
* (devolviendo el foco a su disparador) y el siguiente cierra el drawer.
|
|
4740
|
+
*
|
|
4741
|
+
* <kf-drawer [(open)]="open" role="dialog" side="right" title="Unidad VIN-1003">
|
|
4742
|
+
* ...
|
|
4743
|
+
* <div kf-drawer-footer>
|
|
4744
|
+
* <kf-select label="Saltar a otra unidad" width="full"
|
|
4745
|
+
* [options]="units" [value]="unit()" (valueChange)="unit.set($event)" />
|
|
4746
|
+
* </div>
|
|
4747
|
+
* </kf-drawer>
|
|
4187
4748
|
*
|
|
4188
4749
|
* Al cerrar, el foco vuelve al elemento que lo tenía al abrir solo si sigue
|
|
4189
4750
|
* en el documento (`isConnected`); si fue desmontado mientras el drawer
|
|
@@ -4220,14 +4781,29 @@ class Drawer {
|
|
|
4220
4781
|
/** `closeLabel` o, sin él, el default según el rol. */
|
|
4221
4782
|
resolvedCloseLabel = computed(() => this.closeLabel() ?? (this.role() === 'dialog' ? 'Cerrar' : 'Cerrar menú'), /* @ts-ignore */
|
|
4222
4783
|
...(ngDevMode ? [{ debugName: "resolvedCloseLabel" }] : /* istanbul ignore next */ []));
|
|
4223
|
-
titleId = `kf-drawer-title-${nextId$
|
|
4784
|
+
titleId = `kf-drawer-title-${nextId$3++}`;
|
|
4224
4785
|
panelRef = viewChild.required('panel', /* @ts-ignore */
|
|
4225
4786
|
...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
|
|
4226
4787
|
focusTrapFactory = inject(FocusTrapFactory);
|
|
4227
4788
|
document = inject(DOCUMENT);
|
|
4789
|
+
overlayContainer = inject(OverlayContainer);
|
|
4790
|
+
registeredPanel = null;
|
|
4228
4791
|
focusTrap = null;
|
|
4229
4792
|
lastFocusedElement = null;
|
|
4230
4793
|
constructor() {
|
|
4794
|
+
// Ver `modal-overlay-container.ts`: con `role="dialog"` los paneles
|
|
4795
|
+
// flotantes (select, dropdown) deben vivir dentro del diálogo.
|
|
4796
|
+
installModalAwareOverlayContainer(this.overlayContainer);
|
|
4797
|
+
afterRenderEffect(() => {
|
|
4798
|
+
const panel = this.panelRef().nativeElement;
|
|
4799
|
+
this.registeredPanel = panel;
|
|
4800
|
+
if (this.open() && this.role() === 'dialog') {
|
|
4801
|
+
registerOpenModalDialog(panel);
|
|
4802
|
+
}
|
|
4803
|
+
else {
|
|
4804
|
+
unregisterOpenModalDialog(panel);
|
|
4805
|
+
}
|
|
4806
|
+
});
|
|
4231
4807
|
afterRenderEffect(() => {
|
|
4232
4808
|
if (this.open()) {
|
|
4233
4809
|
this.lastFocusedElement = this.document.activeElement;
|
|
@@ -4250,7 +4826,12 @@ class Drawer {
|
|
|
4250
4826
|
}
|
|
4251
4827
|
});
|
|
4252
4828
|
}
|
|
4253
|
-
inject(DestroyRef).onDestroy(() =>
|
|
4829
|
+
inject(DestroyRef).onDestroy(() => {
|
|
4830
|
+
if (this.registeredPanel) {
|
|
4831
|
+
unregisterOpenModalDialog(this.registeredPanel);
|
|
4832
|
+
}
|
|
4833
|
+
this.focusTrap?.destroy();
|
|
4834
|
+
});
|
|
4254
4835
|
}
|
|
4255
4836
|
/** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
|
|
4256
4837
|
onEscape(event) {
|
|
@@ -4266,7 +4847,7 @@ class Drawer {
|
|
|
4266
4847
|
return this.focusTrap;
|
|
4267
4848
|
}
|
|
4268
4849
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4269
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Drawer, isStandalone: true, selector: "kf-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" }, properties: { "attr.role": "null", "attr.title": "null" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:
|
|
4850
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Drawer, isStandalone: true, selector: "kf-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape($event)" }, properties: { "attr.role": "null", "attr.title": "null" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:none;visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__panel--bottom.kf-drawer__panel--open{transform:none}@media(prefers-reduced-motion:reduce){.kf-drawer__panel,.kf-drawer__panel--open{transition:none}}.kf-drawer__grip{flex:none;align-self:center;width:36px;height:4px;margin-top:var(--kf-space-sm);border-radius:var(--kf-radius-pill);background:var(--kf-color-border-strong)}.kf-drawer__titlebar{flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-drawer__title{margin:0;min-width:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:700;line-height:1.25;color:var(--kf-color-ink)}.kf-drawer__close{flex:none}@media(pointer:coarse),(max-width:760px){.kf-drawer__close{--kf-button-min-height: 44px;min-width:44px}}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__panel--dialog .kf-drawer__body{gap:var(--kf-space-lg)}.kf-drawer__footer{flex:none;padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}.kf-drawer__panel--dialog .kf-drawer__footer{padding:var(--kf-space-md) var(--kf-space-lg)}\n"], dependencies: [{ kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }] });
|
|
4270
4851
|
}
|
|
4271
4852
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, decorators: [{
|
|
4272
4853
|
type: Component,
|
|
@@ -4277,7 +4858,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4277
4858
|
// un tooltip nativo sobre todo el panel. El rol real vive en el panel.
|
|
4278
4859
|
'[attr.role]': 'null',
|
|
4279
4860
|
'[attr.title]': 'null',
|
|
4280
|
-
}, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:
|
|
4861
|
+
}, template: "@if (open()) {\n <button\n type=\"button\"\n class=\"kf-drawer__overlay\"\n [attr.aria-label]=\"resolvedCloseLabel()\"\n [attr.tabindex]=\"role() === 'dialog' ? -1 : null\"\n (click)=\"open.set(false)\"\n ></button>\n}\n\n<!-- Un <div> con rol expl\u00EDcito (no <nav>): un mismo panel puede ser\n navegaci\u00F3n o di\u00E1logo, y <nav> no admite role=\"dialog\". -->\n<div\n #panel\n class=\"kf-drawer__panel\"\n [class.kf-drawer__panel--open]=\"open()\"\n [class.kf-drawer__panel--right]=\"side() === 'right'\"\n [class.kf-drawer__panel--bottom]=\"side() === 'bottom'\"\n [class.kf-drawer__panel--dialog]=\"role() === 'dialog'\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"role() === 'dialog' ? 'true' : null\"\n [attr.aria-label]=\"title() ? null : resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"title() ? titleId : null\"\n [attr.aria-hidden]=\"!open()\"\n>\n @if (side() === 'bottom') {\n <div class=\"kf-drawer__grip\" aria-hidden=\"true\"></div>\n }\n @if (title(); as titleText) {\n <div class=\"kf-drawer__titlebar\">\n <h2 class=\"kf-drawer__title\" [id]=\"titleId\">{{ titleText }}</h2>\n <kf-button\n class=\"kf-drawer__close\"\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n icon=\"x\"\n [ariaLabel]=\"resolvedCloseLabel()\"\n (click)=\"open.set(false)\"\n />\n </div>\n }\n <div class=\"kf-drawer__header\"><ng-content select=\"[kf-drawer-header]\" /></div>\n <div class=\"kf-drawer__body\"><ng-content /></div>\n <div class=\"kf-drawer__footer\"><ng-content select=\"[kf-drawer-footer]\" /></div>\n</div>\n", styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--dialog{width:min(var(--kf-drawer-width, 480px),100vw);max-width:none}@media(max-width:640px){.kf-drawer__panel--dialog{width:100vw}}.kf-drawer__panel--bottom{top:auto;right:0;width:100%;max-width:none;max-height:80vh;border-radius:var(--kf-radius-lg) var(--kf-radius-lg) 0 0;box-shadow:var(--kf-shadow-lg);padding-bottom:env(safe-area-inset-bottom,0px);transform:translateY(100%)}.kf-drawer__panel--open{transform:none;visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__panel--bottom.kf-drawer__panel--open{transform:none}@media(prefers-reduced-motion:reduce){.kf-drawer__panel,.kf-drawer__panel--open{transition:none}}.kf-drawer__grip{flex:none;align-self:center;width:36px;height:4px;margin-top:var(--kf-space-sm);border-radius:var(--kf-radius-pill);background:var(--kf-color-border-strong)}.kf-drawer__titlebar{flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-lg);border-bottom:1px solid var(--kf-color-border)}.kf-drawer__title{margin:0;min-width:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:700;line-height:1.25;color:var(--kf-color-ink)}.kf-drawer__close{flex:none}@media(pointer:coarse),(max-width:760px){.kf-drawer__close{--kf-button-min-height: 44px;min-width:44px}}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__panel--dialog .kf-drawer__body{gap:var(--kf-space-lg)}.kf-drawer__footer{flex:none;padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}.kf-drawer__panel--dialog .kf-drawer__footer{padding:var(--kf-space-md) var(--kf-space-lg)}\n"] }]
|
|
4281
4862
|
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onEscape: [{
|
|
4282
4863
|
type: HostListener,
|
|
4283
4864
|
args: ['document:keydown.escape', ['$event']]
|
|
@@ -4409,6 +4990,326 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4409
4990
|
}, template: "@if (icon(); as iconName) {\n <kf-icon class=\"kf-field__icon\" [name]=\"iconName\" size=\"sm\" />\n}\n@if (label()) {\n <span class=\"kf-field__label\">{{ label() }}</span>\n}\n<span class=\"kf-field__value\">\n @if (value()) {\n {{ value() }}\n } @else {\n <ng-content></ng-content>\n @if (isPlaceholderVisible()) {\n <span class=\"kf-field__placeholder\">{{ placeholder() }}</span>\n }\n }\n</span>\n@if (select()) {\n <kf-icon class=\"kf-field__chevron\" name=\"chevron-down\" size=\"sm\" />\n}\n", styles: [":host{display:inline-flex;align-items:center;gap:var(--kf-space-xs);padding:var(--kf-space-xs) var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);line-height:1.2}:host(.kf-field--select){cursor:pointer}:host(.kf-field--select:hover){border-color:var(--kf-color-border-strong)}.kf-field__icon{color:var(--kf-color-text-muted)}.kf-field__label{color:var(--kf-color-text-muted);white-space:nowrap}.kf-field__value{font-weight:500;color:var(--kf-color-ink);white-space:nowrap}.kf-field__placeholder{color:var(--kf-color-text-muted);font-weight:400}.kf-field__chevron{color:var(--kf-color-text-muted)}\n"] }]
|
|
4410
4991
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], select: [{ type: i0.Input, args: [{ isSignal: true, alias: "select", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }] } });
|
|
4411
4992
|
|
|
4993
|
+
let nextId$2 = 0;
|
|
4994
|
+
/** Nombre legible de un token de `accept`: `.pdf` → `PDF`, `image/png` → `PNG`, `image/*` → `Imágenes`. */
|
|
4995
|
+
function describeAcceptToken(token) {
|
|
4996
|
+
const generic = {
|
|
4997
|
+
'image/*': 'Imágenes',
|
|
4998
|
+
'video/*': 'Videos',
|
|
4999
|
+
'audio/*': 'Audio',
|
|
5000
|
+
'text/*': 'Texto',
|
|
5001
|
+
};
|
|
5002
|
+
if (generic[token]) {
|
|
5003
|
+
return generic[token];
|
|
5004
|
+
}
|
|
5005
|
+
if (token.startsWith('.')) {
|
|
5006
|
+
return token.slice(1).toUpperCase();
|
|
5007
|
+
}
|
|
5008
|
+
return (token.split('/')[1] ?? token).toUpperCase();
|
|
5009
|
+
}
|
|
5010
|
+
/** Tamaño en bytes como texto (`5 MB`, `120 KB`), base 1024. */
|
|
5011
|
+
function formatFileSize(bytes) {
|
|
5012
|
+
if (bytes >= 1024 * 1024) {
|
|
5013
|
+
return `${Number((bytes / (1024 * 1024)).toFixed(1))} MB`;
|
|
5014
|
+
}
|
|
5015
|
+
if (bytes >= 1024) {
|
|
5016
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
5017
|
+
}
|
|
5018
|
+
return `${bytes} B`;
|
|
5019
|
+
}
|
|
5020
|
+
/**
|
|
5021
|
+
* Selector de archivo accesible, con la misma apariencia y contrato de campo
|
|
5022
|
+
* que `kf-input`: label arriba, ayuda y error debajo, `invalid`.
|
|
5023
|
+
*
|
|
5024
|
+
* El control real es un `<input type="file">` nativo, visualmente oculto pero
|
|
5025
|
+
* enfocable: se llega con Tab, se abre con Enter o Espacio y el anillo de foco
|
|
5026
|
+
* se dibuja sobre el botón visible. Su nombre accesible es el `label` (o
|
|
5027
|
+
* `ariaLabel`); la ayuda, el archivo elegido y el error lo describen
|
|
5028
|
+
* (`aria-describedby`).
|
|
5029
|
+
*
|
|
5030
|
+
* **Ayuda antes de elegir.** `hint` es texto libre; si no se pasa, se arma sola
|
|
5031
|
+
* con los formatos de `accept` y el tamaño de `maxSize` (`PDF, JPG · máx. 5 MB`),
|
|
5032
|
+
* para que el usuario sepa qué sirve ANTES de abrir el selector.
|
|
5033
|
+
*
|
|
5034
|
+
* **Validación.** Cada archivo se revisa contra `accept` (extensiones `.pdf`,
|
|
5035
|
+
* tipos `image/png` o comodines `image/*`) y `maxSize` (bytes). Los válidos
|
|
5036
|
+
* quedan elegidos y se emiten en `fileChange` (siempre `File[]`, vacío al
|
|
5037
|
+
* quitar); los descartados se emiten uno a uno en `fileRejected` con su
|
|
5038
|
+
* motivo (`'type'` o `'size'`) y se muestran como error bajo el campo
|
|
5039
|
+
* (`rejectedTypeText` / `rejectedSizeText`); la ayuda sigue visible bajo ese
|
|
5040
|
+
* mensaje, para que se vea qué sí sirve. Un `error` del consumidor tiene
|
|
5041
|
+
* prioridad sobre ese mensaje y reemplaza a la ayuda, igual que `errorMessage`
|
|
5042
|
+
* sobre `hint` en `kf-input`. La validación del navegador no reemplaza la del servidor.
|
|
5043
|
+
*
|
|
5044
|
+
* **Archivo elegido.** Su nombre y tamaño aparecen UNA vez, con un botón para
|
|
5045
|
+
* quitarlo (`removeLabel`, con el nombre del archivo en su `aria-label`); el
|
|
5046
|
+
* nombre del `<input>` nativo queda oculto. Con `multiple` se listan todos y
|
|
5047
|
+
* cada uno se quita por separado. Elegir de nuevo reemplaza la selección.
|
|
5048
|
+
*
|
|
5049
|
+
* `dropzone` agrega arrastrar y soltar sobre el área; el botón y el teclado
|
|
5050
|
+
* siguen siendo la vía principal.
|
|
5051
|
+
*
|
|
5052
|
+
* Es un `ControlValueAccessor`: con `[(ngModel)]`/`formControl` el valor es
|
|
5053
|
+
* `File | null` (o `File[]` con `multiple`); `writeValue(null)` limpia.
|
|
5054
|
+
*
|
|
5055
|
+
* <kf-file-input label="Comprobante" accept=".pdf,.jpg,.png" [maxSize]="5242880"
|
|
5056
|
+
* (fileChange)="onFiles($event)" (fileRejected)="onRejected($event)" />
|
|
5057
|
+
* <kf-file-input label="Fotos" accept="image/*" multiple dropzone formControlName="photos" />
|
|
5058
|
+
* <kf-file-input label="Contrato" error="Adjunta el contrato firmado." formControlName="contract" />
|
|
5059
|
+
*/
|
|
5060
|
+
class FileInput {
|
|
5061
|
+
nativeInput = viewChild.required('nativeInput', /* @ts-ignore */
|
|
5062
|
+
...(ngDevMode ? [{ debugName: "nativeInput" }] : /* istanbul ignore next */ []));
|
|
5063
|
+
label = input(/* @ts-ignore */
|
|
5064
|
+
...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
5065
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5066
|
+
/** Texto de ayuda. Sin valor, se arma con `accept` y `maxSize`. */
|
|
5067
|
+
hint = input(/* @ts-ignore */
|
|
5068
|
+
...(ngDevMode ? [undefined, { debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
5069
|
+
/** Formatos admitidos, como el `accept` nativo: `.pdf,.jpg`, `image/png`, `image/*`. */
|
|
5070
|
+
accept = input(/* @ts-ignore */
|
|
5071
|
+
...(ngDevMode ? [undefined, { debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
5072
|
+
/** Tamaño máximo por archivo, en bytes. Sin valor, sin límite. */
|
|
5073
|
+
maxSize = input(undefined, /* @ts-ignore */
|
|
5074
|
+
...(ngDevMode ? [{ debugName: "maxSize" }] : /* istanbul ignore next */ []));
|
|
5075
|
+
multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5076
|
+
/** Agrega arrastrar y soltar sobre el área del control. */
|
|
5077
|
+
dropzone = input(false, { ...(ngDevMode ? { debugName: "dropzone" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5078
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5079
|
+
/** Borde rojo y `aria-invalid`. Se activa solo con `error`; úsalo para marcar un error sin mensaje. */
|
|
5080
|
+
invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5081
|
+
/** Mensaje de error del consumidor (`role="alert"`); reemplaza a la ayuda y al motivo de rechazo, como `errorMessage` en `kf-input`. */
|
|
5082
|
+
error = input(/* @ts-ignore */
|
|
5083
|
+
...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
|
|
5084
|
+
/** Nombre accesible cuando no hay `label` visible. */
|
|
5085
|
+
ariaLabel = input(/* @ts-ignore */
|
|
5086
|
+
...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
5087
|
+
/** Texto del botón cuando no hay archivo elegido. */
|
|
5088
|
+
buttonLabel = input('Elegir archivo', /* @ts-ignore */
|
|
5089
|
+
...(ngDevMode ? [{ debugName: "buttonLabel" }] : /* istanbul ignore next */ []));
|
|
5090
|
+
/** Texto del botón con `multiple` o cuando ya hay un archivo (cambiar). */
|
|
5091
|
+
changeLabel = input('Cambiar archivo', /* @ts-ignore */
|
|
5092
|
+
...(ngDevMode ? [{ debugName: "changeLabel" }] : /* istanbul ignore next */ []));
|
|
5093
|
+
/** Nombre accesible del botón de quitar; se le agrega el nombre del archivo. */
|
|
5094
|
+
removeLabel = input('Quitar', /* @ts-ignore */
|
|
5095
|
+
...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
|
|
5096
|
+
/** Texto de arrastrar y soltar (solo con `dropzone`). */
|
|
5097
|
+
dropzoneText = input('o arrástralo aquí', /* @ts-ignore */
|
|
5098
|
+
...(ngDevMode ? [{ debugName: "dropzoneText" }] : /* istanbul ignore next */ []));
|
|
5099
|
+
rejectedTypeText = input('Formato no permitido.', /* @ts-ignore */
|
|
5100
|
+
...(ngDevMode ? [{ debugName: "rejectedTypeText" }] : /* istanbul ignore next */ []));
|
|
5101
|
+
rejectedSizeText = input('El archivo supera el tamaño máximo.', /* @ts-ignore */
|
|
5102
|
+
...(ngDevMode ? [{ debugName: "rejectedSizeText" }] : /* istanbul ignore next */ []));
|
|
5103
|
+
/** Selección vigente (`File[]`, vacío al quitar todo). */
|
|
5104
|
+
fileChange = output();
|
|
5105
|
+
/** Un archivo descartado y su motivo. */
|
|
5106
|
+
fileRejected = output();
|
|
5107
|
+
inputId = `kf-file-input-${nextId$2++}`;
|
|
5108
|
+
hintId = `${this.inputId}-hint`;
|
|
5109
|
+
errorId = `${this.inputId}-error`;
|
|
5110
|
+
filesId = `${this.inputId}-files`;
|
|
5111
|
+
files = signal([], /* @ts-ignore */
|
|
5112
|
+
...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
|
|
5113
|
+
rejection = signal(null, /* @ts-ignore */
|
|
5114
|
+
...(ngDevMode ? [{ debugName: "rejection" }] : /* istanbul ignore next */ []));
|
|
5115
|
+
dragging = signal(false, /* @ts-ignore */
|
|
5116
|
+
...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
|
|
5117
|
+
disabledByForm = signal(false, /* @ts-ignore */
|
|
5118
|
+
...(ngDevMode ? [{ debugName: "disabledByForm" }] : /* istanbul ignore next */ []));
|
|
5119
|
+
isDisabled = computed(() => this.disabled() || this.disabledByForm(), /* @ts-ignore */
|
|
5120
|
+
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
5121
|
+
onChange = () => { };
|
|
5122
|
+
onTouched = () => { };
|
|
5123
|
+
/** Mensaje visible: el del consumidor, o el motivo del último rechazo. */
|
|
5124
|
+
message = computed(() => {
|
|
5125
|
+
const own = this.error();
|
|
5126
|
+
if (own) {
|
|
5127
|
+
return own;
|
|
5128
|
+
}
|
|
5129
|
+
const reason = this.rejection();
|
|
5130
|
+
if (reason === 'type') {
|
|
5131
|
+
return this.rejectedTypeText();
|
|
5132
|
+
}
|
|
5133
|
+
return reason === 'size' ? this.rejectedSizeText() : null;
|
|
5134
|
+
}, /* @ts-ignore */
|
|
5135
|
+
...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
5136
|
+
/** Ayuda efectiva: la del consumidor o la armada con formatos y tamaño. */
|
|
5137
|
+
effectiveHint = computed(() => {
|
|
5138
|
+
const own = this.hint();
|
|
5139
|
+
if (own) {
|
|
5140
|
+
return own;
|
|
5141
|
+
}
|
|
5142
|
+
const parts = [];
|
|
5143
|
+
const tokens = (this.accept() ?? '')
|
|
5144
|
+
.split(',')
|
|
5145
|
+
.map((token) => token.trim())
|
|
5146
|
+
.filter(Boolean);
|
|
5147
|
+
if (tokens.length) {
|
|
5148
|
+
parts.push([...new Set(tokens.map(describeAcceptToken))].join(', '));
|
|
5149
|
+
}
|
|
5150
|
+
const max = this.maxSize();
|
|
5151
|
+
if (max) {
|
|
5152
|
+
parts.push(`máx. ${formatFileSize(max)}`);
|
|
5153
|
+
}
|
|
5154
|
+
return parts.join(' · ');
|
|
5155
|
+
}, /* @ts-ignore */
|
|
5156
|
+
...(ngDevMode ? [{ debugName: "effectiveHint" }] : /* istanbul ignore next */ []));
|
|
5157
|
+
isInvalid = computed(() => this.invalid() || !!this.message(), /* @ts-ignore */
|
|
5158
|
+
...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
|
|
5159
|
+
describedBy = computed(() => {
|
|
5160
|
+
const ids = [];
|
|
5161
|
+
if (this.message()) {
|
|
5162
|
+
ids.push(this.errorId);
|
|
5163
|
+
}
|
|
5164
|
+
if (this.effectiveHint() && !this.error()) {
|
|
5165
|
+
ids.push(this.hintId);
|
|
5166
|
+
}
|
|
5167
|
+
if (this.files().length) {
|
|
5168
|
+
ids.push(this.filesId);
|
|
5169
|
+
}
|
|
5170
|
+
return ids.join(' ') || null;
|
|
5171
|
+
}, /* @ts-ignore */
|
|
5172
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
5173
|
+
triggerText = computed(() => this.files().length && !this.multiple() ? this.changeLabel() : this.buttonLabel(), /* @ts-ignore */
|
|
5174
|
+
...(ngDevMode ? [{ debugName: "triggerText" }] : /* istanbul ignore next */ []));
|
|
5175
|
+
/** Enfoca el `<input>` nativo. */
|
|
5176
|
+
focus(options) {
|
|
5177
|
+
this.nativeInput().nativeElement.focus(options);
|
|
5178
|
+
}
|
|
5179
|
+
writeValue(value) {
|
|
5180
|
+
const list = Array.isArray(value) ? value : value ? [value] : [];
|
|
5181
|
+
this.files.set(list);
|
|
5182
|
+
this.rejection.set(null);
|
|
5183
|
+
}
|
|
5184
|
+
registerOnChange(fn) {
|
|
5185
|
+
this.onChange = fn;
|
|
5186
|
+
}
|
|
5187
|
+
registerOnTouched(fn) {
|
|
5188
|
+
this.onTouched = fn;
|
|
5189
|
+
}
|
|
5190
|
+
setDisabledState(isDisabled) {
|
|
5191
|
+
this.disabledByForm.set(isDisabled);
|
|
5192
|
+
}
|
|
5193
|
+
onPick(event) {
|
|
5194
|
+
const native = event.target;
|
|
5195
|
+
const picked = Array.from(native.files ?? []);
|
|
5196
|
+
// Se limpia el valor nativo: el nombre ya se muestra en la lista y así se
|
|
5197
|
+
// puede volver a elegir el mismo archivo después de quitarlo.
|
|
5198
|
+
native.value = '';
|
|
5199
|
+
this.receive(picked);
|
|
5200
|
+
}
|
|
5201
|
+
onDragOver(event) {
|
|
5202
|
+
if (this.isDisabled()) {
|
|
5203
|
+
return;
|
|
5204
|
+
}
|
|
5205
|
+
event.preventDefault();
|
|
5206
|
+
this.dragging.set(true);
|
|
5207
|
+
}
|
|
5208
|
+
onDragLeave() {
|
|
5209
|
+
this.dragging.set(false);
|
|
5210
|
+
}
|
|
5211
|
+
onDrop(event) {
|
|
5212
|
+
event.preventDefault();
|
|
5213
|
+
this.dragging.set(false);
|
|
5214
|
+
if (this.isDisabled()) {
|
|
5215
|
+
return;
|
|
5216
|
+
}
|
|
5217
|
+
this.receive(Array.from(event.dataTransfer?.files ?? []));
|
|
5218
|
+
}
|
|
5219
|
+
remove(index) {
|
|
5220
|
+
const next = this.files().filter((_, i) => i !== index);
|
|
5221
|
+
this.rejection.set(null);
|
|
5222
|
+
this.commit(next);
|
|
5223
|
+
this.nativeInput().nativeElement.focus();
|
|
5224
|
+
}
|
|
5225
|
+
formatSize(file) {
|
|
5226
|
+
return formatFileSize(file.size);
|
|
5227
|
+
}
|
|
5228
|
+
/** Valida lo recibido, emite los rechazos y reemplaza la selección si hay algo válido. */
|
|
5229
|
+
receive(incoming) {
|
|
5230
|
+
if (!incoming.length) {
|
|
5231
|
+
return;
|
|
5232
|
+
}
|
|
5233
|
+
const candidates = this.multiple() ? incoming : incoming.slice(0, 1);
|
|
5234
|
+
const accepted = [];
|
|
5235
|
+
let lastReason = null;
|
|
5236
|
+
for (const file of candidates) {
|
|
5237
|
+
const reason = this.reject(file);
|
|
5238
|
+
if (reason) {
|
|
5239
|
+
lastReason = reason;
|
|
5240
|
+
this.fileRejected.emit({ file, reason });
|
|
5241
|
+
}
|
|
5242
|
+
else {
|
|
5243
|
+
accepted.push(file);
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
this.rejection.set(lastReason);
|
|
5247
|
+
if (accepted.length) {
|
|
5248
|
+
this.commit(accepted);
|
|
5249
|
+
}
|
|
5250
|
+
this.onTouched();
|
|
5251
|
+
}
|
|
5252
|
+
reject(file) {
|
|
5253
|
+
if (!this.matchesAccept(file)) {
|
|
5254
|
+
return 'type';
|
|
5255
|
+
}
|
|
5256
|
+
const max = this.maxSize();
|
|
5257
|
+
return max !== undefined && file.size > max ? 'size' : null;
|
|
5258
|
+
}
|
|
5259
|
+
matchesAccept(file) {
|
|
5260
|
+
const tokens = (this.accept() ?? '')
|
|
5261
|
+
.split(',')
|
|
5262
|
+
.map((token) => token.trim().toLowerCase())
|
|
5263
|
+
.filter(Boolean);
|
|
5264
|
+
if (!tokens.length) {
|
|
5265
|
+
return true;
|
|
5266
|
+
}
|
|
5267
|
+
const name = file.name.toLowerCase();
|
|
5268
|
+
const type = file.type.toLowerCase();
|
|
5269
|
+
return tokens.some((token) => {
|
|
5270
|
+
if (token.startsWith('.')) {
|
|
5271
|
+
return name.endsWith(token);
|
|
5272
|
+
}
|
|
5273
|
+
if (token.endsWith('/*')) {
|
|
5274
|
+
return type.startsWith(token.slice(0, -1));
|
|
5275
|
+
}
|
|
5276
|
+
return type === token;
|
|
5277
|
+
});
|
|
5278
|
+
}
|
|
5279
|
+
commit(files) {
|
|
5280
|
+
this.files.set(files);
|
|
5281
|
+
this.fileChange.emit(files);
|
|
5282
|
+
this.onChange(this.multiple() ? files : (files[0] ?? null));
|
|
5283
|
+
}
|
|
5284
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5285
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FileInput, isStandalone: true, selector: "kf-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, dropzone: { classPropertyName: "dropzone", publicName: "dropzone", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, changeLabel: { classPropertyName: "changeLabel", publicName: "changeLabel", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null }, dropzoneText: { classPropertyName: "dropzoneText", publicName: "dropzoneText", isSignal: true, isRequired: false, transformFunction: null }, rejectedTypeText: { classPropertyName: "rejectedTypeText", publicName: "rejectedTypeText", isSignal: true, isRequired: false, transformFunction: null }, rejectedSizeText: { classPropertyName: "rejectedSizeText", publicName: "rejectedSizeText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileChange: "fileChange", fileRejected: "fileRejected" }, providers: [
|
|
5286
|
+
{
|
|
5287
|
+
provide: NG_VALUE_ACCESSOR,
|
|
5288
|
+
useExisting: forwardRef(() => FileInput),
|
|
5289
|
+
multi: true,
|
|
5290
|
+
},
|
|
5291
|
+
], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-file-input__area\"\n [class.kf-file-input__area--dropzone]=\"dropzone()\"\n [class.kf-file-input__area--dragging]=\"dragging()\"\n [class.kf-file-input__area--invalid]=\"isInvalid()\"\n [class.kf-file-input__area--disabled]=\"isDisabled()\"\n (dragover)=\"dropzone() ? onDragOver($event) : null\"\n (dragleave)=\"dropzone() ? onDragLeave() : null\"\n (drop)=\"dropzone() ? onDrop($event) : null\"\n>\n <input\n #nativeInput\n type=\"file\"\n class=\"kf-file-input__native\"\n [id]=\"inputId\"\n [attr.accept]=\"accept() || null\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required() && !files().length\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n (change)=\"onPick($event)\"\n />\n <label class=\"kf-file-input__button\" [for]=\"inputId\" aria-hidden=\"true\">\n <kf-icon name=\"upload-cloud\" size=\"sm\" />\n {{ triggerText() }}\n </label>\n @if (dropzone()) {\n <span class=\"kf-file-input__drop-text\" aria-hidden=\"true\">{{ dropzoneText() }}</span>\n }\n</div>\n@if (files().length) {\n <ul class=\"kf-file-input__files\" [id]=\"filesId\">\n @for (file of files(); track $index) {\n <li class=\"kf-file-input__file\">\n <kf-icon class=\"kf-file-input__file-icon\" name=\"file\" size=\"sm\" />\n <span class=\"kf-file-input__file-name\">{{ file.name }}</span>\n <span class=\"kf-file-input__file-size\">{{ formatSize(file) }}</span>\n <button\n type=\"button\"\n class=\"kf-file-input__remove\"\n [attr.aria-label]=\"removeLabel() + ' ' + file.name\"\n [disabled]=\"isDisabled()\"\n (click)=\"remove($index)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n}\n@if (message(); as text) {\n <span class=\"kf-file-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ text }}</span>\n}\n@if (effectiveHint() && !error()) {\n <span class=\"kf-file-input__hint kf-field-hint\" [id]=\"hintId\">{{ effectiveHint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-file-input__area{position:relative;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-file-input__native{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}.kf-file-input__native:disabled{cursor:not-allowed}.kf-file-input__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);min-height:2.25rem;padding:var(--kf-space-sm) var(--kf-space-md);box-sizing:border-box;border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary-active);font:inherit;font-weight:600;pointer-events:none;transition:background .14s,box-shadow .14s}.kf-file-input__area:hover .kf-file-input__native:not(:disabled)+.kf-file-input__button{background:var(--kf-color-primary-soft)}.kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus);outline:2px solid transparent}.kf-file-input__area--invalid .kf-file-input__button{border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-file-input__area--invalid .kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus-error)}.kf-file-input__area--disabled{opacity:.5}.kf-file-input__area--dropzone{flex-direction:column;justify-content:center;gap:var(--kf-space-xs);padding:var(--kf-space-lg) var(--kf-space-md);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);transition:background .14s,border-color .14s}.kf-file-input__area--dragging{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-file-input__area--dropzone.kf-file-input__area--invalid{border-color:var(--kf-color-error)}.kf-file-input__drop-text{font-size:.875rem;color:var(--kf-color-text);pointer-events:none}.kf-file-input__files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-file-input__file{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-xs) var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-file-input__file-icon{flex:none;color:var(--kf-color-text-muted)}.kf-file-input__file-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--kf-color-ink);font-weight:600}.kf-file-input__file-size{flex:none;font-size:.75rem;color:var(--kf-color-text)}.kf-file-input__remove{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:2rem;min-height:2rem;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-color-text);cursor:pointer}.kf-file-input__remove:hover:not(:disabled){background:var(--kf-color-surface);color:var(--kf-color-ink)}.kf-file-input__remove:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-file-input__hint{color:var(--kf-color-text)}@media(pointer:coarse),(max-width:760px){.kf-file-input__button{min-height:44px}.kf-file-input__remove{min-width:44px;min-height:44px}}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5292
|
+
}
|
|
5293
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileInput, decorators: [{
|
|
5294
|
+
type: Component,
|
|
5295
|
+
args: [{ selector: 'kf-file-input', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FieldLabel, Icon], providers: [
|
|
5296
|
+
{
|
|
5297
|
+
provide: NG_VALUE_ACCESSOR,
|
|
5298
|
+
useExisting: forwardRef(() => FileInput),
|
|
5299
|
+
multi: true,
|
|
5300
|
+
},
|
|
5301
|
+
], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-file-input__area\"\n [class.kf-file-input__area--dropzone]=\"dropzone()\"\n [class.kf-file-input__area--dragging]=\"dragging()\"\n [class.kf-file-input__area--invalid]=\"isInvalid()\"\n [class.kf-file-input__area--disabled]=\"isDisabled()\"\n (dragover)=\"dropzone() ? onDragOver($event) : null\"\n (dragleave)=\"dropzone() ? onDragLeave() : null\"\n (drop)=\"dropzone() ? onDrop($event) : null\"\n>\n <input\n #nativeInput\n type=\"file\"\n class=\"kf-file-input__native\"\n [id]=\"inputId\"\n [attr.accept]=\"accept() || null\"\n [multiple]=\"multiple()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required() && !files().length\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"isInvalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n (change)=\"onPick($event)\"\n />\n <label class=\"kf-file-input__button\" [for]=\"inputId\" aria-hidden=\"true\">\n <kf-icon name=\"upload-cloud\" size=\"sm\" />\n {{ triggerText() }}\n </label>\n @if (dropzone()) {\n <span class=\"kf-file-input__drop-text\" aria-hidden=\"true\">{{ dropzoneText() }}</span>\n }\n</div>\n@if (files().length) {\n <ul class=\"kf-file-input__files\" [id]=\"filesId\">\n @for (file of files(); track $index) {\n <li class=\"kf-file-input__file\">\n <kf-icon class=\"kf-file-input__file-icon\" name=\"file\" size=\"sm\" />\n <span class=\"kf-file-input__file-name\">{{ file.name }}</span>\n <span class=\"kf-file-input__file-size\">{{ formatSize(file) }}</span>\n <button\n type=\"button\"\n class=\"kf-file-input__remove\"\n [attr.aria-label]=\"removeLabel() + ' ' + file.name\"\n [disabled]=\"isDisabled()\"\n (click)=\"remove($index)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n </li>\n }\n </ul>\n}\n@if (message(); as text) {\n <span class=\"kf-file-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ text }}</span>\n}\n@if (effectiveHint() && !error()) {\n <span class=\"kf-file-input__hint kf-field-hint\" [id]=\"hintId\">{{ effectiveHint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-file-input__area{position:relative;display:flex;align-items:center;gap:var(--kf-space-sm)}.kf-file-input__native{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}.kf-file-input__native:disabled{cursor:not-allowed}.kf-file-input__button{display:inline-flex;align-items:center;justify-content:center;gap:var(--kf-space-xs);min-height:2.25rem;padding:var(--kf-space-sm) var(--kf-space-md);box-sizing:border-box;border:1px solid var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary-active);font:inherit;font-weight:600;pointer-events:none;transition:background .14s,box-shadow .14s}.kf-file-input__area:hover .kf-file-input__native:not(:disabled)+.kf-file-input__button{background:var(--kf-color-primary-soft)}.kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus);outline:2px solid transparent}.kf-file-input__area--invalid .kf-file-input__button{border-color:var(--kf-color-error);color:var(--kf-color-error-strong)}.kf-file-input__area--invalid .kf-file-input__native:focus-visible+.kf-file-input__button{box-shadow:var(--kf-shadow-focus-error)}.kf-file-input__area--disabled{opacity:.5}.kf-file-input__area--dropzone{flex-direction:column;justify-content:center;gap:var(--kf-space-xs);padding:var(--kf-space-lg) var(--kf-space-md);border:1px dashed var(--kf-color-border-strong);border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);transition:background .14s,border-color .14s}.kf-file-input__area--dragging{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-file-input__area--dropzone.kf-file-input__area--invalid{border-color:var(--kf-color-error)}.kf-file-input__drop-text{font-size:.875rem;color:var(--kf-color-text);pointer-events:none}.kf-file-input__files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-xs)}.kf-file-input__file{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-xs) var(--kf-space-sm);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);min-width:0}.kf-file-input__file-icon{flex:none;color:var(--kf-color-text-muted)}.kf-file-input__file-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--kf-color-ink);font-weight:600}.kf-file-input__file-size{flex:none;font-size:.75rem;color:var(--kf-color-text)}.kf-file-input__remove{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:2rem;min-height:2rem;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-color-text);cursor:pointer}.kf-file-input__remove:hover:not(:disabled){background:var(--kf-color-surface);color:var(--kf-color-ink)}.kf-file-input__remove:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-file-input__hint{color:var(--kf-color-text)}@media(pointer:coarse),(max-width:760px){.kf-file-input__button{min-height:44px}.kf-file-input__remove{min-width:44px;min-height:44px}}\n"] }]
|
|
5302
|
+
}], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], dropzone: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropzone", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], buttonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLabel", required: false }] }], changeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "changeLabel", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], dropzoneText: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropzoneText", required: false }] }], rejectedTypeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rejectedTypeText", required: false }] }], rejectedSizeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rejectedSizeText", required: false }] }], fileChange: [{ type: i0.Output, args: ["fileChange"] }], fileRejected: [{ type: i0.Output, args: ["fileRejected"] }] } });
|
|
5303
|
+
|
|
5304
|
+
/** Vacío o `undefined` no emiten el atributo; `0` sí es un límite válido. */
|
|
5305
|
+
function normalizeBound(value) {
|
|
5306
|
+
return value === undefined || value === '' ? null : value;
|
|
5307
|
+
}
|
|
5308
|
+
/** `maxlength` como entero positivo; cualquier otra cosa (vacío, NaN, 0, negativo) no se emite. */
|
|
5309
|
+
function toMaxLength(value) {
|
|
5310
|
+
const parsed = typeof value === 'string' ? Number.parseInt(value, 10) : value;
|
|
5311
|
+
return typeof parsed === 'number' && Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
5312
|
+
}
|
|
4412
5313
|
/**
|
|
4413
5314
|
* Input de formulario apilado: label arriba (en mayúsculas vía CSS), campo con
|
|
4414
5315
|
* fondo gris que pasa a blanco con anillo de foco azul al enfocar.
|
|
@@ -4472,6 +5373,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4472
5373
|
*
|
|
4473
5374
|
* <kf-input label="RUT" autocomplete="username" name="rut" inputmode="text" formControlName="rut" />
|
|
4474
5375
|
* <kf-input type="password" label="Contraseña" toggleable autocomplete="current-password" name="password" formControlName="password" />
|
|
5376
|
+
*
|
|
5377
|
+
* `min`, `max`, `step` y `maxlength` se reenvían al `<input>` nativo (mismo
|
|
5378
|
+
* motivo: un atributo en el host no llega adentro) y sin valor no se
|
|
5379
|
+
* renderiza ningún atributo. `min`/`max`/`step` aceptan número o texto según
|
|
5380
|
+
* el `type` (`number`, `range`, `date`, `time`, `month`…): con `type="date"` el
|
|
5381
|
+
* límite es una fecha ISO. `maxlength` es un entero positivo y aplica a los
|
|
5382
|
+
* tipos de texto (`text`, `search`, `email`, `tel`, `url`, `password`). El
|
|
5383
|
+
* navegador usa los límites para la validación nativa (`:invalid`,
|
|
5384
|
+
* `validity.rangeUnderflow`) y para el selector de fechas; no reemplazan la
|
|
5385
|
+
* validación del formulario ni el `errorMessage`.
|
|
5386
|
+
*
|
|
5387
|
+
* <kf-input type="date" label="Fecha de entrega" min="2026-01-01" max="2026-12-31" />
|
|
5388
|
+
* <kf-input type="number" label="Cantidad" [min]="1" [max]="20" [step]="1" />
|
|
5389
|
+
* <kf-input label="Placa" [maxlength]="7" />
|
|
4475
5390
|
*/
|
|
4476
5391
|
class Input {
|
|
4477
5392
|
static nextId = 0;
|
|
@@ -4521,6 +5436,14 @@ class Input {
|
|
|
4521
5436
|
* el `type`. Sin valor, el atributo no se renderiza. */
|
|
4522
5437
|
inputmode = input(/* @ts-ignore */
|
|
4523
5438
|
...(ngDevMode ? [undefined, { debugName: "inputmode" }] : /* istanbul ignore next */ []));
|
|
5439
|
+
/** Límite inferior del control (`min` nativo): número, o texto para fechas/horas (`'2026-01-01'`). */
|
|
5440
|
+
min = input(null, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: normalizeBound });
|
|
5441
|
+
/** Límite superior del control (`max` nativo). Ver `min`. */
|
|
5442
|
+
max = input(null, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: normalizeBound });
|
|
5443
|
+
/** Paso del control (`step` nativo): número o `'any'`. */
|
|
5444
|
+
step = input(null, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: normalizeBound });
|
|
5445
|
+
/** Largo máximo del texto (`maxlength` nativo), entero positivo. Solo aplica a tipos de texto. */
|
|
5446
|
+
maxlength = input(null, { ...(ngDevMode ? { debugName: "maxlength" } : /* istanbul ignore next */ {}), transform: toMaxLength });
|
|
4524
5447
|
/** Agrega el botón de mostrar/ocultar — sin efecto si `type` no es `"password"`. */
|
|
4525
5448
|
toggleable = input(false, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4526
5449
|
/** Nombre accesible del `<input>` cuando no hay `label` visible. Ignorado
|
|
@@ -4630,13 +5553,13 @@ class Input {
|
|
|
4630
5553
|
this.passwordVisible.update((visible) => !visible);
|
|
4631
5554
|
}
|
|
4632
5555
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4633
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Input, isStandalone: true, selector: "kf-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared" }, providers: [
|
|
5556
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Input, isStandalone: true, selector: "kf-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cleared: "cleared" }, providers: [
|
|
4634
5557
|
{
|
|
4635
5558
|
provide: NG_VALUE_ACCESSOR,
|
|
4636
5559
|
useExisting: forwardRef(() => Input),
|
|
4637
5560
|
multi: true,
|
|
4638
5561
|
},
|
|
4639
|
-
], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5562
|
+
], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.min]=\"min()\"\n [attr.max]=\"max()\"\n [attr.step]=\"step()\"\n [attr.maxlength]=\"maxlength()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4640
5563
|
}
|
|
4641
5564
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Input, decorators: [{
|
|
4642
5565
|
type: Component,
|
|
@@ -4646,8 +5569,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4646
5569
|
useExisting: forwardRef(() => Input),
|
|
4647
5570
|
multi: true,
|
|
4648
5571
|
},
|
|
4649
|
-
], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
|
|
4650
|
-
}], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
|
|
5572
|
+
], template: "@if (label()) {\n <kf-field-label [for]=\"inputId\" [required]=\"required()\">{{ label() }}</kf-field-label>\n}\n<div\n class=\"kf-input__field\"\n [class.kf-input__field--disabled]=\"isDisabled()\"\n [class.kf-input__field--readonly]=\"readonly()\"\n [class.kf-input__field--focused]=\"focused()\"\n [class.kf-input__field--invalid]=\"invalid()\"\n>\n @if (icon(); as iconName) {\n <kf-icon class=\"kf-input__icon\" [name]=\"iconName\" size=\"sm\" />\n }\n <input\n #nativeInput\n [id]=\"inputId\"\n class=\"kf-input__control\"\n [type]=\"effectiveType()\"\n [placeholder]=\"placeholder()\"\n [attr.autocomplete]=\"autocomplete() || null\"\n [attr.name]=\"name() || null\"\n [attr.inputmode]=\"inputmode() || null\"\n [attr.min]=\"min()\"\n [attr.max]=\"max()\"\n [attr.step]=\"step()\"\n [attr.maxlength]=\"maxlength()\"\n [disabled]=\"isDisabled()\"\n [required]=\"required()\"\n [attr.readonly]=\"readonly() ? true : null\"\n [attr.aria-describedby]=\"errorMessage() ? errorId : hint() ? hintId : null\"\n [attr.aria-invalid]=\"invalid() ? true : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [value]=\"value()\"\n (input)=\"onInput(nativeInput.value)\"\n (blur)=\"onBlur()\"\n (focus)=\"focused.set(true)\"\n />\n @if (showClear()) {\n <button\n type=\"button\"\n class=\"kf-input__clear\"\n [attr.aria-label]=\"clearLabel()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onClear()\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n }\n @if (showPasswordToggle()) {\n <button\n type=\"button\"\n class=\"kf-input__toggle\"\n [attr.aria-label]=\"passwordVisible() ? 'Ocultar contrase\u00F1a' : 'Mostrar contrase\u00F1a'\"\n [attr.aria-pressed]=\"passwordVisible()\"\n (click)=\"onTogglePasswordVisibility()\"\n >\n <kf-icon [name]=\"passwordVisible() ? 'eye-off' : 'eye'\" size=\"sm\" />\n </button>\n } @else if (suffix()) {\n <span class=\"kf-input__suffix\">{{ suffix() }}</span>\n }\n</div>\n@if (errorMessage(); as message) {\n <span class=\"kf-input__error kf-field-error\" [id]=\"errorId\" role=\"alert\">{{ message }}</span>\n} @else if (hint()) {\n <span class=\"kf-input__hint kf-field-hint\" [id]=\"hintId\">{{ hint() }}</span>\n}\n", styles: [".kf-field-hint{font-size:.75rem;color:var(--kf-color-text-muted)}.kf-field-error{display:flex;align-items:center;gap:var(--kf-space-xs);font-size:.75rem;font-weight:600;color:var(--kf-color-error-strong)}:host{display:flex;flex-direction:column;gap:var(--kf-space-xs);font-family:var(--kf-font-body)}.kf-input__field{display:flex;align-items:center;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);transition:background .14s,border-color .14s,box-shadow .14s}.kf-input__field--focused{background:var(--kf-color-white);border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-input__field--invalid{border-color:var(--kf-color-error)}.kf-input__field--invalid.kf-input__field--focused{box-shadow:var(--kf-shadow-focus-error)}.kf-input__field--disabled{opacity:.5}.kf-input__field--readonly{background:var(--kf-color-gray-100)}.kf-input__control:read-only{cursor:default}.kf-input__icon{color:var(--kf-color-text-muted)}.kf-input__field--invalid .kf-input__icon{color:var(--kf-color-error)}.kf-input__control{flex:1;min-width:0;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-text);padding:var(--kf-space-sm) 0}.kf-input__control::placeholder{color:var(--kf-color-text-muted)}.kf-input__control:disabled{cursor:not-allowed}.kf-input__suffix{flex:none;color:var(--kf-color-text-muted);font-size:.875rem}.kf-input__control::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.kf-input__clear,.kf-input__toggle{flex:none;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:var(--kf-space-xs);margin:0 calc(var(--kf-space-xs) * -1);color:var(--kf-color-text-muted);cursor:pointer;border-radius:var(--kf-radius-xs);transition:color .14s}.kf-input__clear:hover,.kf-input__toggle:hover{color:var(--kf-color-text)}.kf-input__clear:focus-visible,.kf-input__toggle:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
|
|
5573
|
+
}], propDecorators: { nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
|
|
4651
5574
|
|
|
4652
5575
|
let nextId$1 = 0;
|
|
4653
5576
|
/**
|
|
@@ -4775,7 +5698,11 @@ class MultiSelect {
|
|
|
4775
5698
|
this.searchInputRef()?.nativeElement.focus();
|
|
4776
5699
|
}
|
|
4777
5700
|
});
|
|
4778
|
-
inject(
|
|
5701
|
+
const stopEscape = listenEscapeCapture(inject(DOCUMENT), () => this.onEscape());
|
|
5702
|
+
inject(DestroyRef).onDestroy(() => {
|
|
5703
|
+
stopEscape();
|
|
5704
|
+
this.overlay.dispose();
|
|
5705
|
+
});
|
|
4779
5706
|
}
|
|
4780
5707
|
isSelected(value) {
|
|
4781
5708
|
return this.values().includes(value);
|
|
@@ -4822,11 +5749,21 @@ class MultiSelect {
|
|
|
4822
5749
|
this.toggleOption(option.value);
|
|
4823
5750
|
}
|
|
4824
5751
|
}
|
|
5752
|
+
/**
|
|
5753
|
+
* Escape con el panel abierto, en fase de captura sobre `document` (ver
|
|
5754
|
+
* `listenEscapeCapture`): consume la tecla solo si había un panel que
|
|
5755
|
+
* cerrar, así un `kf-drawer`/`kf-modal` contenedor no se cierra con el
|
|
5756
|
+
* mismo Escape. Devuelve si cerró algo.
|
|
5757
|
+
*/
|
|
4825
5758
|
onEscape() {
|
|
5759
|
+
if (!this.isOpen()) {
|
|
5760
|
+
return false;
|
|
5761
|
+
}
|
|
4826
5762
|
this.isOpen.set(false);
|
|
5763
|
+
return true;
|
|
4827
5764
|
}
|
|
4828
5765
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4829
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MultiSelect, isStandalone: true, selector: "kf-multi-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valuesChange: "valuesChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" }, properties: { "class.kf-multi-select--width-sm": "width() === 'sm'", "class.kf-multi-select--width-lg": "width() === 'lg'", "class.kf-multi-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-multi-select__trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n (click)=\"toggle()\"\n>\n <span\n class=\"kf-multi-select__value\"\n [class.kf-multi-select__value--placeholder]=\"selectedCount() === 0\"\n >\n {{ summaryText() }}\n </span>\n @if (selectedCount() > 0) {\n <span class=\"kf-multi-select__count\">{{ selectedCount() }}</span>\n }\n <span\n class=\"kf-multi-select__caret kf-caret\"\n [class.kf-multi-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n<ng-template #panelTemplate>\n <div class=\"kf-multi-select__panel\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-multi-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); toggleActive()\"\n />\n }\n <ul\n class=\"kf-multi-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-labelledby]=\"triggerId\"\n >\n @if (options().length > 0) {\n <li\n class=\"kf-multi-select__option\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"allSelected()\"\n (click)=\"toggleAll()\"\n (keydown.enter)=\"toggleAll()\"\n (keydown.space)=\"$event.preventDefault(); toggleAll()\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"allSelected()\"\n [class.kf-multi-select__checkbox--indeterminate]=\"isPartiallySelected()\"\n ></span>\n <span class=\"kf-multi-select__option-label\">Todas</span>\n </li>\n }\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-multi-select__option\"\n [class.kf-multi-select__option--selected]=\"isSelected(option.value)\"\n [class.kf-multi-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(option.value)\"\n (click)=\"toggleOption(option.value)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"toggleOption(option.value)\"\n (keydown.space)=\"$event.preventDefault(); toggleOption(option.value)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"isSelected(option.value)\"\n ></span>\n <span class=\"kf-multi-select__option-label\">{{ option.label }}</span>\n </li>\n } @empty {\n @if (options().length > 0) {\n <li class=\"kf-multi-select__empty\">Sin resultados</li>\n } @else {\n <li class=\"kf-multi-select__empty\">Sin opciones disponibles</li>\n }\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-checkbox-box{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--kf-space-md);height:var(--kf-space-md);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-xs);background:var(--kf-color-white);transition:background .14s,border-color .14s;position:relative}.kf-checkbox-box--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}:host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:var(--kf-select-width-md)}:host(.kf-multi-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-multi-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-multi-select--width-full){width:100%}.kf-multi-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-family:var(--kf-font-body);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-multi-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-multi-select__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-multi-select__trigger:disabled{opacity:.5;cursor:not-allowed}.kf-multi-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}.kf-multi-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-multi-select__count{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 5px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white);font-size:.72rem;font-weight:700}.kf-multi-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-multi-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-multi-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-multi-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-multi-select__list::-webkit-scrollbar{width:6px}.kf-multi-select__list::-webkit-scrollbar-track{background:transparent}.kf-multi-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-multi-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-multi-select__option{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-multi-select__option:hover{background:var(--kf-color-primary-soft)}.kf-multi-select__option--selected{color:var(--kf-color-primary);font-weight:600}.kf-multi-select__option--active{background:var(--kf-color-primary-soft)}.kf-multi-select__checkbox{display:inline-flex}.kf-multi-select__checkbox--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--checked:after{content:\"\";position:absolute;left:4px;top:1px;width:var(--kf-checkbox-mark-width);height:var(--kf-checkbox-mark-height);border:var(--kf-checkbox-mark-border);border-width:var(--kf-checkbox-mark-border-width);transform:rotate(45deg)}.kf-multi-select__checkbox--indeterminate{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:var(--kf-checkbox-dash-width);height:var(--kf-checkbox-dash-height);background:var(--kf-color-white);border:none;transform:none}.kf-multi-select__option-label{overflow:hidden;text-overflow:ellipsis}.kf-multi-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
|
|
5766
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MultiSelect, isStandalone: true, selector: "kf-multi-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valuesChange: "valuesChange" }, host: { properties: { "class.kf-multi-select--width-sm": "width() === 'sm'", "class.kf-multi-select--width-lg": "width() === 'lg'", "class.kf-multi-select--width-full": "width() === 'full'" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-multi-select__trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n (click)=\"toggle()\"\n>\n <span\n class=\"kf-multi-select__value\"\n [class.kf-multi-select__value--placeholder]=\"selectedCount() === 0\"\n >\n {{ summaryText() }}\n </span>\n @if (selectedCount() > 0) {\n <span class=\"kf-multi-select__count\">{{ selectedCount() }}</span>\n }\n <span\n class=\"kf-multi-select__caret kf-caret\"\n [class.kf-multi-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n<ng-template #panelTemplate>\n <div class=\"kf-multi-select__panel\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-multi-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); toggleActive()\"\n />\n }\n <ul\n class=\"kf-multi-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-labelledby]=\"triggerId\"\n >\n @if (options().length > 0) {\n <li\n class=\"kf-multi-select__option\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"allSelected()\"\n (click)=\"toggleAll()\"\n (keydown.enter)=\"toggleAll()\"\n (keydown.space)=\"$event.preventDefault(); toggleAll()\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"allSelected()\"\n [class.kf-multi-select__checkbox--indeterminate]=\"isPartiallySelected()\"\n ></span>\n <span class=\"kf-multi-select__option-label\">Todas</span>\n </li>\n }\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-multi-select__option\"\n [class.kf-multi-select__option--selected]=\"isSelected(option.value)\"\n [class.kf-multi-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(option.value)\"\n (click)=\"toggleOption(option.value)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"toggleOption(option.value)\"\n (keydown.space)=\"$event.preventDefault(); toggleOption(option.value)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"isSelected(option.value)\"\n ></span>\n <span class=\"kf-multi-select__option-label\">{{ option.label }}</span>\n </li>\n } @empty {\n @if (options().length > 0) {\n <li class=\"kf-multi-select__empty\">Sin resultados</li>\n } @else {\n <li class=\"kf-multi-select__empty\">Sin opciones disponibles</li>\n }\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-checkbox-box{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--kf-space-md);height:var(--kf-space-md);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-xs);background:var(--kf-color-white);transition:background .14s,border-color .14s;position:relative}.kf-checkbox-box--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}:host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:var(--kf-select-width-md)}:host(.kf-multi-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-multi-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-multi-select--width-full){width:100%}.kf-multi-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-family:var(--kf-font-body);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-multi-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-multi-select__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-multi-select__trigger:disabled{opacity:.5;cursor:not-allowed}.kf-multi-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}.kf-multi-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-multi-select__count{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 5px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white);font-size:.72rem;font-weight:700}.kf-multi-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-multi-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-multi-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-multi-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-multi-select__list::-webkit-scrollbar{width:6px}.kf-multi-select__list::-webkit-scrollbar-track{background:transparent}.kf-multi-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-multi-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-multi-select__option{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-multi-select__option:hover{background:var(--kf-color-primary-soft)}.kf-multi-select__option--selected{color:var(--kf-color-primary);font-weight:600}.kf-multi-select__option--active{background:var(--kf-color-primary-soft)}.kf-multi-select__checkbox{display:inline-flex}.kf-multi-select__checkbox--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--checked:after{content:\"\";position:absolute;left:4px;top:1px;width:var(--kf-checkbox-mark-width);height:var(--kf-checkbox-mark-height);border:var(--kf-checkbox-mark-border);border-width:var(--kf-checkbox-mark-border-width);transform:rotate(45deg)}.kf-multi-select__checkbox--indeterminate{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:var(--kf-checkbox-dash-width);height:var(--kf-checkbox-dash-height);background:var(--kf-color-white);border:none;transform:none}.kf-multi-select__option-label{overflow:hidden;text-overflow:ellipsis}.kf-multi-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
|
|
4830
5767
|
}
|
|
4831
5768
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, decorators: [{
|
|
4832
5769
|
type: Component,
|
|
@@ -4835,10 +5772,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4835
5772
|
'[class.kf-multi-select--width-lg]': "width() === 'lg'",
|
|
4836
5773
|
'[class.kf-multi-select--width-full]': "width() === 'full'",
|
|
4837
5774
|
}, template: "@if (label()) {\n <kf-field-label [for]=\"triggerId\">{{ label() }}</kf-field-label>\n}\n<button\n #trigger\n type=\"button\"\n [id]=\"triggerId\"\n class=\"kf-multi-select__trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n (click)=\"toggle()\"\n>\n <span\n class=\"kf-multi-select__value\"\n [class.kf-multi-select__value--placeholder]=\"selectedCount() === 0\"\n >\n {{ summaryText() }}\n </span>\n @if (selectedCount() > 0) {\n <span class=\"kf-multi-select__count\">{{ selectedCount() }}</span>\n }\n <span\n class=\"kf-multi-select__caret kf-caret\"\n [class.kf-multi-select__caret--open]=\"isOpen()\"\n [class.kf-caret--open]=\"isOpen()\"\n aria-hidden=\"true\"\n ></span>\n</button>\n<ng-template #panelTemplate>\n <div class=\"kf-multi-select__panel\">\n @if (showSearch()) {\n <input\n #searchInput\n type=\"text\"\n class=\"kf-multi-select__search\"\n placeholder=\"Buscar\u2026\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n (keydown.enter)=\"$event.preventDefault(); toggleActive()\"\n />\n }\n <ul\n class=\"kf-multi-select__list\"\n [id]=\"listboxId\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-labelledby]=\"triggerId\"\n >\n @if (options().length > 0) {\n <li\n class=\"kf-multi-select__option\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"allSelected()\"\n (click)=\"toggleAll()\"\n (keydown.enter)=\"toggleAll()\"\n (keydown.space)=\"$event.preventDefault(); toggleAll()\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"allSelected()\"\n [class.kf-multi-select__checkbox--indeterminate]=\"isPartiallySelected()\"\n ></span>\n <span class=\"kf-multi-select__option-label\">Todas</span>\n </li>\n }\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <li\n class=\"kf-multi-select__option\"\n [class.kf-multi-select__option--selected]=\"isSelected(option.value)\"\n [class.kf-multi-select__option--active]=\"i === activeIndex()\"\n role=\"option\"\n tabindex=\"0\"\n [attr.aria-selected]=\"isSelected(option.value)\"\n (click)=\"toggleOption(option.value)\"\n (mouseenter)=\"activeIndex.set(i)\"\n (keydown.enter)=\"toggleOption(option.value)\"\n (keydown.space)=\"$event.preventDefault(); toggleOption(option.value)\"\n (keydown.arrowdown)=\"$event.preventDefault(); moveActive(1)\"\n (keydown.arrowup)=\"$event.preventDefault(); moveActive(-1)\"\n >\n <span\n class=\"kf-multi-select__checkbox kf-checkbox-box\"\n [class.kf-multi-select__checkbox--checked]=\"isSelected(option.value)\"\n ></span>\n <span class=\"kf-multi-select__option-label\">{{ option.label }}</span>\n </li>\n } @empty {\n @if (options().length > 0) {\n <li class=\"kf-multi-select__empty\">Sin resultados</li>\n } @else {\n <li class=\"kf-multi-select__empty\">Sin opciones disponibles</li>\n }\n }\n </ul>\n </div>\n</ng-template>\n", styles: [".kf-caret{flex-shrink:0;width:8px;height:8px;border-right:2px solid var(--kf-color-text-muted);border-bottom:2px solid var(--kf-color-text-muted);transform:rotate(45deg);transition:transform .14s;margin-top:-4px}.kf-caret--open{transform:rotate(-135deg);margin-top:4px}.kf-checkbox-box{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--kf-space-md);height:var(--kf-space-md);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-xs);background:var(--kf-color-white);transition:background .14s,border-color .14s;position:relative}.kf-checkbox-box--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}:host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:var(--kf-select-width-md)}:host(.kf-multi-select--width-sm){width:var(--kf-select-width-sm)}:host(.kf-multi-select--width-lg){width:var(--kf-select-width-lg)}:host(.kf-multi-select--width-full){width:100%}.kf-multi-select__trigger{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);width:100%;font:inherit;font-family:var(--kf-font-body);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);cursor:pointer;transition:border-color .14s,box-shadow .14s}.kf-multi-select__trigger:hover:not(:disabled){border-color:var(--kf-color-border-strong)}.kf-multi-select__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-multi-select__trigger:disabled{opacity:.5;cursor:not-allowed}.kf-multi-select__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;text-align:left}.kf-multi-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-multi-select__count{flex:none;display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 5px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white);font-size:.72rem;font-weight:700}.kf-multi-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-multi-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-multi-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-multi-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-multi-select__list::-webkit-scrollbar{width:6px}.kf-multi-select__list::-webkit-scrollbar-track{background:transparent}.kf-multi-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-multi-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-multi-select__option{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-multi-select__option:hover{background:var(--kf-color-primary-soft)}.kf-multi-select__option--selected{color:var(--kf-color-primary);font-weight:600}.kf-multi-select__option--active{background:var(--kf-color-primary-soft)}.kf-multi-select__checkbox{display:inline-flex}.kf-multi-select__checkbox--checked{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--checked:after{content:\"\";position:absolute;left:4px;top:1px;width:var(--kf-checkbox-mark-width);height:var(--kf-checkbox-mark-height);border:var(--kf-checkbox-mark-border);border-width:var(--kf-checkbox-mark-border-width);transform:rotate(45deg)}.kf-multi-select__checkbox--indeterminate{background:var(--kf-color-primary);border-color:var(--kf-color-primary)}.kf-multi-select__checkbox--indeterminate:after{content:\"\";position:absolute;left:3px;top:6px;width:var(--kf-checkbox-dash-width);height:var(--kf-checkbox-dash-height);background:var(--kf-color-white);border:none;transform:none}.kf-multi-select__option-label{overflow:hidden;text-overflow:ellipsis}.kf-multi-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
|
|
4838
|
-
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], valuesChange: [{ type: i0.Output, args: ["valuesChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }]
|
|
4839
|
-
type: HostListener,
|
|
4840
|
-
args: ['document:keydown.escape']
|
|
4841
|
-
}] } });
|
|
5775
|
+
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], valuesChange: [{ type: i0.Output, args: ["valuesChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
4842
5776
|
|
|
4843
5777
|
const DEFAULT_FIELD_WIDTH = '240px';
|
|
4844
5778
|
/**
|
|
@@ -4903,7 +5837,7 @@ class Filter {
|
|
|
4903
5837
|
this.fieldChange.emit({ key, value });
|
|
4904
5838
|
}
|
|
4905
5839
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4906
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Filter, isStandalone: true, selector: "kf-filter", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mobileTitle: { classPropertyName: "mobileTitle", publicName: "mobileTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange" }, ngImport: i0, template: "<ng-template #fieldsTpl let-stacked=\"stacked\">\n @for (field of fields(); track field.key) {\n @switch (field.type) {\n @case ('select') {\n <kf-select\n [label]=\"field.label ?? ''\"\n [options]=\"optionsFor(field.key)\"\n [value]=\"singleValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valueChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('multi-select') {\n <kf-multi-select\n [label]=\"field.label ?? ''\"\n [options]=\"multiOptionsFor(field.key)\"\n [values]=\"multiValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valuesChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('search') {\n <kf-input\n [label]=\"field.label ?? ''\"\n [icon]=\"field.icon ?? 'search'\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n [ngModel]=\"singleValueFor(field.key)\"\n (ngModelChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n }\n @if (!stacked && field.showDivider) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n</ng-template>\n\n<div class=\"kf-filter kf-filter--row\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: false }\" />\n</div>\n\n<kf-collapsible class=\"kf-filter__collapsible\" [title]=\"mobileTitle()\">\n <div class=\"kf-filter kf-filter--stack\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: true }\" />\n </div>\n</kf-collapsible>\n", styles: ["@charset \"UTF-8\";:host{display:block}.kf-filter--row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--kf-space-md)}.kf-filter--stack{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-filter__collapsible{display:none}@media(max-width:768px){.kf-filter--row{display:none}.kf-filter__collapsible{display:block}}\n"], dependencies: [{ kind: "component", type: Collapsible, selector: "kf-collapsible", inputs: ["title", "expanded"], outputs: ["expandedChange"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "autocomplete", "name", "inputmode", "toggleable", "ariaLabel", "clearable", "clearLabel"], outputs: ["cleared"] }, { kind: "component", type: MultiSelect, selector: "kf-multi-select", inputs: ["label", "options", "values", "placeholder", "disabled", "width", "searchable", "searchThreshold"], outputs: ["valuesChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold", "ariaLabel", "icon", "panelTitle", "panelWidth"], outputs: ["valueChange"] }] });
|
|
5840
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Filter, isStandalone: true, selector: "kf-filter", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mobileTitle: { classPropertyName: "mobileTitle", publicName: "mobileTitle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange" }, ngImport: i0, template: "<ng-template #fieldsTpl let-stacked=\"stacked\">\n @for (field of fields(); track field.key) {\n @switch (field.type) {\n @case ('select') {\n <kf-select\n [label]=\"field.label ?? ''\"\n [options]=\"optionsFor(field.key)\"\n [value]=\"singleValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valueChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('multi-select') {\n <kf-multi-select\n [label]=\"field.label ?? ''\"\n [options]=\"multiOptionsFor(field.key)\"\n [values]=\"multiValueFor(field.key)\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n (valuesChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n @case ('search') {\n <kf-input\n [label]=\"field.label ?? ''\"\n [icon]=\"field.icon ?? 'search'\"\n [placeholder]=\"field.placeholder ?? ''\"\n [style.width]=\"stacked ? '100%' : fieldWidth(field)\"\n [ngModel]=\"singleValueFor(field.key)\"\n (ngModelChange)=\"onFieldChange(field.key, $event)\"\n />\n }\n }\n @if (!stacked && field.showDivider) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n</ng-template>\n\n<div class=\"kf-filter kf-filter--row\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: false }\" />\n</div>\n\n<kf-collapsible class=\"kf-filter__collapsible\" [title]=\"mobileTitle()\">\n <div class=\"kf-filter kf-filter--stack\">\n <ng-container [ngTemplateOutlet]=\"fieldsTpl\" [ngTemplateOutletContext]=\"{ stacked: true }\" />\n </div>\n</kf-collapsible>\n", styles: ["@charset \"UTF-8\";:host{display:block}.kf-filter--row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--kf-space-md)}.kf-filter--stack{display:flex;flex-direction:column;gap:var(--kf-space-md)}.kf-filter__collapsible{display:none}@media(max-width:768px){.kf-filter--row{display:none}.kf-filter__collapsible{display:block}}\n"], dependencies: [{ kind: "component", type: Collapsible, selector: "kf-collapsible", inputs: ["title", "expanded"], outputs: ["expandedChange"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "autocomplete", "name", "inputmode", "min", "max", "step", "maxlength", "toggleable", "ariaLabel", "clearable", "clearLabel"], outputs: ["cleared"] }, { kind: "component", type: MultiSelect, selector: "kf-multi-select", inputs: ["label", "options", "values", "placeholder", "disabled", "width", "searchable", "searchThreshold"], outputs: ["valuesChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold", "ariaLabel", "icon", "panelTitle", "panelWidth"], outputs: ["valueChange"] }] });
|
|
4907
5841
|
}
|
|
4908
5842
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Filter, decorators: [{
|
|
4909
5843
|
type: Component,
|
|
@@ -5514,6 +6448,14 @@ function cancelFrame(handle) {
|
|
|
5514
6448
|
* <kf-metric-card label="Saldo Pendiente" value="$45,000">
|
|
5515
6449
|
* <kf-button actions variant="primary" size="sm">Pagar Ahora</kf-button>
|
|
5516
6450
|
* </kf-metric-card>
|
|
6451
|
+
*
|
|
6452
|
+
* `loading` (default `false`): dibuja el esqueleto de la card — etiqueta,
|
|
6453
|
+
* ícono, valor y `foot` como bloques del alto real de cada línea — dentro del
|
|
6454
|
+
* mismo `kf-card`, y reserva (sin mostrar) el espacio de las acciones, así que
|
|
6455
|
+
* al llegar los datos no hay salto de layout. Pasa los mismos inputs que
|
|
6456
|
+
* usarás cargada. Marca el host con `aria-busy="true"`.
|
|
6457
|
+
*
|
|
6458
|
+
* <kf-metric-card label="Tickets abiertos" value="11" foot="+2 hoy" [loading]="isLoading()" />
|
|
5517
6459
|
*/
|
|
5518
6460
|
class MetricCard {
|
|
5519
6461
|
label = input.required(/* @ts-ignore */
|
|
@@ -5533,93 +6475,261 @@ class MetricCard {
|
|
|
5533
6475
|
/** Badge de esquina opcional, pegado al valor (ej. "+2%"). */
|
|
5534
6476
|
badge = input(/* @ts-ignore */
|
|
5535
6477
|
...(ngDevMode ? [undefined, { debugName: "badge" }] : /* istanbul ignore next */ []));
|
|
6478
|
+
/** Dibuja el esqueleto en lugar del contenido, conservando las dimensiones. */
|
|
6479
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
5536
6480
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MetricCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5537
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MetricCard, isStandalone: true, selector: "kf-metric-card", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, foot: { classPropertyName: "foot", publicName: "foot", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n
|
|
6481
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MetricCard, isStandalone: true, selector: "kf-metric-card", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, foot: { classPropertyName: "foot", publicName: "foot", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-busy": "loading() ? 'true' : null" } }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n @if (loading()) {\n <span\n class=\"kf-metric-card__label-bone kf-bone\"\n style=\"--kf-bone-width: 45%\"\n aria-hidden=\"true\"\n >\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n </span>\n @if (icon()) {\n <span class=\"kf-metric-card__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n }\n } @else {\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n <span class=\"kf-metric-card__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n }\n </div>\n <div class=\"kf-metric-card__value-row\">\n @if (loading()) {\n <div class=\"kf-metric-card__value kf-bone\" style=\"--kf-bone-width: 50%\" aria-hidden=\"true\">\n {{ value() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__value\" [style.color]=\"valueColor()\">\n {{ value() }}\n @if (unit()) {\n <span class=\"kf-metric-card__unit\">{{ unit() }}</span>\n }\n </div>\n @if (badge()) {\n <span class=\"kf-metric-card__badge\">\n <kf-text as=\"span\" variant=\"overline-strong\" tone=\"ok\">{{ badge() }}</kf-text>\n </span>\n }\n }\n </div>\n @if (foot()) {\n @if (loading()) {\n <div class=\"kf-metric-card__foot kf-bone\" style=\"--kf-bone-width: 60%\" aria-hidden=\"true\">\n {{ foot() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__foot\">{{ foot() }}</div>\n }\n }\n <div class=\"kf-metric-card__actions\" [class.kf-metric-card__actions--loading]=\"loading()\">\n <ng-content select=\"[actions]\" />\n </div>\n</kf-card>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-metric-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kf-space-xs)}.kf-metric-card__icon{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-metric-card__value-row{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-metric-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:1.6rem;line-height:1.1;color:var(--kf-color-ink)}.kf-metric-card__unit{font-size:.85rem;font-weight:500;color:var(--kf-color-text-muted);margin-left:var(--kf-space-xs)}.kf-metric-card__badge{padding:2px var(--kf-space-xs);border-radius:var(--kf-radius-pill);background:var(--kf-color-success-soft)}.kf-metric-card__foot{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-metric-card__actions:empty{display:none}.kf-metric-card__actions{display:flex;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm);margin-top:auto}.kf-metric-card__actions--loading{visibility:hidden}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap", "loading"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
|
|
5538
6482
|
}
|
|
5539
6483
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MetricCard, decorators: [{
|
|
5540
6484
|
type: Component,
|
|
5541
|
-
args: [{ selector: 'kf-metric-card', imports: [Card, Icon, Text],
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
* con dos independientes donde solo uno está abierto.
|
|
5552
|
-
*/
|
|
5553
|
-
const openModalDialogs = [];
|
|
5554
|
-
/** Llamado por `Modal` cuando su `<dialog>` pasa a estar abierto. */
|
|
5555
|
-
function registerOpenModalDialog(dialog) {
|
|
5556
|
-
if (!openModalDialogs.includes(dialog)) {
|
|
5557
|
-
openModalDialogs.push(dialog);
|
|
5558
|
-
}
|
|
5559
|
-
}
|
|
5560
|
-
/** Llamado por `Modal` cuando su `<dialog>` se cierra o el componente se destruye. */
|
|
5561
|
-
function unregisterOpenModalDialog(dialog) {
|
|
5562
|
-
const index = openModalDialogs.indexOf(dialog);
|
|
5563
|
-
if (index !== -1) {
|
|
5564
|
-
openModalDialogs.splice(index, 1);
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
function topmostOpenModalDialog() {
|
|
5568
|
-
return openModalDialogs.at(-1);
|
|
5569
|
-
}
|
|
6485
|
+
args: [{ selector: 'kf-metric-card', imports: [Card, Icon, Text], host: {
|
|
6486
|
+
'[attr.aria-busy]': "loading() ? 'true' : null",
|
|
6487
|
+
}, template: "<kf-card>\n <div class=\"kf-metric-card__head\">\n @if (loading()) {\n <span\n class=\"kf-metric-card__label-bone kf-bone\"\n style=\"--kf-bone-width: 45%\"\n aria-hidden=\"true\"\n >\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n </span>\n @if (icon()) {\n <span class=\"kf-metric-card__icon kf-bone-fill\" aria-hidden=\"true\"></span>\n }\n } @else {\n <kf-text as=\"span\" variant=\"overline-strong\">{{ label() }}</kf-text>\n @if (icon(); as iconName) {\n <span class=\"kf-metric-card__icon\">\n <kf-icon [name]=\"iconName\" size=\"sm\" />\n </span>\n }\n }\n </div>\n <div class=\"kf-metric-card__value-row\">\n @if (loading()) {\n <div class=\"kf-metric-card__value kf-bone\" style=\"--kf-bone-width: 50%\" aria-hidden=\"true\">\n {{ value() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__value\" [style.color]=\"valueColor()\">\n {{ value() }}\n @if (unit()) {\n <span class=\"kf-metric-card__unit\">{{ unit() }}</span>\n }\n </div>\n @if (badge()) {\n <span class=\"kf-metric-card__badge\">\n <kf-text as=\"span\" variant=\"overline-strong\" tone=\"ok\">{{ badge() }}</kf-text>\n </span>\n }\n }\n </div>\n @if (foot()) {\n @if (loading()) {\n <div class=\"kf-metric-card__foot kf-bone\" style=\"--kf-bone-width: 60%\" aria-hidden=\"true\">\n {{ foot() }}\n </div>\n } @else {\n <div class=\"kf-metric-card__foot\">{{ foot() }}</div>\n }\n }\n <div class=\"kf-metric-card__actions\" [class.kf-metric-card__actions--loading]=\"loading()\">\n <ng-content select=\"[actions]\" />\n </div>\n</kf-card>\n", styles: [".kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{background:linear-gradient(90deg,var(--kf-color-border) 25%,var(--kf-color-surface) 37%,var(--kf-color-border) 63%);background-size:400% 100%;animation:kf-bone-shimmer 1.4s ease infinite}.kf-bone{display:block;box-sizing:border-box;width:var(--kf-bone-width, 60%);max-width:100%;overflow:hidden;white-space:nowrap;border-radius:var(--kf-radius-xs);-webkit-user-select:none;user-select:none;pointer-events:none}.kf-bone.kf-bone{color:transparent}.kf-bone>*{visibility:hidden}@keyframes kf-bone-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media(prefers-reduced-motion:reduce){.kf-bone.kf-bone,.kf-bone-fill.kf-bone-fill,.kf-bone-overlay.kf-bone-overlay:after{animation:none;background:var(--kf-color-border)}}:host{display:block}.kf-metric-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kf-space-xs)}.kf-metric-card__icon{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-metric-card__value-row{display:flex;align-items:center;gap:var(--kf-space-xs)}.kf-metric-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:1.6rem;line-height:1.1;color:var(--kf-color-ink)}.kf-metric-card__unit{font-size:.85rem;font-weight:500;color:var(--kf-color-text-muted);margin-left:var(--kf-space-xs)}.kf-metric-card__badge{padding:2px var(--kf-space-xs);border-radius:var(--kf-radius-pill);background:var(--kf-color-success-soft)}.kf-metric-card__foot{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-metric-card__actions:empty{display:none}.kf-metric-card__actions{display:flex;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm);margin-top:auto}.kf-metric-card__actions--loading{visibility:hidden}\n"] }]
|
|
6488
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], foot: [{ type: i0.Input, args: [{ isSignal: true, alias: "foot", required: false }] }], valueColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColor", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }] } });
|
|
6489
|
+
|
|
6490
|
+
const PROGRESS_STATUS = {
|
|
6491
|
+
DONE: 'done',
|
|
6492
|
+
CURRENT: 'current',
|
|
6493
|
+
PENDING: 'pending',
|
|
6494
|
+
};
|
|
5570
6495
|
/**
|
|
5571
|
-
*
|
|
5572
|
-
*
|
|
5573
|
-
* a la vez, no interfiere con instancias nuevas que arma un `TestBed` fresco
|
|
5574
|
-
* en cada spec (root providers se recrean por test).
|
|
6496
|
+
* Texto por defecto (español) de cada estado: el estado nunca se comunica solo
|
|
6497
|
+
* con color. Los componentes que lo muestran aceptan sus propios textos.
|
|
5575
6498
|
*/
|
|
5576
|
-
const
|
|
6499
|
+
const PROGRESS_STATUS_LABEL = {
|
|
6500
|
+
[PROGRESS_STATUS.DONE]: 'Completado',
|
|
6501
|
+
[PROGRESS_STATUS.CURRENT]: 'En curso',
|
|
6502
|
+
[PROGRESS_STATUS.PENDING]: 'Pendiente',
|
|
6503
|
+
};
|
|
6504
|
+
|
|
5577
6505
|
/**
|
|
5578
|
-
*
|
|
5579
|
-
*
|
|
5580
|
-
* (
|
|
5581
|
-
* `
|
|
5582
|
-
* siempre appendea `.cdk-overlay-container` como hijo de `<body>`, que
|
|
5583
|
-
* queda fuera del `<dialog>` y por lo tanto inerte: el overlay se ve pero
|
|
5584
|
-
* no recibe clicks.
|
|
6506
|
+
* Marca pequeña de estado para leyendas y listas de hitos: círculo relleno
|
|
6507
|
+
* con check (completado), anillo con punto (en curso) o anillo vacío
|
|
6508
|
+
* (pendiente). La forma comunica el estado y el color solo lo refuerza.
|
|
6509
|
+
* `exception` la pinta con borde discontinuo y tono de aviso.
|
|
5585
6510
|
*
|
|
5586
|
-
*
|
|
5587
|
-
*
|
|
5588
|
-
*
|
|
5589
|
-
* inyector RAÍZ de la app la primera vez que algo la necesita — incluida
|
|
5590
|
-
* la propia `Overlay`, que la inyecta puertas adentro de SU construcción,
|
|
5591
|
-
* no en el inyector del componente que gatilló esa construcción. Un
|
|
5592
|
-
* `providers` a nivel de componente (`kf-modal`, o incluso `kf-select`)
|
|
5593
|
-
* nunca llega a interceptar esa creación; solo un `providers` explícito en
|
|
5594
|
-
* el bootstrap de cada app lo haría, y eso es justo la configuración manual
|
|
5595
|
-
* que se quiere evitar.
|
|
6511
|
+
* Es decorativa por defecto (`aria-hidden`): el texto del estado va en el
|
|
6512
|
+
* contexto que la usa. Para usarla suelta (ej. una leyenda sin texto al lado),
|
|
6513
|
+
* `label` la expone como imagen con ese nombre.
|
|
5596
6514
|
*
|
|
5597
|
-
*
|
|
5598
|
-
*
|
|
5599
|
-
*
|
|
5600
|
-
*
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
6515
|
+
* <kf-status-mark status="done" small />
|
|
6516
|
+
* <kf-status-mark status="current" label="En curso" />
|
|
6517
|
+
*
|
|
6518
|
+
* Tamaño: variables CSS `--kf-status-mark-size` y `--kf-status-mark-size-sm`.
|
|
6519
|
+
*/
|
|
6520
|
+
class StatusMark {
|
|
6521
|
+
status = input.required(/* @ts-ignore */
|
|
6522
|
+
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
6523
|
+
/** Versión reducida, para listas de hitos. */
|
|
6524
|
+
small = input(false, /* @ts-ignore */
|
|
6525
|
+
...(ngDevMode ? [{ debugName: "small" }] : /* istanbul ignore next */ []));
|
|
6526
|
+
/** Hito de excepción: borde discontinuo y tono de aviso. */
|
|
6527
|
+
exception = input(false, /* @ts-ignore */
|
|
6528
|
+
...(ngDevMode ? [{ debugName: "exception" }] : /* istanbul ignore next */ []));
|
|
6529
|
+
/** Nombre accesible; sin él la marca es decorativa. */
|
|
6530
|
+
label = input(null, /* @ts-ignore */
|
|
6531
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6532
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusMark, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6533
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: StatusMark, isStandalone: true, selector: "kf-status-mark", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, small: { classPropertyName: "small", publicName: "small", isSignal: true, isRequired: false, transformFunction: null }, exception: { classPropertyName: "exception", publicName: "exception", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-status-mark-host--sm": "small()" } }, ngImport: i0, template: `<span
|
|
6534
|
+
class="kf-status-mark"
|
|
6535
|
+
[class]="'kf-status-mark--' + status()"
|
|
6536
|
+
[class.kf-status-mark--exception]="exception()"
|
|
6537
|
+
[attr.role]="label() ? 'img' : null"
|
|
6538
|
+
[attr.aria-label]="label() || null"
|
|
6539
|
+
[attr.aria-hidden]="label() ? null : 'true'"
|
|
6540
|
+
></span>`, isInline: true, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-mark-size, 1.375rem)}:host(.kf-status-mark-host--sm){--_size: var(--kf-status-mark-size-sm, 1rem)}.kf-status-mark{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);border-radius:50%;box-sizing:border-box;background:var(--kf-color-white)}.kf-status-mark--done{background:var(--kf-color-success-strong)}.kf-status-mark--done:after{content:\"\";width:calc(var(--_size) * .23);height:calc(var(--_size) * .41);border:solid var(--kf-color-white);border-width:0 2px 2px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-mark--current{border:2px solid var(--kf-color-primary);box-shadow:0 0 0 calc(var(--_size) * .14) var(--kf-color-primary-soft)}.kf-status-mark--current:after{content:\"\";width:calc(var(--_size) * .36);height:calc(var(--_size) * .36);border-radius:50%;background:var(--kf-color-primary)}.kf-status-mark--pending{border:2px solid var(--kf-color-text-muted)}.kf-status-mark--exception{border-style:dashed;border-color:var(--kf-color-warning-strong)}.kf-status-mark--exception.kf-status-mark--done{background:var(--kf-color-warning-strong)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6541
|
+
}
|
|
6542
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusMark, decorators: [{
|
|
6543
|
+
type: Component,
|
|
6544
|
+
args: [{ selector: 'kf-status-mark', template: `<span
|
|
6545
|
+
class="kf-status-mark"
|
|
6546
|
+
[class]="'kf-status-mark--' + status()"
|
|
6547
|
+
[class.kf-status-mark--exception]="exception()"
|
|
6548
|
+
[attr.role]="label() ? 'img' : null"
|
|
6549
|
+
[attr.aria-label]="label() || null"
|
|
6550
|
+
[attr.aria-hidden]="label() ? null : 'true'"
|
|
6551
|
+
></span>`, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.kf-status-mark-host--sm]': 'small()' }, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-mark-size, 1.375rem)}:host(.kf-status-mark-host--sm){--_size: var(--kf-status-mark-size-sm, 1rem)}.kf-status-mark{display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);border-radius:50%;box-sizing:border-box;background:var(--kf-color-white)}.kf-status-mark--done{background:var(--kf-color-success-strong)}.kf-status-mark--done:after{content:\"\";width:calc(var(--_size) * .23);height:calc(var(--_size) * .41);border:solid var(--kf-color-white);border-width:0 2px 2px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-mark--current{border:2px solid var(--kf-color-primary);box-shadow:0 0 0 calc(var(--_size) * .14) var(--kf-color-primary-soft)}.kf-status-mark--current:after{content:\"\";width:calc(var(--_size) * .36);height:calc(var(--_size) * .36);border-radius:50%;background:var(--kf-color-primary)}.kf-status-mark--pending{border:2px solid var(--kf-color-text-muted)}.kf-status-mark--exception{border-style:dashed;border-color:var(--kf-color-warning-strong)}.kf-status-mark--exception.kf-status-mark--done{background:var(--kf-color-warning-strong)}\n"] }]
|
|
6552
|
+
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], small: [{ type: i0.Input, args: [{ isSignal: true, alias: "small", required: false }] }], exception: [{ type: i0.Input, args: [{ isSignal: true, alias: "exception", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
6553
|
+
|
|
6554
|
+
/**
|
|
6555
|
+
* Nodo circular de una etapa: ícono de la etapa más una insignia de estado.
|
|
6556
|
+
* La forma comunica el estado (relleno con check, anillo con punto, anillo
|
|
6557
|
+
* vacío) y el color solo lo refuerza.
|
|
6558
|
+
*
|
|
6559
|
+
* Es decorativo por defecto (`aria-hidden`): el texto del estado debe estar en
|
|
6560
|
+
* el contexto que lo usa. Para usarlo suelto, `label` lo expone como imagen
|
|
6561
|
+
* con ese nombre.
|
|
6562
|
+
*
|
|
6563
|
+
* <kf-status-node status="current" icon="truck" />
|
|
6564
|
+
* <kf-status-node status="done" icon="check" size="sm" label="Entrega completada" />
|
|
6565
|
+
*
|
|
6566
|
+
* Tamaño: variables CSS `--kf-status-node-size` y `--kf-status-node-size-sm`.
|
|
5607
6567
|
*/
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
6568
|
+
class StatusNode {
|
|
6569
|
+
status = input.required(/* @ts-ignore */
|
|
6570
|
+
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
6571
|
+
icon = input.required(/* @ts-ignore */
|
|
6572
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
6573
|
+
size = input('md', /* @ts-ignore */
|
|
6574
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
6575
|
+
/** Nombre accesible; sin él el nodo es decorativo. */
|
|
6576
|
+
label = input(null, /* @ts-ignore */
|
|
6577
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6578
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusNode, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6579
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: StatusNode, isStandalone: true, selector: "kf-status-node", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kf-status-node-host--sm": "size() === 'sm'" } }, ngImport: i0, template: `
|
|
6580
|
+
<span
|
|
6581
|
+
class="kf-status-node"
|
|
6582
|
+
[class]="'kf-status-node--' + status()"
|
|
6583
|
+
[attr.role]="label() ? 'img' : null"
|
|
6584
|
+
[attr.aria-label]="label() || null"
|
|
6585
|
+
[attr.aria-hidden]="label() ? null : 'true'"
|
|
6586
|
+
>
|
|
6587
|
+
<kf-icon [name]="icon()" size="sm" />
|
|
6588
|
+
<span class="kf-status-node__badge"></span>
|
|
6589
|
+
</span>
|
|
6590
|
+
`, isInline: true, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-node-size, 2.125rem);--_halo: calc(var(--_size) * .12);--_badge: calc(var(--_size) * .44)}:host(.kf-status-node-host--sm){--_size: var(--kf-status-node-size-sm, 1.75rem);--_halo: calc(var(--_size) * .11);--_badge: calc(var(--_size) * .46)}.kf-status-node{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);box-sizing:border-box;border-radius:50%;background:var(--kf-color-white);border:2px solid var(--kf-color-text-muted);color:var(--kf-color-text-muted)}.kf-status-node--done{background:var(--kf-color-success-strong);border-color:var(--kf-color-success-strong);color:var(--kf-color-white)}.kf-status-node--current{border-color:var(--kf-color-primary);color:var(--kf-color-primary);box-shadow:0 0 0 var(--_halo) var(--kf-color-primary-soft)}.kf-status-node__badge{position:absolute;right:calc(var(--_badge) * -.33);bottom:calc(var(--_badge) * -.33);display:none;align-items:center;justify-content:center;width:var(--_badge);height:var(--_badge);border:2px solid var(--kf-color-white);border-radius:50%;box-sizing:border-box}.kf-status-node--done .kf-status-node__badge{display:inline-flex;background:var(--kf-color-success-strong)}.kf-status-node--done .kf-status-node__badge:after{content:\"\";width:calc(var(--_badge) * .2);height:calc(var(--_badge) * .4);border:solid var(--kf-color-white);border-width:0 1.5px 1.5px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-node--current .kf-status-node__badge{display:inline-flex;background:var(--kf-color-primary)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6591
|
+
}
|
|
6592
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatusNode, decorators: [{
|
|
6593
|
+
type: Component,
|
|
6594
|
+
args: [{ selector: 'kf-status-node', imports: [Icon], template: `
|
|
6595
|
+
<span
|
|
6596
|
+
class="kf-status-node"
|
|
6597
|
+
[class]="'kf-status-node--' + status()"
|
|
6598
|
+
[attr.role]="label() ? 'img' : null"
|
|
6599
|
+
[attr.aria-label]="label() || null"
|
|
6600
|
+
[attr.aria-hidden]="label() ? null : 'true'"
|
|
6601
|
+
>
|
|
6602
|
+
<kf-icon [name]="icon()" size="sm" />
|
|
6603
|
+
<span class="kf-status-node__badge"></span>
|
|
6604
|
+
</span>
|
|
6605
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.kf-status-node-host--sm]': "size() === 'sm'" }, styles: [":host{display:inline-flex;flex:none;--_size: var(--kf-status-node-size, 2.125rem);--_halo: calc(var(--_size) * .12);--_badge: calc(var(--_size) * .44)}:host(.kf-status-node-host--sm){--_size: var(--kf-status-node-size-sm, 1.75rem);--_halo: calc(var(--_size) * .11);--_badge: calc(var(--_size) * .46)}.kf-status-node{position:relative;display:inline-flex;align-items:center;justify-content:center;flex:none;width:var(--_size);height:var(--_size);box-sizing:border-box;border-radius:50%;background:var(--kf-color-white);border:2px solid var(--kf-color-text-muted);color:var(--kf-color-text-muted)}.kf-status-node--done{background:var(--kf-color-success-strong);border-color:var(--kf-color-success-strong);color:var(--kf-color-white)}.kf-status-node--current{border-color:var(--kf-color-primary);color:var(--kf-color-primary);box-shadow:0 0 0 var(--_halo) var(--kf-color-primary-soft)}.kf-status-node__badge{position:absolute;right:calc(var(--_badge) * -.33);bottom:calc(var(--_badge) * -.33);display:none;align-items:center;justify-content:center;width:var(--_badge);height:var(--_badge);border:2px solid var(--kf-color-white);border-radius:50%;box-sizing:border-box}.kf-status-node--done .kf-status-node__badge{display:inline-flex;background:var(--kf-color-success-strong)}.kf-status-node--done .kf-status-node__badge:after{content:\"\";width:calc(var(--_badge) * .2);height:calc(var(--_badge) * .4);border:solid var(--kf-color-white);border-width:0 1.5px 1.5px 0;transform:rotate(45deg) translate(-5%,-5%)}.kf-status-node--current .kf-status-node__badge{display:inline-flex;background:var(--kf-color-primary)}\n"] }]
|
|
6606
|
+
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
6607
|
+
|
|
6608
|
+
const DEFAULT_MILESTONE_TIMELINE_LABELS = {
|
|
6609
|
+
heading: 'Avance por etapa',
|
|
6610
|
+
expandAll: 'Expandir todo',
|
|
6611
|
+
collapseAll: 'Contraer todo',
|
|
6612
|
+
statusDone: PROGRESS_STATUS_LABEL[PROGRESS_STATUS.DONE],
|
|
6613
|
+
statusPending: PROGRESS_STATUS_LABEL[PROGRESS_STATUS.PENDING],
|
|
6614
|
+
currentMeta: (done, total) => `${PROGRESS_STATUS_LABEL[PROGRESS_STATUS.CURRENT]} · ${done} de ${total}`,
|
|
6615
|
+
next: 'Siguiente',
|
|
6616
|
+
exceptionsTitle: 'Observaciones · no cuentan en el avance',
|
|
6617
|
+
noExceptions: 'Sin observaciones registradas.',
|
|
6618
|
+
itemDone: 'completado',
|
|
6619
|
+
itemPending: 'pendiente',
|
|
6620
|
+
};
|
|
6621
|
+
|
|
6622
|
+
let nextInstanceId = 0;
|
|
6623
|
+
/**
|
|
6624
|
+
* Línea de tiempo de etapas con hitos. Cada etapa es un encabezado expandible
|
|
6625
|
+
* (`aria-expanded`/`aria-controls`) con un panel de hitos y, si la etapa tiene
|
|
6626
|
+
* hitos de excepción, un bloque aparte que no cuenta en el avance. Una etapa
|
|
6627
|
+
* sin hitos es solo informativa (no es un botón).
|
|
6628
|
+
*
|
|
6629
|
+
* El estado nunca se comunica solo con color: cada etapa y cada hito llevan su
|
|
6630
|
+
* estado en texto (visible o solo para lectores de pantalla) y los nodos y
|
|
6631
|
+
* marcas lo dibujan también con la forma. Todos los textos salen de `labels`
|
|
6632
|
+
* (español por defecto) y las fechas llegan ya formateadas.
|
|
6633
|
+
*
|
|
6634
|
+
* El conector entre etapas se deriva del tamaño del nodo
|
|
6635
|
+
* (`--kf-status-node-size`, default `2.125rem`) y de la separación del
|
|
6636
|
+
* encabezado: cambiar el tamaño del nodo mueve la línea con él.
|
|
6637
|
+
*
|
|
6638
|
+
* <kf-milestone-timeline
|
|
6639
|
+
* [groups]="stages()"
|
|
6640
|
+
* [(expandedIds)]="expanded"
|
|
6641
|
+
* nextItemId="stage2-docs"
|
|
6642
|
+
* ariaLabel="Avance de la entrega"
|
|
6643
|
+
* [labels]="{ heading: 'Progress', expandAll: 'Expand all' }"
|
|
6644
|
+
* />
|
|
6645
|
+
*/
|
|
6646
|
+
class MilestoneTimeline {
|
|
6647
|
+
host = inject(ElementRef);
|
|
6648
|
+
instanceId = `kf-milestone-timeline-${nextInstanceId++}`;
|
|
6649
|
+
groups = input.required(/* @ts-ignore */
|
|
6650
|
+
...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
6651
|
+
/** Ids de las etapas expandidas, two-way: `[(expandedIds)]`. El padre controla el estado inicial. */
|
|
6652
|
+
expandedIds = model([], /* @ts-ignore */
|
|
6653
|
+
...(ngDevMode ? [{ debugName: "expandedIds" }] : /* istanbul ignore next */ []));
|
|
6654
|
+
/** Id del hito que se destaca como el siguiente a completar. */
|
|
6655
|
+
nextItemId = input(undefined, /* @ts-ignore */
|
|
6656
|
+
...(ngDevMode ? [{ debugName: "nextItemId" }] : /* istanbul ignore next */ []));
|
|
6657
|
+
/** Textos; los que no se pasen usan el español por defecto. */
|
|
6658
|
+
labels = input({}, /* @ts-ignore */
|
|
6659
|
+
...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
6660
|
+
/** Nombre accesible de la lista de etapas. */
|
|
6661
|
+
ariaLabel = input(undefined, /* @ts-ignore */
|
|
6662
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
6663
|
+
/** Nivel (2-6) del título "Avance por etapa", para respetar el esquema de títulos de la página. */
|
|
6664
|
+
headingLevel = input(3, /* @ts-ignore */
|
|
6665
|
+
...(ngDevMode ? [{ debugName: "headingLevel" }] : /* istanbul ignore next */ []));
|
|
6666
|
+
text = computed(() => ({
|
|
6667
|
+
...DEFAULT_MILESTONE_TIMELINE_LABELS,
|
|
6668
|
+
...this.labels(),
|
|
6669
|
+
}), /* @ts-ignore */
|
|
6670
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
6671
|
+
rows = computed(() => this.groups().map((group) => {
|
|
6672
|
+
const main = group.items.filter((item) => !item.exception);
|
|
6673
|
+
const doneCount = main.filter((item) => item.done).length;
|
|
6674
|
+
const exceptions = group.items.filter((item) => item.exception);
|
|
6675
|
+
return {
|
|
6676
|
+
group,
|
|
6677
|
+
main,
|
|
6678
|
+
exceptions,
|
|
6679
|
+
happened: exceptions.filter((item) => item.done),
|
|
6680
|
+
expandable: group.items.length > 0,
|
|
6681
|
+
meta: this.metaOf(group, doneCount, main.length),
|
|
6682
|
+
};
|
|
6683
|
+
}), /* @ts-ignore */
|
|
6684
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
6685
|
+
expandableIds = computed(() => this.groups()
|
|
6686
|
+
.filter((group) => group.items.length > 0)
|
|
6687
|
+
.map((group) => group.id), /* @ts-ignore */
|
|
6688
|
+
...(ngDevMode ? [{ debugName: "expandableIds" }] : /* istanbul ignore next */ []));
|
|
6689
|
+
hasExpandable = computed(() => this.expandableIds().length > 0, /* @ts-ignore */
|
|
6690
|
+
...(ngDevMode ? [{ debugName: "hasExpandable" }] : /* istanbul ignore next */ []));
|
|
6691
|
+
allExpanded = computed(() => this.hasExpandable() && this.expandableIds().every((id) => this.isExpanded(id)), /* @ts-ignore */
|
|
6692
|
+
...(ngDevMode ? [{ debugName: "allExpanded" }] : /* istanbul ignore next */ []));
|
|
6693
|
+
isExpanded(groupId) {
|
|
6694
|
+
return this.expandedIds().includes(groupId);
|
|
5611
6695
|
}
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
6696
|
+
headId(groupId) {
|
|
6697
|
+
return `${this.instanceId}-head-${groupId}`;
|
|
6698
|
+
}
|
|
6699
|
+
panelId(groupId) {
|
|
6700
|
+
return `${this.instanceId}-panel-${groupId}`;
|
|
6701
|
+
}
|
|
6702
|
+
itemStatus(item) {
|
|
6703
|
+
return item.done ? PROGRESS_STATUS.DONE : PROGRESS_STATUS.PENDING;
|
|
6704
|
+
}
|
|
6705
|
+
toggle(groupId) {
|
|
6706
|
+
this.expandedIds.update((ids) => ids.includes(groupId) ? ids.filter((id) => id !== groupId) : [...ids, groupId]);
|
|
6707
|
+
}
|
|
6708
|
+
toggleAll() {
|
|
6709
|
+
this.expandedIds.set(this.allExpanded() ? [] : this.expandableIds());
|
|
6710
|
+
}
|
|
6711
|
+
/** Lleva el foco al encabezado de una etapa expandible; devuelve el elemento, o `null` si no existe o no es expandible. */
|
|
6712
|
+
focusGroup(groupId) {
|
|
6713
|
+
// Solo las etapas expandibles tienen un encabezado enfocable (un botón).
|
|
6714
|
+
const head = this.host.nativeElement.querySelector(`button#${this.headId(groupId)}`);
|
|
6715
|
+
head?.focus({ preventScroll: true });
|
|
6716
|
+
return head;
|
|
6717
|
+
}
|
|
6718
|
+
metaOf(group, done, total) {
|
|
6719
|
+
const text = this.text();
|
|
6720
|
+
if (group.status === PROGRESS_STATUS.DONE)
|
|
6721
|
+
return group.date ?? text.statusDone;
|
|
6722
|
+
if (group.status === PROGRESS_STATUS.CURRENT)
|
|
6723
|
+
return text.currentMeta(done, total);
|
|
6724
|
+
return text.statusPending;
|
|
6725
|
+
}
|
|
6726
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MilestoneTimeline, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6727
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MilestoneTimeline, isStandalone: true, selector: "kf-milestone-timeline", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: true, transformFunction: null }, expandedIds: { classPropertyName: "expandedIds", publicName: "expandedIds", isSignal: true, isRequired: false, transformFunction: null }, nextItemId: { classPropertyName: "nextItemId", publicName: "nextItemId", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, headingLevel: { classPropertyName: "headingLevel", publicName: "headingLevel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedIds: "expandedIdsChange" }, ngImport: i0, template: "<div class=\"kf-milestone-timeline\">\n <div class=\"kf-milestone-timeline__toolbar\">\n <p class=\"kf-milestone-timeline__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n {{ text().heading }}\n </p>\n @if (hasExpandable()) {\n <kf-button\n class=\"kf-milestone-timeline__toggle-all\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"toggleAll()\"\n >\n {{ allExpanded() ? text().collapseAll : text().expandAll }}\n </kf-button>\n }\n </div>\n\n <ol class=\"kf-milestone-timeline__list\" [attr.aria-label]=\"ariaLabel() ?? null\">\n @for (row of rows(); track row.group.id) {\n <li\n class=\"kf-milestone-timeline__group\"\n [class]=\"'kf-milestone-timeline__group--' + row.group.status\"\n >\n @if (row.expandable) {\n <button\n type=\"button\"\n class=\"kf-milestone-timeline__head\"\n [id]=\"headId(row.group.id)\"\n [attr.aria-expanded]=\"isExpanded(row.group.id)\"\n [attr.aria-controls]=\"panelId(row.group.id)\"\n (click)=\"toggle(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n <kf-icon class=\"kf-milestone-timeline__chevron\" name=\"chevron-down\" size=\"sm\" />\n </button>\n\n <div\n class=\"kf-milestone-timeline__panel\"\n role=\"group\"\n [id]=\"panelId(row.group.id)\"\n [attr.aria-labelledby]=\"headId(row.group.id)\"\n [hidden]=\"!isExpanded(row.group.id)\"\n >\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.main; track item.id) {\n <li\n class=\"kf-milestone-timeline__item\"\n [class.kf-milestone-timeline__item--done]=\"item.done\"\n [class.kf-milestone-timeline__item--next]=\"item.id === nextItemId()\"\n >\n <kf-status-mark [status]=\"itemStatus(item)\" [small]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\"\n >: {{ item.done ? text().itemDone : text().itemPending }}</span\n ></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n @if (item.id === nextItemId()) {\n <kf-badge tone=\"info\">{{ text().next }}</kf-badge>\n }\n </li>\n }\n </ul>\n\n @if (row.exceptions.length > 0) {\n <div class=\"kf-milestone-timeline__exceptions\">\n <p class=\"kf-milestone-timeline__exceptions-title\">{{ text().exceptionsTitle }}</p>\n @if (row.happened.length > 0) {\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.happened; track item.id) {\n <li class=\"kf-milestone-timeline__item kf-milestone-timeline__item--done\">\n <kf-status-mark status=\"done\" [small]=\"true\" [exception]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\">: {{ text().itemDone }}</span></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n </li>\n }\n </ul>\n } @else {\n <p class=\"kf-milestone-timeline__exceptions-empty\">{{ text().noExceptions }}</p>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-milestone-timeline__head kf-milestone-timeline__head--static\"\n [id]=\"headId(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n </div>\n }\n </li>\n }\n </ol>\n</div>\n\n<!-- Nodo + t\u00EDtulo + estado de una etapa, compartido por el encabezado bot\u00F3n y el est\u00E1tico. -->\n<ng-template #headContent let-row>\n <kf-status-node [status]=\"row.group.status\" [icon]=\"row.group.icon\" />\n <span class=\"kf-milestone-timeline__text\">\n <span class=\"kf-milestone-timeline__title\">{{ row.group.label }}</span>\n <span class=\"kf-milestone-timeline__meta\">\n @if (row.group.status === 'done') {\n <span class=\"kf-sr-only\">{{ text().statusDone }}. </span>\n }\n {{ row.meta }}\n </span>\n </span>\n</ng-template>\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px}.kf-milestone-timeline{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-milestone-timeline__heading{margin:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-milestone-timeline__list{list-style:none;margin:0;padding:0}.kf-milestone-timeline__group{position:relative}.kf-milestone-timeline__group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border-strong)}.kf-milestone-timeline__group--done:not(:last-child):before{background:var(--kf-color-success-strong)}.kf-milestone-timeline__head{position:relative;z-index:1;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;width:100%;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter);background:none;border:0;border-radius:var(--kf-radius-sm);text-align:left;font:inherit;color:inherit}button.kf-milestone-timeline__head{cursor:pointer}button.kf-milestone-timeline__head:hover{background:var(--kf-color-surface)}button.kf-milestone-timeline__head:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px}.kf-milestone-timeline__text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-milestone-timeline__title{font-weight:700;font-size:var(--kf-font-size-body);color:var(--kf-color-ink)}.kf-milestone-timeline__meta{font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__group--current .kf-milestone-timeline__meta{color:var(--kf-color-primary-active);font-weight:600}.kf-milestone-timeline__chevron{flex:none;align-self:center;color:var(--kf-color-text);transition:transform .15s ease}.kf-milestone-timeline__head[aria-expanded=true] .kf-milestone-timeline__chevron{transform:rotate(180deg)}.kf-milestone-timeline__panel{padding-block:var(--kf-space-sm) var(--kf-space-md);padding-inline:calc(var(--_gutter) + var(--_node) + var(--kf-space-md)) var(--_gutter)}.kf-milestone-timeline__panel[hidden]{display:none}.kf-milestone-timeline__items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__item{display:flex;align-items:flex-start;gap:var(--kf-space-sm);font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__item kf-status-mark{margin-top:2px}.kf-milestone-timeline__item--done{color:var(--kf-color-ink)}.kf-milestone-timeline__item--next{color:var(--kf-color-ink);font-weight:700}.kf-milestone-timeline__item-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.kf-milestone-timeline__item-date{font-size:var(--kf-font-size-caption);font-weight:400;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions{margin-top:var(--kf-space-md);padding-top:var(--kf-space-sm);border-top:1px dashed var(--kf-color-border-strong)}.kf-milestone-timeline__exceptions-title{margin:0 0 var(--kf-space-sm);font-size:var(--kf-font-size-caption);font-weight:700;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions-empty{margin:0;font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}@media(max-width:640px),(pointer:coarse){.kf-milestone-timeline__toggle-all{--kf-button-min-height: 44px}}@media(prefers-reduced-motion:reduce){.kf-milestone-timeline__chevron{transition:none}}\n"], dependencies: [{ kind: "component", type: Badge, selector: "kf-badge", inputs: ["tone", "variant", "dot", "truncate"] }, { kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StatusMark, selector: "kf-status-mark", inputs: ["status", "small", "exception", "label"] }, { kind: "component", type: StatusNode, selector: "kf-status-node", inputs: ["status", "icon", "size", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5622
6728
|
}
|
|
6729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MilestoneTimeline, decorators: [{
|
|
6730
|
+
type: Component,
|
|
6731
|
+
args: [{ selector: 'kf-milestone-timeline', imports: [Badge, Button, Icon, NgTemplateOutlet, StatusMark, StatusNode], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"kf-milestone-timeline\">\n <div class=\"kf-milestone-timeline__toolbar\">\n <p class=\"kf-milestone-timeline__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n {{ text().heading }}\n </p>\n @if (hasExpandable()) {\n <kf-button\n class=\"kf-milestone-timeline__toggle-all\"\n variant=\"ghost\"\n size=\"sm\"\n (click)=\"toggleAll()\"\n >\n {{ allExpanded() ? text().collapseAll : text().expandAll }}\n </kf-button>\n }\n </div>\n\n <ol class=\"kf-milestone-timeline__list\" [attr.aria-label]=\"ariaLabel() ?? null\">\n @for (row of rows(); track row.group.id) {\n <li\n class=\"kf-milestone-timeline__group\"\n [class]=\"'kf-milestone-timeline__group--' + row.group.status\"\n >\n @if (row.expandable) {\n <button\n type=\"button\"\n class=\"kf-milestone-timeline__head\"\n [id]=\"headId(row.group.id)\"\n [attr.aria-expanded]=\"isExpanded(row.group.id)\"\n [attr.aria-controls]=\"panelId(row.group.id)\"\n (click)=\"toggle(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n <kf-icon class=\"kf-milestone-timeline__chevron\" name=\"chevron-down\" size=\"sm\" />\n </button>\n\n <div\n class=\"kf-milestone-timeline__panel\"\n role=\"group\"\n [id]=\"panelId(row.group.id)\"\n [attr.aria-labelledby]=\"headId(row.group.id)\"\n [hidden]=\"!isExpanded(row.group.id)\"\n >\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.main; track item.id) {\n <li\n class=\"kf-milestone-timeline__item\"\n [class.kf-milestone-timeline__item--done]=\"item.done\"\n [class.kf-milestone-timeline__item--next]=\"item.id === nextItemId()\"\n >\n <kf-status-mark [status]=\"itemStatus(item)\" [small]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\"\n >: {{ item.done ? text().itemDone : text().itemPending }}</span\n ></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n @if (item.id === nextItemId()) {\n <kf-badge tone=\"info\">{{ text().next }}</kf-badge>\n }\n </li>\n }\n </ul>\n\n @if (row.exceptions.length > 0) {\n <div class=\"kf-milestone-timeline__exceptions\">\n <p class=\"kf-milestone-timeline__exceptions-title\">{{ text().exceptionsTitle }}</p>\n @if (row.happened.length > 0) {\n <ul class=\"kf-milestone-timeline__items\">\n @for (item of row.happened; track item.id) {\n <li class=\"kf-milestone-timeline__item kf-milestone-timeline__item--done\">\n <kf-status-mark status=\"done\" [small]=\"true\" [exception]=\"true\" />\n <span class=\"kf-milestone-timeline__item-body\">\n <span\n >{{ item.label\n }}<span class=\"kf-sr-only\">: {{ text().itemDone }}</span></span\n >\n @if (item.date) {\n <span class=\"kf-milestone-timeline__item-date\">{{ item.date }}</span>\n }\n </span>\n </li>\n }\n </ul>\n } @else {\n <p class=\"kf-milestone-timeline__exceptions-empty\">{{ text().noExceptions }}</p>\n }\n </div>\n }\n </div>\n } @else {\n <div\n class=\"kf-milestone-timeline__head kf-milestone-timeline__head--static\"\n [id]=\"headId(row.group.id)\"\n >\n <ng-container *ngTemplateOutlet=\"headContent; context: { $implicit: row }\" />\n </div>\n }\n </li>\n }\n </ol>\n</div>\n\n<!-- Nodo + t\u00EDtulo + estado de una etapa, compartido por el encabezado bot\u00F3n y el est\u00E1tico. -->\n<ng-template #headContent let-row>\n <kf-status-node [status]=\"row.group.status\" [icon]=\"row.group.icon\" />\n <span class=\"kf-milestone-timeline__text\">\n <span class=\"kf-milestone-timeline__title\">{{ row.group.label }}</span>\n <span class=\"kf-milestone-timeline__meta\">\n @if (row.group.status === 'done') {\n <span class=\"kf-sr-only\">{{ text().statusDone }}. </span>\n }\n {{ row.meta }}\n </span>\n </span>\n</ng-template>\n", styles: [".kf-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}:host{display:block;--_node: var(--kf-status-node-size, 2.125rem);--_gutter: var(--kf-space-sm);--_pad-y: var(--kf-space-sm);--_line: 2px}.kf-milestone-timeline{display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__toolbar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm)}.kf-milestone-timeline__heading{margin:0;font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-sm);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-milestone-timeline__list{list-style:none;margin:0;padding:0}.kf-milestone-timeline__group{position:relative}.kf-milestone-timeline__group:not(:last-child):before{content:\"\";position:absolute;inset-inline-start:calc(var(--_gutter) + var(--_node) / 2 - var(--_line) / 2);top:calc(var(--_pad-y) + var(--_node));bottom:calc(var(--kf-space-xs) - var(--_pad-y));width:var(--_line);background:var(--kf-color-border-strong)}.kf-milestone-timeline__group--done:not(:last-child):before{background:var(--kf-color-success-strong)}.kf-milestone-timeline__head{position:relative;z-index:1;display:flex;align-items:flex-start;gap:var(--kf-space-md);box-sizing:border-box;width:100%;min-height:2.75rem;padding:var(--_pad-y) var(--_gutter);background:none;border:0;border-radius:var(--kf-radius-sm);text-align:left;font:inherit;color:inherit}button.kf-milestone-timeline__head{cursor:pointer}button.kf-milestone-timeline__head:hover{background:var(--kf-color-surface)}button.kf-milestone-timeline__head:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:2px}.kf-milestone-timeline__text{flex:1;display:flex;flex-direction:column;justify-content:center;gap:2px;min-width:0;min-height:var(--_node)}.kf-milestone-timeline__title{font-weight:700;font-size:var(--kf-font-size-body);color:var(--kf-color-ink)}.kf-milestone-timeline__meta{font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__group--current .kf-milestone-timeline__meta{color:var(--kf-color-primary-active);font-weight:600}.kf-milestone-timeline__chevron{flex:none;align-self:center;color:var(--kf-color-text);transition:transform .15s ease}.kf-milestone-timeline__head[aria-expanded=true] .kf-milestone-timeline__chevron{transform:rotate(180deg)}.kf-milestone-timeline__panel{padding-block:var(--kf-space-sm) var(--kf-space-md);padding-inline:calc(var(--_gutter) + var(--_node) + var(--kf-space-md)) var(--_gutter)}.kf-milestone-timeline__panel[hidden]{display:none}.kf-milestone-timeline__items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-milestone-timeline__item{display:flex;align-items:flex-start;gap:var(--kf-space-sm);font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-milestone-timeline__item kf-status-mark{margin-top:2px}.kf-milestone-timeline__item--done{color:var(--kf-color-ink)}.kf-milestone-timeline__item--next{color:var(--kf-color-ink);font-weight:700}.kf-milestone-timeline__item-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.kf-milestone-timeline__item-date{font-size:var(--kf-font-size-caption);font-weight:400;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions{margin-top:var(--kf-space-md);padding-top:var(--kf-space-sm);border-top:1px dashed var(--kf-color-border-strong)}.kf-milestone-timeline__exceptions-title{margin:0 0 var(--kf-space-sm);font-size:var(--kf-font-size-caption);font-weight:700;color:var(--kf-color-text)}.kf-milestone-timeline__exceptions-empty{margin:0;font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}@media(max-width:640px),(pointer:coarse){.kf-milestone-timeline__toggle-all{--kf-button-min-height: 44px}}@media(prefers-reduced-motion:reduce){.kf-milestone-timeline__chevron{transition:none}}\n"] }]
|
|
6732
|
+
}], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: true }] }], expandedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIds", required: false }] }, { type: i0.Output, args: ["expandedIdsChange"] }], nextItemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextItemId", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], headingLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingLevel", required: false }] }] } });
|
|
5623
6733
|
|
|
5624
6734
|
let modalId = 0;
|
|
5625
6735
|
/** Controles que pueden recibir el foco inicial. */
|
|
@@ -6021,13 +7131,538 @@ class Panel {
|
|
|
6021
7131
|
*/
|
|
6022
7132
|
actionClick = output();
|
|
6023
7133
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6024
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Panel, isStandalone: true, selector: "kf-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
|
|
7134
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Panel, isStandalone: true, selector: "kf-panel", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Card, selector: "kf-card", inputs: ["flush", "gap", "loading"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Link, selector: "kf-link", inputs: ["variant", "disabled", "href", "showArrow"] }] });
|
|
6025
7135
|
}
|
|
6026
7136
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, decorators: [{
|
|
6027
7137
|
type: Component,
|
|
6028
7138
|
args: [{ selector: 'kf-panel', imports: [Card, Divider, Link], template: "<kf-card>\n <div class=\"kf-panel__head\">\n <span class=\"kf-panel__title\">{{ title() }}</span>\n @if (actionLabel()) {\n <kf-link\n class=\"kf-panel__action\"\n [href]=\"actionHref() || '#'\"\n showArrow\n (click)=\"actionClick.emit($event)\"\n >\n {{ actionLabel() }}\n </kf-link>\n }\n </div>\n <kf-divider />\n <div class=\"kf-panel__body\">\n <ng-content />\n </div>\n</kf-card>\n", styles: [":host{display:flex;flex-direction:column}kf-card{flex:1}.kf-panel__head{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-sm)}.kf-panel__title{font-family:var(--kf-font-title);font-size:1.05rem;font-weight:700;color:var(--kf-color-ink)}.kf-panel__action{flex:none;font-size:.8125rem;font-weight:600}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
|
|
6029
7139
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
|
|
6030
7140
|
|
|
7141
|
+
let nextPopoverId = 0;
|
|
7142
|
+
/** Separación (px) entre el anfitrión y el panel. */
|
|
7143
|
+
const POPOVER_GAP = 8;
|
|
7144
|
+
/** Margen (px) mínimo entre el panel y el borde del viewport. */
|
|
7145
|
+
const POPOVER_VIEWPORT_MARGIN = 8;
|
|
7146
|
+
/**
|
|
7147
|
+
* Ventana (ms) tras abrir en la que un `scroll` no cierra el panel: enfocar el
|
|
7148
|
+
* anfitrión puede desplazar su contenedor para dejarlo a la vista, y ese
|
|
7149
|
+
* `scroll` llega después del `focusin` que abrió el panel.
|
|
7150
|
+
*/
|
|
7151
|
+
const SCROLL_GRACE_MS = 150;
|
|
7152
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
7153
|
+
/**
|
|
7154
|
+
* Panel visual del popover — componente interno, no exportado en
|
|
7155
|
+
* `public-api.ts`. `PopoverDirective` es la única forma pública de usarlo.
|
|
7156
|
+
*
|
|
7157
|
+
* `role="tooltip"` cuando es solo informativo (el patrón WAI-ARIA para
|
|
7158
|
+
* contenido que el lector anuncia vía `aria-describedby` del anfitrión) y
|
|
7159
|
+
* `role="dialog"` no modal cuando es `interactive` (puede tener enlaces o
|
|
7160
|
+
* botones adentro, que un `tooltip` no admite).
|
|
7161
|
+
*/
|
|
7162
|
+
class PopoverPanel {
|
|
7163
|
+
template = input.required(/* @ts-ignore */
|
|
7164
|
+
...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
|
|
7165
|
+
interactive = input(false, /* @ts-ignore */
|
|
7166
|
+
...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
7167
|
+
label = input(null, /* @ts-ignore */
|
|
7168
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
7169
|
+
pointerEntered = output();
|
|
7170
|
+
pointerLeft = output();
|
|
7171
|
+
focusEntered = output();
|
|
7172
|
+
focusLeft = output();
|
|
7173
|
+
onPointerEnter(event) {
|
|
7174
|
+
if (event.pointerType !== 'touch') {
|
|
7175
|
+
this.pointerEntered.emit();
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
onPointerLeave(event) {
|
|
7179
|
+
if (event.pointerType !== 'touch') {
|
|
7180
|
+
this.pointerLeft.emit();
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7184
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.0", type: PopoverPanel, isStandalone: true, selector: "kf-popover-panel", inputs: { template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: true, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointerEntered: "pointerEntered", pointerLeft: "pointerLeft", focusEntered: "focusEntered", focusLeft: "focusLeft" }, host: { listeners: { "pointerenter": "onPointerEnter($event)", "pointerleave": "onPointerLeave($event)", "focusin": "focusEntered.emit()", "focusout": "focusLeft.emit($event)" }, properties: { "attr.role": "interactive() ? 'dialog' : 'tooltip'", "attr.aria-label": "label()", "class.kf-popover-panel--interactive": "interactive()" } }, ngImport: i0, template: `<ng-container [ngTemplateOutlet]="template()" />`, isInline: true, styles: [":host{display:block;box-sizing:border-box;max-width:min(22rem,calc(100vw - 2 * var(--kf-space-sm)));max-height:calc(100vh - 2 * var(--kf-space-sm));padding:var(--kf-space-md);overflow:auto;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);color:var(--kf-color-text);font-family:var(--kf-font-body);font-size:var(--kf-font-size-body-sm);line-height:1.45;box-shadow:var(--kf-shadow-lg);animation:kf-popover-enter .12s ease-out}:host(:not(.kf-popover-panel--interactive)){pointer-events:none}@media(prefers-reduced-motion:reduce){:host{animation:none}}@keyframes kf-popover-enter{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7185
|
+
}
|
|
7186
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverPanel, decorators: [{
|
|
7187
|
+
type: Component,
|
|
7188
|
+
args: [{ selector: 'kf-popover-panel', imports: [NgTemplateOutlet], template: `<ng-container [ngTemplateOutlet]="template()" />`, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
7189
|
+
'[attr.role]': "interactive() ? 'dialog' : 'tooltip'",
|
|
7190
|
+
'[attr.aria-label]': 'label()',
|
|
7191
|
+
'[class.kf-popover-panel--interactive]': 'interactive()',
|
|
7192
|
+
'(pointerenter)': 'onPointerEnter($event)',
|
|
7193
|
+
'(pointerleave)': 'onPointerLeave($event)',
|
|
7194
|
+
'(focusin)': 'focusEntered.emit()',
|
|
7195
|
+
'(focusout)': 'focusLeft.emit($event)',
|
|
7196
|
+
}, styles: [":host{display:block;box-sizing:border-box;max-width:min(22rem,calc(100vw - 2 * var(--kf-space-sm)));max-height:calc(100vh - 2 * var(--kf-space-sm));padding:var(--kf-space-md);overflow:auto;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);color:var(--kf-color-text);font-family:var(--kf-font-body);font-size:var(--kf-font-size-body-sm);line-height:1.45;box-shadow:var(--kf-shadow-lg);animation:kf-popover-enter .12s ease-out}:host(:not(.kf-popover-panel--interactive)){pointer-events:none}@media(prefers-reduced-motion:reduce){:host{animation:none}}@keyframes kf-popover-enter{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
7197
|
+
}], propDecorators: { template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: true }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], pointerEntered: [{ type: i0.Output, args: ["pointerEntered"] }], pointerLeft: [{ type: i0.Output, args: ["pointerLeft"] }], focusEntered: [{ type: i0.Output, args: ["focusEntered"] }], focusLeft: [{ type: i0.Output, args: ["focusLeft"] }] } });
|
|
7198
|
+
/**
|
|
7199
|
+
* Popover de contenido enriquecido anclado a un elemento — la contraparte de
|
|
7200
|
+
* `kfTooltip` para cuando el texto plano no alcanza (varias líneas, énfasis,
|
|
7201
|
+
* un desglose, enlaces). Mismo mecanismo de `@angular/cdk/overlay`. El host
|
|
7202
|
+
* sigue siendo el elemento que recibe el foco: la directiva no lo hace
|
|
7203
|
+
* focusable, así que va sobre un `<button>`, un `<a>` o un elemento con
|
|
7204
|
+
* `tabindex="0"`.
|
|
7205
|
+
*
|
|
7206
|
+
* <button [kfPopover]="detail" kfPopoverPlacement="top">Etapa</button>
|
|
7207
|
+
* <ng-template #detail><strong>Recepción</strong><p>Completada el 12/05.</p></ng-template>
|
|
7208
|
+
*
|
|
7209
|
+
* Apertura: puntero (hover, tras `kfPopoverShowDelay` ms; NO se abre con puntero
|
|
7210
|
+
* táctil) y foco de teclado (al instante, solo si el foco es visible: un
|
|
7211
|
+
* clic con el mouse no lo abre). Cierre: Escape (descarta el panel hasta que
|
|
7212
|
+
* el puntero y el foco salgan y vuelvan), salida del puntero tras
|
|
7213
|
+
* `kfPopoverHideDelay` ms, pérdida de foco, scroll de cualquier contenedor y
|
|
7214
|
+
* redimensionado de la ventana. El panel se reposiciona dentro del viewport
|
|
7215
|
+
* (invierte el lado si no cabe).
|
|
7216
|
+
*
|
|
7217
|
+
* Con `kfPopoverInteractive`, el puntero puede entrar al panel sin cerrarlo y
|
|
7218
|
+
* el panel es un `dialog` no modal: Tab desde el anfitrión entra al primer
|
|
7219
|
+
* control del panel y Mayús+Tab desde ese control vuelve al anfitrión. Sin
|
|
7220
|
+
* `kfPopoverInteractive` (default), el panel es un `tooltip` que no captura
|
|
7221
|
+
* el puntero y no debe contener controles.
|
|
7222
|
+
*
|
|
7223
|
+
* Si el anfitrión no es interactivo pero vive dentro de un control (un botón,
|
|
7224
|
+
* un enlace, un elemento con `tabindex`), los eventos de puntero, foco y
|
|
7225
|
+
* teclado se escuchan en ese control y el panel se ancla al anfitrión. Es el
|
|
7226
|
+
* caso de un indicador dentro de una celda interactiva de `kf-data-table`:
|
|
7227
|
+
*
|
|
7228
|
+
* <ng-template #stageCell let-row>
|
|
7229
|
+
* <span [kfPopover]="detail"><kf-status-mark [status]="row.status" /></span>
|
|
7230
|
+
* </ng-template>
|
|
7231
|
+
*
|
|
7232
|
+
* Accesibilidad: mientras está abierto, el elemento que recibe el foco
|
|
7233
|
+
* referencia el panel con `aria-describedby` (se conserva cualquier valor
|
|
7234
|
+
* previo).
|
|
7235
|
+
*/
|
|
7236
|
+
class PopoverDirective {
|
|
7237
|
+
/** Contenido del popover (un `ng-template`). `null`/`undefined` = no se abre. */
|
|
7238
|
+
kfPopover = input.required(/* @ts-ignore */
|
|
7239
|
+
...(ngDevMode ? [{ debugName: "kfPopover" }] : /* istanbul ignore next */ []));
|
|
7240
|
+
/** Lado preferido; si no cabe en el viewport, se invierte. Default `bottom`. */
|
|
7241
|
+
kfPopoverPlacement = input('bottom', /* @ts-ignore */
|
|
7242
|
+
...(ngDevMode ? [{ debugName: "kfPopoverPlacement" }] : /* istanbul ignore next */ []));
|
|
7243
|
+
/** Retardo (ms) antes de abrir con el puntero. El foco de teclado abre sin retardo. */
|
|
7244
|
+
kfPopoverShowDelay = input(150, { ...(ngDevMode ? { debugName: "kfPopoverShowDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
7245
|
+
/** Retardo (ms) antes de cerrar al salir el puntero. */
|
|
7246
|
+
kfPopoverHideDelay = input(150, { ...(ngDevMode ? { debugName: "kfPopoverHideDelay" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
7247
|
+
/** Desactiva el popover (y lo cierra si estaba abierto). */
|
|
7248
|
+
kfPopoverDisabled = input(false, { ...(ngDevMode ? { debugName: "kfPopoverDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
7249
|
+
/** Permite que el puntero entre al panel sin cerrarlo y que el teclado llegue a sus controles. */
|
|
7250
|
+
kfPopoverInteractive = input(false, { ...(ngDevMode ? { debugName: "kfPopoverInteractive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
7251
|
+
/** Nombre accesible del panel cuando es `interactive` (rol `dialog`). */
|
|
7252
|
+
kfPopoverLabel = input(null, /* @ts-ignore */
|
|
7253
|
+
...(ngDevMode ? [{ debugName: "kfPopoverLabel" }] : /* istanbul ignore next */ []));
|
|
7254
|
+
/** Emite `true` al abrirse y `false` al cerrarse. */
|
|
7255
|
+
kfPopoverOpenChange = output();
|
|
7256
|
+
host = inject(ElementRef);
|
|
7257
|
+
overlayService = inject(Overlay);
|
|
7258
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
7259
|
+
injector = inject(Injector);
|
|
7260
|
+
document = inject(DOCUMENT);
|
|
7261
|
+
destroyRef = inject(DestroyRef);
|
|
7262
|
+
panelId = `kf-popover-${nextPopoverId++}`;
|
|
7263
|
+
openState = signal(false, /* @ts-ignore */
|
|
7264
|
+
...(ngDevMode ? [{ debugName: "openState" }] : /* istanbul ignore next */ []));
|
|
7265
|
+
/** `true` mientras el panel está abierto. */
|
|
7266
|
+
isOpen = this.openState.asReadonly();
|
|
7267
|
+
/**
|
|
7268
|
+
* Elemento que recibe los eventos de puntero, foco y teclado: el propio
|
|
7269
|
+
* anfitrión o, si este no es interactivo pero vive dentro de un control
|
|
7270
|
+
* (botón, enlace, `tabindex`), ese control. `aria-describedby` va en él.
|
|
7271
|
+
*/
|
|
7272
|
+
trigger = this.host.nativeElement;
|
|
7273
|
+
overlayRef = null;
|
|
7274
|
+
panelElement = null;
|
|
7275
|
+
showTimer = null;
|
|
7276
|
+
hideTimer = null;
|
|
7277
|
+
openedAt = 0;
|
|
7278
|
+
teardownGlobalListeners = null;
|
|
7279
|
+
destroyed = false;
|
|
7280
|
+
pointerOnHost = false;
|
|
7281
|
+
pointerOnPanel = false;
|
|
7282
|
+
focusOnHost = false;
|
|
7283
|
+
focusOnPanel = false;
|
|
7284
|
+
/** Escape lo descartó: no reabre hasta que puntero y foco hayan salido. */
|
|
7285
|
+
dismissed = false;
|
|
7286
|
+
constructor() {
|
|
7287
|
+
effect(() => {
|
|
7288
|
+
if (this.kfPopoverDisabled()) {
|
|
7289
|
+
untracked(() => this.close());
|
|
7290
|
+
}
|
|
7291
|
+
});
|
|
7292
|
+
// El elemento que recibe los eventos se resuelve tras el primer render: en
|
|
7293
|
+
// una vista embebida (ej. dentro de la celda de un `kf-data-table`) el
|
|
7294
|
+
// anfitrión aún no tiene a su ancestro interactivo en el DOM al construirse.
|
|
7295
|
+
afterNextRender(() => {
|
|
7296
|
+
if (this.destroyed) {
|
|
7297
|
+
return;
|
|
7298
|
+
}
|
|
7299
|
+
const target = this.resolveTrigger();
|
|
7300
|
+
this.trigger = target;
|
|
7301
|
+
const listeners = [
|
|
7302
|
+
['pointerenter', this.onHostPointerEnter],
|
|
7303
|
+
['pointerleave', this.onHostPointerLeave],
|
|
7304
|
+
['focusin', this.onHostFocusIn],
|
|
7305
|
+
['focusout', this.onHostFocusOut],
|
|
7306
|
+
['keydown', this.onHostKeydown],
|
|
7307
|
+
];
|
|
7308
|
+
for (const [type, listener] of listeners) {
|
|
7309
|
+
target.addEventListener(type, listener);
|
|
7310
|
+
}
|
|
7311
|
+
this.destroyRef.onDestroy(() => {
|
|
7312
|
+
for (const [type, listener] of listeners) {
|
|
7313
|
+
target.removeEventListener(type, listener);
|
|
7314
|
+
}
|
|
7315
|
+
});
|
|
7316
|
+
});
|
|
7317
|
+
}
|
|
7318
|
+
ngOnDestroy() {
|
|
7319
|
+
this.destroyed = true;
|
|
7320
|
+
this.clearTimers();
|
|
7321
|
+
this.dispose();
|
|
7322
|
+
}
|
|
7323
|
+
/** Cierra el panel de inmediato, sin retardo. */
|
|
7324
|
+
close() {
|
|
7325
|
+
this.clearTimers();
|
|
7326
|
+
this.dispose();
|
|
7327
|
+
}
|
|
7328
|
+
onHostPointerEnter = (event) => {
|
|
7329
|
+
if (event.pointerType === 'touch') {
|
|
7330
|
+
return;
|
|
7331
|
+
}
|
|
7332
|
+
this.pointerOnHost = true;
|
|
7333
|
+
this.reevaluate(this.kfPopoverShowDelay());
|
|
7334
|
+
};
|
|
7335
|
+
onHostPointerLeave = (event) => {
|
|
7336
|
+
if (event.pointerType === 'touch') {
|
|
7337
|
+
return;
|
|
7338
|
+
}
|
|
7339
|
+
this.pointerOnHost = false;
|
|
7340
|
+
this.reevaluate(this.kfPopoverHideDelay());
|
|
7341
|
+
};
|
|
7342
|
+
onHostFocusIn = (event) => {
|
|
7343
|
+
// Solo foco de teclado: un clic o un toque enfocan el botón pero no
|
|
7344
|
+
// deben abrir el panel (el clic tiene su propia acción).
|
|
7345
|
+
if (!this.isKeyboardFocus(event.target)) {
|
|
7346
|
+
return;
|
|
7347
|
+
}
|
|
7348
|
+
this.focusOnHost = true;
|
|
7349
|
+
this.reevaluate(0);
|
|
7350
|
+
};
|
|
7351
|
+
onHostFocusOut = (event) => {
|
|
7352
|
+
const next = event.relatedTarget;
|
|
7353
|
+
if (next && this.trigger.contains(next)) {
|
|
7354
|
+
return;
|
|
7355
|
+
}
|
|
7356
|
+
this.focusOnHost = false;
|
|
7357
|
+
if (next && this.kfPopoverInteractive() && this.panelElement?.contains(next)) {
|
|
7358
|
+
// El foco pasa al panel: su propio `focusin` lo registra.
|
|
7359
|
+
return;
|
|
7360
|
+
}
|
|
7361
|
+
this.reevaluate(0);
|
|
7362
|
+
};
|
|
7363
|
+
onHostKeydown = (event) => {
|
|
7364
|
+
if (event.key !== 'Tab' ||
|
|
7365
|
+
event.shiftKey ||
|
|
7366
|
+
event.defaultPrevented ||
|
|
7367
|
+
!this.panelElement ||
|
|
7368
|
+
!this.kfPopoverInteractive()) {
|
|
7369
|
+
return;
|
|
7370
|
+
}
|
|
7371
|
+
const first = this.panelElement.querySelector(FOCUSABLE_SELECTOR);
|
|
7372
|
+
if (first) {
|
|
7373
|
+
event.preventDefault();
|
|
7374
|
+
first.focus();
|
|
7375
|
+
}
|
|
7376
|
+
};
|
|
7377
|
+
get wanted() {
|
|
7378
|
+
if (this.kfPopoverDisabled()) {
|
|
7379
|
+
return false;
|
|
7380
|
+
}
|
|
7381
|
+
const interactive = this.kfPopoverInteractive();
|
|
7382
|
+
return (this.pointerOnHost ||
|
|
7383
|
+
this.focusOnHost ||
|
|
7384
|
+
(interactive && (this.pointerOnPanel || this.focusOnPanel)));
|
|
7385
|
+
}
|
|
7386
|
+
/** Abre o cierra según los cuatro orígenes (puntero/foco × anfitrión/panel). */
|
|
7387
|
+
reevaluate(delay) {
|
|
7388
|
+
if (this.wanted) {
|
|
7389
|
+
this.clearHideTimer();
|
|
7390
|
+
if (!this.dismissed) {
|
|
7391
|
+
this.scheduleOpen(delay);
|
|
7392
|
+
}
|
|
7393
|
+
return;
|
|
7394
|
+
}
|
|
7395
|
+
this.dismissed = false;
|
|
7396
|
+
this.clearShowTimer();
|
|
7397
|
+
this.scheduleClose(delay);
|
|
7398
|
+
}
|
|
7399
|
+
scheduleOpen(delay) {
|
|
7400
|
+
if (this.overlayRef || this.showTimer !== null) {
|
|
7401
|
+
return;
|
|
7402
|
+
}
|
|
7403
|
+
if (delay <= 0) {
|
|
7404
|
+
this.open();
|
|
7405
|
+
return;
|
|
7406
|
+
}
|
|
7407
|
+
this.showTimer = setTimeout(() => {
|
|
7408
|
+
this.showTimer = null;
|
|
7409
|
+
if (this.wanted && !this.dismissed) {
|
|
7410
|
+
this.open();
|
|
7411
|
+
}
|
|
7412
|
+
}, delay);
|
|
7413
|
+
}
|
|
7414
|
+
scheduleClose(delay) {
|
|
7415
|
+
if (!this.overlayRef || this.hideTimer !== null) {
|
|
7416
|
+
return;
|
|
7417
|
+
}
|
|
7418
|
+
if (delay <= 0) {
|
|
7419
|
+
this.dispose();
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
this.hideTimer = setTimeout(() => {
|
|
7423
|
+
this.hideTimer = null;
|
|
7424
|
+
if (!this.wanted) {
|
|
7425
|
+
this.dispose();
|
|
7426
|
+
}
|
|
7427
|
+
}, delay);
|
|
7428
|
+
}
|
|
7429
|
+
open() {
|
|
7430
|
+
const template = this.kfPopover();
|
|
7431
|
+
if (this.overlayRef || !template || this.kfPopoverDisabled()) {
|
|
7432
|
+
return;
|
|
7433
|
+
}
|
|
7434
|
+
const positionStrategy = this.overlayService
|
|
7435
|
+
.position()
|
|
7436
|
+
.flexibleConnectedTo(this.host)
|
|
7437
|
+
.withPositions(this.positionsFor(this.kfPopoverPlacement()))
|
|
7438
|
+
.withViewportMargin(POPOVER_VIEWPORT_MARGIN)
|
|
7439
|
+
.withFlexibleDimensions(false)
|
|
7440
|
+
.withPush(true);
|
|
7441
|
+
const overlayRef = this.overlayService.create({
|
|
7442
|
+
positionStrategy,
|
|
7443
|
+
// El cierre por scroll lo resuelve `listenGlobal`: el scroll strategy de
|
|
7444
|
+
// CDK solo ve ancestros registrados como `cdkScrollable`.
|
|
7445
|
+
scrollStrategy: this.overlayService.scrollStrategies.noop(),
|
|
7446
|
+
});
|
|
7447
|
+
const portal = new ComponentPortal(PopoverPanel, this.viewContainerRef, this.injector);
|
|
7448
|
+
const panelRef = overlayRef.attach(portal);
|
|
7449
|
+
panelRef.setInput('template', template);
|
|
7450
|
+
panelRef.setInput('interactive', this.kfPopoverInteractive());
|
|
7451
|
+
panelRef.setInput('label', this.kfPopoverInteractive() ? this.kfPopoverLabel() : null);
|
|
7452
|
+
this.panelElement = panelRef.location.nativeElement;
|
|
7453
|
+
this.panelElement.id = this.panelId;
|
|
7454
|
+
panelRef.instance.pointerEntered.subscribe(() => {
|
|
7455
|
+
this.pointerOnPanel = true;
|
|
7456
|
+
this.reevaluate(0);
|
|
7457
|
+
});
|
|
7458
|
+
panelRef.instance.pointerLeft.subscribe(() => {
|
|
7459
|
+
this.pointerOnPanel = false;
|
|
7460
|
+
this.reevaluate(this.kfPopoverHideDelay());
|
|
7461
|
+
});
|
|
7462
|
+
panelRef.instance.focusEntered.subscribe(() => {
|
|
7463
|
+
this.focusOnPanel = true;
|
|
7464
|
+
this.reevaluate(0);
|
|
7465
|
+
});
|
|
7466
|
+
panelRef.instance.focusLeft.subscribe((event) => this.onPanelFocusOut(event));
|
|
7467
|
+
this.panelElement.addEventListener('keydown', this.onPanelKeydown);
|
|
7468
|
+
// Renderiza el contenido antes de medir, para posicionar con el tamaño real.
|
|
7469
|
+
panelRef.changeDetectorRef.detectChanges();
|
|
7470
|
+
overlayRef.updatePosition();
|
|
7471
|
+
this.overlayRef = overlayRef;
|
|
7472
|
+
this.addDescribedBy();
|
|
7473
|
+
this.listenGlobal();
|
|
7474
|
+
this.openedAt = Date.now();
|
|
7475
|
+
this.openState.set(true);
|
|
7476
|
+
this.kfPopoverOpenChange.emit(true);
|
|
7477
|
+
}
|
|
7478
|
+
dispose() {
|
|
7479
|
+
const overlayRef = this.overlayRef;
|
|
7480
|
+
const wasOpen = overlayRef !== null;
|
|
7481
|
+
// Primero se suelta el estado y después se destruye el panel: quitar del DOM
|
|
7482
|
+
// un elemento con foco dispara `focusout` de forma síncrona, y ese manejador
|
|
7483
|
+
// no debe volver a entrar a `dispose()` con el panel a medio destruir.
|
|
7484
|
+
this.overlayRef = null;
|
|
7485
|
+
this.teardownGlobalListeners?.();
|
|
7486
|
+
this.teardownGlobalListeners = null;
|
|
7487
|
+
this.panelElement?.removeEventListener('keydown', this.onPanelKeydown);
|
|
7488
|
+
this.panelElement = null;
|
|
7489
|
+
this.pointerOnPanel = false;
|
|
7490
|
+
this.focusOnPanel = false;
|
|
7491
|
+
overlayRef?.dispose();
|
|
7492
|
+
this.removeDescribedBy();
|
|
7493
|
+
if (wasOpen && !this.destroyed) {
|
|
7494
|
+
this.openState.set(false);
|
|
7495
|
+
this.kfPopoverOpenChange.emit(false);
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
onPanelFocusOut(event) {
|
|
7499
|
+
if (!this.overlayRef) {
|
|
7500
|
+
// `focusout` por quitar del DOM el panel que se está cerrando.
|
|
7501
|
+
return;
|
|
7502
|
+
}
|
|
7503
|
+
this.focusOnPanel = false;
|
|
7504
|
+
const next = event.relatedTarget;
|
|
7505
|
+
if (next && (this.panelElement?.contains(next) || this.trigger.contains(next))) {
|
|
7506
|
+
return;
|
|
7507
|
+
}
|
|
7508
|
+
this.reevaluate(0);
|
|
7509
|
+
}
|
|
7510
|
+
/** Mayús+Tab desde el primer control del panel devuelve el foco al anfitrión. */
|
|
7511
|
+
onPanelKeydown = (event) => {
|
|
7512
|
+
if (event.key !== 'Tab' || !event.shiftKey || !this.panelElement) {
|
|
7513
|
+
return;
|
|
7514
|
+
}
|
|
7515
|
+
const first = this.panelElement.querySelector(FOCUSABLE_SELECTOR);
|
|
7516
|
+
if (first && this.document.activeElement === first) {
|
|
7517
|
+
event.preventDefault();
|
|
7518
|
+
this.focusHost();
|
|
7519
|
+
}
|
|
7520
|
+
};
|
|
7521
|
+
focusHost() {
|
|
7522
|
+
const host = this.trigger;
|
|
7523
|
+
const focusable = host.matches(FOCUSABLE_SELECTOR)
|
|
7524
|
+
? host
|
|
7525
|
+
: host.querySelector(FOCUSABLE_SELECTOR);
|
|
7526
|
+
focusable?.focus({ preventScroll: true });
|
|
7527
|
+
}
|
|
7528
|
+
/**
|
|
7529
|
+
* Escape, scroll y resize mientras el panel está abierto. En fase de captura
|
|
7530
|
+
* y sobre `document`: `scroll` no burbujea (hay que capturarlo para ver el
|
|
7531
|
+
* de cualquier contenedor) y Escape debe cerrar primero el popover sin
|
|
7532
|
+
* llegar al modal o drawer que lo contenga.
|
|
7533
|
+
*/
|
|
7534
|
+
listenGlobal() {
|
|
7535
|
+
const view = this.document.defaultView;
|
|
7536
|
+
const onKeydown = (event) => {
|
|
7537
|
+
if (event.key !== 'Escape') {
|
|
7538
|
+
return;
|
|
7539
|
+
}
|
|
7540
|
+
event.stopPropagation();
|
|
7541
|
+
const focusInPanel = this.panelElement?.contains(this.document.activeElement) ?? false;
|
|
7542
|
+
this.dismissed = true;
|
|
7543
|
+
this.close();
|
|
7544
|
+
if (focusInPanel) {
|
|
7545
|
+
this.focusHost();
|
|
7546
|
+
}
|
|
7547
|
+
};
|
|
7548
|
+
const onScroll = (event) => {
|
|
7549
|
+
const target = event.target;
|
|
7550
|
+
if (Date.now() - this.openedAt < SCROLL_GRACE_MS) {
|
|
7551
|
+
return;
|
|
7552
|
+
}
|
|
7553
|
+
if (target && this.panelElement?.contains(target)) {
|
|
7554
|
+
return;
|
|
7555
|
+
}
|
|
7556
|
+
this.close();
|
|
7557
|
+
};
|
|
7558
|
+
const onResize = () => this.close();
|
|
7559
|
+
this.document.addEventListener('keydown', onKeydown, true);
|
|
7560
|
+
this.document.addEventListener('scroll', onScroll, true);
|
|
7561
|
+
view?.addEventListener('resize', onResize);
|
|
7562
|
+
this.teardownGlobalListeners = () => {
|
|
7563
|
+
this.document.removeEventListener('keydown', onKeydown, true);
|
|
7564
|
+
this.document.removeEventListener('scroll', onScroll, true);
|
|
7565
|
+
view?.removeEventListener('resize', onResize);
|
|
7566
|
+
};
|
|
7567
|
+
}
|
|
7568
|
+
resolveTrigger() {
|
|
7569
|
+
const host = this.host.nativeElement;
|
|
7570
|
+
return host.closest(FOCUSABLE_SELECTOR) ?? host;
|
|
7571
|
+
}
|
|
7572
|
+
/** Foco de teclado (`:focus-visible`); sin soporte del selector, se asume teclado. */
|
|
7573
|
+
isKeyboardFocus(target) {
|
|
7574
|
+
if (!(target instanceof Element)) {
|
|
7575
|
+
return true;
|
|
7576
|
+
}
|
|
7577
|
+
try {
|
|
7578
|
+
return target.matches(':focus-visible');
|
|
7579
|
+
}
|
|
7580
|
+
catch {
|
|
7581
|
+
return true;
|
|
7582
|
+
}
|
|
7583
|
+
}
|
|
7584
|
+
addDescribedBy() {
|
|
7585
|
+
const tokens = this.describedByTokens();
|
|
7586
|
+
if (!tokens.includes(this.panelId)) {
|
|
7587
|
+
tokens.push(this.panelId);
|
|
7588
|
+
this.trigger.setAttribute('aria-describedby', tokens.join(' '));
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
removeDescribedBy() {
|
|
7592
|
+
const remaining = this.describedByTokens().filter((token) => token !== this.panelId);
|
|
7593
|
+
if (remaining.length > 0) {
|
|
7594
|
+
this.trigger.setAttribute('aria-describedby', remaining.join(' '));
|
|
7595
|
+
}
|
|
7596
|
+
else {
|
|
7597
|
+
this.trigger.removeAttribute('aria-describedby');
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
describedByTokens() {
|
|
7601
|
+
return (this.trigger.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean);
|
|
7602
|
+
}
|
|
7603
|
+
clearShowTimer() {
|
|
7604
|
+
if (this.showTimer !== null) {
|
|
7605
|
+
clearTimeout(this.showTimer);
|
|
7606
|
+
this.showTimer = null;
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
clearHideTimer() {
|
|
7610
|
+
if (this.hideTimer !== null) {
|
|
7611
|
+
clearTimeout(this.hideTimer);
|
|
7612
|
+
this.hideTimer = null;
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
clearTimers() {
|
|
7616
|
+
this.clearShowTimer();
|
|
7617
|
+
this.clearHideTimer();
|
|
7618
|
+
}
|
|
7619
|
+
/**
|
|
7620
|
+
* Posiciones en orden de preferencia: el lado pedido (centrado, luego
|
|
7621
|
+
* alineado al inicio y al final del anfitrión), el lado opuesto con las
|
|
7622
|
+
* mismas tres alineaciones y, por último, los dos lados perpendiculares.
|
|
7623
|
+
* Así un anfitrión pegado a una esquina encuentra una posición que cabe
|
|
7624
|
+
* entera en vez de depender solo del empuje al viewport.
|
|
7625
|
+
*/
|
|
7626
|
+
positionsFor(placement) {
|
|
7627
|
+
const opposite = {
|
|
7628
|
+
top: 'bottom',
|
|
7629
|
+
bottom: 'top',
|
|
7630
|
+
left: 'right',
|
|
7631
|
+
right: 'left',
|
|
7632
|
+
};
|
|
7633
|
+
const perpendicular = placement === 'top' || placement === 'bottom' ? ['right', 'left'] : ['bottom', 'top'];
|
|
7634
|
+
return [placement, opposite[placement], ...perpendicular].flatMap((side) => ['center', 'start', 'end'].map((align) => this.positionFor(side, align)));
|
|
7635
|
+
}
|
|
7636
|
+
positionFor(side, align) {
|
|
7637
|
+
if (side === 'top' || side === 'bottom') {
|
|
7638
|
+
return {
|
|
7639
|
+
originX: align,
|
|
7640
|
+
originY: side === 'top' ? 'top' : 'bottom',
|
|
7641
|
+
overlayX: align,
|
|
7642
|
+
overlayY: side === 'top' ? 'bottom' : 'top',
|
|
7643
|
+
offsetY: side === 'top' ? -POPOVER_GAP : POPOVER_GAP,
|
|
7644
|
+
};
|
|
7645
|
+
}
|
|
7646
|
+
const vertical = align === 'center' ? 'center' : align === 'start' ? 'top' : 'bottom';
|
|
7647
|
+
return {
|
|
7648
|
+
originX: side === 'left' ? 'start' : 'end',
|
|
7649
|
+
originY: vertical,
|
|
7650
|
+
overlayX: side === 'left' ? 'end' : 'start',
|
|
7651
|
+
overlayY: vertical,
|
|
7652
|
+
offsetX: side === 'left' ? -POPOVER_GAP : POPOVER_GAP,
|
|
7653
|
+
};
|
|
7654
|
+
}
|
|
7655
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
7656
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.0", type: PopoverDirective, isStandalone: true, selector: "[kfPopover]", inputs: { kfPopover: { classPropertyName: "kfPopover", publicName: "kfPopover", isSignal: true, isRequired: true, transformFunction: null }, kfPopoverPlacement: { classPropertyName: "kfPopoverPlacement", publicName: "kfPopoverPlacement", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverShowDelay: { classPropertyName: "kfPopoverShowDelay", publicName: "kfPopoverShowDelay", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverHideDelay: { classPropertyName: "kfPopoverHideDelay", publicName: "kfPopoverHideDelay", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverDisabled: { classPropertyName: "kfPopoverDisabled", publicName: "kfPopoverDisabled", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverInteractive: { classPropertyName: "kfPopoverInteractive", publicName: "kfPopoverInteractive", isSignal: true, isRequired: false, transformFunction: null }, kfPopoverLabel: { classPropertyName: "kfPopoverLabel", publicName: "kfPopoverLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { kfPopoverOpenChange: "kfPopoverOpenChange" }, exportAs: ["kfPopover"], ngImport: i0 });
|
|
7657
|
+
}
|
|
7658
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PopoverDirective, decorators: [{
|
|
7659
|
+
type: Directive,
|
|
7660
|
+
args: [{
|
|
7661
|
+
selector: '[kfPopover]',
|
|
7662
|
+
exportAs: 'kfPopover',
|
|
7663
|
+
}]
|
|
7664
|
+
}], ctorParameters: () => [], propDecorators: { kfPopover: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopover", required: true }] }], kfPopoverPlacement: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverPlacement", required: false }] }], kfPopoverShowDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverShowDelay", required: false }] }], kfPopoverHideDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverHideDelay", required: false }] }], kfPopoverDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverDisabled", required: false }] }], kfPopoverInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverInteractive", required: false }] }], kfPopoverLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kfPopoverLabel", required: false }] }], kfPopoverOpenChange: [{ type: i0.Output, args: ["kfPopoverOpenChange"] }] } });
|
|
7665
|
+
|
|
6031
7666
|
/**
|
|
6032
7667
|
* Cabecera completa del portal (página 31) — franja de administrador
|
|
6033
7668
|
* (acento ámbar, solo admins), barra de marca con logo, navegación y
|
|
@@ -6179,36 +7814,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
6179
7814
|
args: ['window:resize']
|
|
6180
7815
|
}] } });
|
|
6181
7816
|
|
|
6182
|
-
/**
|
|
6183
|
-
* Barra de progreso de un solo valor (0-100%) con color por severidad —
|
|
6184
|
-
* para un porcentaje simple, a diferencia de `kf-bucket-progress`, que
|
|
6185
|
-
* dibuja varias categorías proporcionales en la misma barra.
|
|
6186
|
-
*
|
|
6187
|
-
* <kf-progress [value]="80" tone="ok" label="80%" />
|
|
6188
|
-
* <kf-progress [value]="ratio()" [tone]="severityTone()" />
|
|
6189
|
-
*
|
|
6190
|
-
* `value` se clampea a [0, 100] — un consumidor que calcule un ratio fuera
|
|
6191
|
-
* de rango (ej. tiempo transcurrido sobre un SLA ya vencido) no rompe el
|
|
6192
|
-
* ancho de la barra. `label` es texto libre opcional sobre la barra (ej.
|
|
6193
|
-
* "80%"); sin él, no se renderiza ningún texto, solo la barra.
|
|
6194
|
-
*/
|
|
6195
|
-
class Progress {
|
|
6196
|
-
value = input.required(/* @ts-ignore */
|
|
6197
|
-
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
6198
|
-
tone = input('neutral', /* @ts-ignore */
|
|
6199
|
-
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
6200
|
-
label = input(/* @ts-ignore */
|
|
6201
|
-
...(ngDevMode ? [undefined, { debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6202
|
-
clampedValue = computed(() => Math.min(Math.max(this.value(), 0), 100), /* @ts-ignore */
|
|
6203
|
-
...(ngDevMode ? [{ debugName: "clampedValue" }] : /* istanbul ignore next */ []));
|
|
6204
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6205
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Progress, isStandalone: true, selector: "kf-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] });
|
|
6206
|
-
}
|
|
6207
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Progress, decorators: [{
|
|
6208
|
-
type: Component,
|
|
6209
|
-
args: [{ selector: 'kf-progress', template: "<div class=\"kf-progress\">\n @if (label(); as text) {\n <div class=\"kf-progress__top\">\n <span class=\"kf-progress__label kf-progress--{{ tone() }}\">{{ text }}</span>\n </div>\n }\n <div\n class=\"kf-progress__track\"\n role=\"progressbar\"\n [attr.aria-valuenow]=\"clampedValue()\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <div class=\"kf-progress__fill kf-progress--{{ tone() }}\" [style.width.%]=\"clampedValue()\"></div>\n </div>\n</div>\n", styles: [":host{display:block}.kf-progress{display:flex;flex-direction:column;min-width:0}.kf-progress__top{display:flex;justify-content:flex-end;margin-block-end:3px}.kf-progress__label{font-family:var(--kf-font-mono);font-size:var(--kf-font-size-caption);font-weight:700}.kf-progress__track{height:4px;border-radius:var(--kf-radius-pill);background:var(--kf-color-gray-200);overflow:hidden}.kf-progress__fill{height:100%;border-radius:var(--kf-radius-pill);transition:width .3s ease}@media(prefers-reduced-motion:reduce){.kf-progress__fill{transition:none}}.kf-progress--neutral{color:var(--kf-color-text-muted)}.kf-progress__fill.kf-progress--neutral{background:var(--kf-color-secondary)}.kf-progress--primary{color:var(--kf-color-primary)}.kf-progress__fill.kf-progress--primary{background:var(--kf-color-primary)}.kf-progress--ok{color:var(--kf-color-success-strong)}.kf-progress__fill.kf-progress--ok{background:var(--kf-color-success)}.kf-progress--warn{color:var(--kf-color-warning-strong)}.kf-progress__fill.kf-progress--warn{background:var(--kf-color-warning)}.kf-progress--error{color:var(--kf-color-error-strong)}.kf-progress__fill.kf-progress--error{background:var(--kf-color-error)}\n"] }]
|
|
6210
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
6211
|
-
|
|
6212
7817
|
/**
|
|
6213
7818
|
* Punto que emite ondas concéntricas en bucle, para marcar un dato que
|
|
6214
7819
|
* necesita atención inmediata sin depender de que el operador lo vea por
|
|
@@ -6857,47 +8462,16 @@ class SidebarShell {
|
|
|
6857
8462
|
this.navItemClick.emit(item);
|
|
6858
8463
|
}
|
|
6859
8464
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6860
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarShell, isStandalone: true, selector: "kf-sidebar-shell", inputs: { navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", mobileOpen: "mobileOpenChange", navItemClick: "navItemClick" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1)
|
|
8465
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarShell, isStandalone: true, selector: "kf-sidebar-shell", inputs: { navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, mobileOpen: { classPropertyName: "mobileOpen", publicName: "mobileOpen", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", mobileOpen: "mobileOpenChange", navItemClick: "navItemClick" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1),box-shadow .26s cubic-bezier(.4,0,.2,1);box-shadow:none}.kf-sidebar-shell__rail--mobile-open{transform:translate(0);box-shadow:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw;padding-inline:var(--kf-space-md)}.kf-sidebar-shell__brand{display:grid;grid-template-columns:1fr 2rem;align-items:center}.kf-sidebar-shell__brand-full{grid-column:1;justify-content:flex-start}.kf-sidebar-shell__mobile-close{grid-column:2;display:flex}.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__backdrop{display:block;position:fixed;inset:0;z-index:90;border:none;padding:0;cursor:pointer;background:#0a0e1480}.kf-sidebar-shell__topbar{display:flex;align-items:center;gap:var(--kf-space-md);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-white);border-bottom:1px solid var(--kf-color-border)}.kf-sidebar-shell__menu-toggle{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-ink);cursor:pointer;flex:none}.kf-sidebar-shell__menu-toggle:hover{background:var(--kf-color-surface)}.kf-sidebar-shell__topbar-brand ::ng-deep img{display:block;height:1.25rem}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: SidebarNavItem, selector: "kf-sidebar-nav-item", inputs: ["icon", "label", "href", "active", "collapsed"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
|
|
6861
8466
|
}
|
|
6862
8467
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarShell, decorators: [{
|
|
6863
8468
|
type: Component,
|
|
6864
|
-
args: [{ selector: 'kf-sidebar-shell', imports: [Icon, SidebarNavItem, TooltipDirective], template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1)
|
|
8469
|
+
args: [{ selector: 'kf-sidebar-shell', imports: [Icon, SidebarNavItem, TooltipDirective], template: "@if (mobileOpen()) {\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__backdrop\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n ></button>\n}\n\n<aside\n class=\"kf-sidebar-shell__rail\"\n [class.kf-sidebar-shell__rail--collapsed]=\"itemsCollapsed()\"\n [class.kf-sidebar-shell__rail--mobile-open]=\"mobileOpen()\"\n>\n <div class=\"kf-sidebar-shell__brand\">\n <div class=\"kf-sidebar-shell__brand-full\">\n <ng-content select=\"[kf-sidebar-brand]\" />\n </div>\n <div class=\"kf-sidebar-shell__brand-collapsed\">\n <ng-content select=\"[kf-sidebar-brand-collapsed]\" />\n </div>\n\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__mobile-close\"\n aria-label=\"Cerrar men\u00FA\"\n (click)=\"mobileOpen.set(false)\"\n >\n <kf-icon name=\"x\" size=\"md\" />\n </button>\n </div>\n\n <nav class=\"kf-sidebar-shell__nav\">\n @for (entry of itemsWithGroupHeader(); track entry.item.href) {\n @if (entry.showGroupLabel && !itemsCollapsed()) {\n <div class=\"kf-sidebar-shell__group-label\">{{ entry.item.groupLabel }}</div>\n }\n <kf-sidebar-nav-item\n [icon]=\"entry.item.icon\"\n [label]=\"entry.item.label\"\n [href]=\"entry.item.href\"\n [active]=\"!!entry.item.active\"\n [collapsed]=\"itemsCollapsed()\"\n (click)=\"onNavItemClick($event, entry.item)\"\n />\n }\n </nav>\n\n <div class=\"kf-sidebar-shell__bottom\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__collapse-toggle\"\n [class.kf-sidebar-shell__collapse-toggle--collapsed]=\"itemsCollapsed()\"\n [attr.aria-expanded]=\"!collapsed()\"\n [attr.aria-label]=\"collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA'\"\n [kfTooltip]=\"itemsCollapsed() ? (collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA') : ''\"\n (click)=\"collapsed.set(!collapsed())\"\n >\n <kf-icon [name]=\"collapsed() ? 'chevron-right' : 'chevron-left'\" size=\"sm\" />\n @if (!itemsCollapsed()) {\n <span class=\"kf-sidebar-shell__collapse-toggle-label\">\n {{ collapsed() ? 'Expandir men\u00FA' : 'Colapsar men\u00FA' }}\n </span>\n }\n </button>\n\n <div class=\"kf-sidebar-shell__footer\">\n <ng-content select=\"[kf-sidebar-footer]\" />\n </div>\n </div>\n</aside>\n\n<div class=\"kf-sidebar-shell__topbar\">\n <button\n type=\"button\"\n class=\"kf-sidebar-shell__menu-toggle\"\n aria-label=\"Abrir men\u00FA de navegaci\u00F3n\"\n (click)=\"mobileOpen.set(true)\"\n >\n <kf-icon name=\"menu\" size=\"md\" />\n </button>\n <div class=\"kf-sidebar-shell__topbar-brand\">\n <ng-content select=\"[kf-sidebar-brand-mobile]\" />\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:sticky;top:0;height:100dvh;width:268px;flex:none;padding:var(--kf-space-lg) var(--kf-space-md);background:linear-gradient(190deg,var(--kf-color-sidebar-gradient-from) 0%,var(--kf-color-sidebar-gradient-to) 60%);transition:width .18s ease,padding-inline .18s ease}.kf-sidebar-shell__rail--collapsed{width:56px;padding-inline:var(--kf-space-sm)}.kf-sidebar-shell__brand{display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;min-width:0}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none;justify-content:center;min-width:0}.kf-sidebar-shell__brand-collapsed ::ng-deep img{display:block;height:1.75rem}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:none}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed{display:flex}.kf-sidebar-shell__mobile-close{display:none;align-items:center;justify-content:center;width:2rem;height:2rem;border:none;border-radius:var(--kf-radius-sm);background:#ffffff14;color:#fffc;cursor:pointer}.kf-sidebar-shell__mobile-close:hover{background:#ffffff29;color:var(--kf-color-white)}.kf-sidebar-shell__nav{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);overflow-y:auto}.kf-sidebar-shell__group-label{margin-top:var(--kf-space-sm);padding:0 var(--kf-space-md);font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:#fff6;white-space:nowrap;overflow:hidden}.kf-sidebar-shell__nav>.kf-sidebar-shell__group-label:first-child{margin-top:0}.kf-sidebar-shell__bottom{display:flex;flex-direction:column;margin-top:var(--kf-space-sm);padding-top:var(--kf-space-sm);border-top:1px solid rgba(255,255,255,.12)}.kf-sidebar-shell__footer{margin-top:var(--kf-space-sm)}.kf-sidebar-shell__footer:empty{display:none}.kf-sidebar-shell__collapse-toggle{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-radius:var(--kf-radius-sm);background:#ffffff0f;color:#ffffffb3;font-family:var(--kf-font-body);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .14s,color .14s}.kf-sidebar-shell__collapse-toggle:hover{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-shell__collapse-toggle--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-shell__collapse-toggle-label{flex:1 1 auto;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-sidebar-shell__topbar{display:none}@media(max-width:900px){.kf-sidebar-shell__rail{position:fixed;inset:0 auto 0 0;z-index:100;width:284px;max-width:86vw;transform:translate(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1),box-shadow .26s cubic-bezier(.4,0,.2,1);box-shadow:none}.kf-sidebar-shell__rail--mobile-open{transform:translate(0);box-shadow:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw;padding-inline:var(--kf-space-md)}.kf-sidebar-shell__brand{display:grid;grid-template-columns:1fr 2rem;align-items:center}.kf-sidebar-shell__brand-full{grid-column:1;justify-content:flex-start}.kf-sidebar-shell__mobile-close{grid-column:2;display:flex}.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__backdrop{display:block;position:fixed;inset:0;z-index:90;border:none;padding:0;cursor:pointer;background:#0a0e1480}.kf-sidebar-shell__topbar{display:flex;align-items:center;gap:var(--kf-space-md);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-white);border-bottom:1px solid var(--kf-color-border)}.kf-sidebar-shell__menu-toggle{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-ink);cursor:pointer;flex:none}.kf-sidebar-shell__menu-toggle:hover{background:var(--kf-color-surface)}.kf-sidebar-shell__topbar-brand ::ng-deep img{display:block;height:1.25rem}}\n"] }]
|
|
6865
8470
|
}], propDecorators: { navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], mobileOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileOpen", required: false }] }, { type: i0.Output, args: ["mobileOpenChange"] }], navItemClick: [{ type: i0.Output, args: ["navItemClick"] }], onEscape: [{
|
|
6866
8471
|
type: HostListener,
|
|
6867
8472
|
args: ['document:keydown.escape']
|
|
6868
8473
|
}] } });
|
|
6869
8474
|
|
|
6870
|
-
/**
|
|
6871
|
-
* Card de estadística horizontal: ícono circular grande a la izquierda,
|
|
6872
|
-
* valor + label + nota apilados al centro, chevron opcional al final si es
|
|
6873
|
-
* clickeable. Distinta de `kf-metric-card` (layout vertical, label arriba +
|
|
6874
|
-
* ícono a la derecha) — este es el patrón real del dashboard de OTD ("Bonos
|
|
6875
|
-
* otorgados", "Cortesías otorgadas"), no una variante de la misma card.
|
|
6876
|
-
*
|
|
6877
|
-
* <kf-stat-card icon="gift" tone="purple" value="4" label="Bonos otorgados" note="$15,000.00 en beneficios" (cardClick)="onNavigate('beneficios')" />
|
|
6878
|
-
*/
|
|
6879
|
-
class StatCard {
|
|
6880
|
-
icon = input.required(/* @ts-ignore */
|
|
6881
|
-
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
6882
|
-
tone = input('primary', /* @ts-ignore */
|
|
6883
|
-
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
6884
|
-
value = input.required(/* @ts-ignore */
|
|
6885
|
-
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
6886
|
-
label = input.required(/* @ts-ignore */
|
|
6887
|
-
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6888
|
-
note = input(/* @ts-ignore */
|
|
6889
|
-
...(ngDevMode ? [undefined, { debugName: "note" }] : /* istanbul ignore next */ []));
|
|
6890
|
-
/** Si se escucha `cardClick`, la card se renderiza como botón clickeable con chevron. */
|
|
6891
|
-
clickable = input(false, { ...(ngDevMode ? { debugName: "clickable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6892
|
-
cardClick = output();
|
|
6893
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6894
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: StatCard, isStandalone: true, selector: "kf-stat-card", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "@if (clickable()) {\n <button type=\"button\" class=\"kf-stat-card kf-stat-card--clickable\" (click)=\"cardClick.emit()\">\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n </span>\n</ng-template>\n", styles: [":host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
6895
|
-
}
|
|
6896
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatCard, decorators: [{
|
|
6897
|
-
type: Component,
|
|
6898
|
-
args: [{ selector: 'kf-stat-card', imports: [Icon, NgTemplateOutlet], template: "@if (clickable()) {\n <button type=\"button\" class=\"kf-stat-card kf-stat-card--clickable\" (click)=\"cardClick.emit()\">\n <ng-container *ngTemplateOutlet=\"content\" />\n <kf-icon class=\"kf-stat-card__chevron\" name=\"chevron-right\" size=\"sm\" />\n </button>\n} @else {\n <div class=\"kf-stat-card\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n}\n\n<ng-template #content>\n <span class=\"kf-stat-card__icon\" [class]=\"'kf-stat-card__icon--' + tone()\">\n <kf-icon [name]=\"icon()\" size=\"md\" />\n </span>\n <span class=\"kf-stat-card__body\">\n <span class=\"kf-stat-card__value\">{{ value() }}</span>\n <span class=\"kf-stat-card__label\">{{ label() }}</span>\n @if (note()) {\n <span class=\"kf-stat-card__note\">{{ note() }}</span>\n }\n </span>\n</ng-template>\n", styles: [":host{display:block}.kf-stat-card{display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-lg);border:1px solid var(--kf-color-border-strong);border-radius:var(--kf-radius-md);background:var(--kf-color-white);box-shadow:var(--kf-shadow-sm);font:inherit;text-align:left}.kf-stat-card--clickable{cursor:pointer;transition:box-shadow .14s,transform .14s}.kf-stat-card--clickable:hover{box-shadow:var(--kf-shadow-md);transform:translateY(-2px)}.kf-stat-card--clickable:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-stat-card__icon{display:grid;place-items:center;width:50px;height:50px;flex:none;border-radius:var(--kf-radius-md)}.kf-stat-card__icon--primary{background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-stat-card__icon--success{background:var(--kf-color-success-soft);color:var(--kf-color-success-strong)}.kf-stat-card__icon--error{background:var(--kf-color-error-soft);color:var(--kf-color-error)}.kf-stat-card__icon--purple{background:var(--kf-color-purple-soft);color:var(--kf-color-purple)}.kf-stat-card__body{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-stat-card__value{font-family:var(--kf-font-title);font-weight:700;font-size:var(--kf-font-size-title-md);color:var(--kf-color-ink)}.kf-stat-card__label{font-size:.875rem;font-weight:700;color:var(--kf-color-ink)}.kf-stat-card__note{font-size:.78rem;color:var(--kf-color-text-muted)}.kf-stat-card__chevron{margin-left:auto;flex:none;color:var(--kf-color-gray-400)}\n"] }]
|
|
6899
|
-
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], clickable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickable", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
|
|
6900
|
-
|
|
6901
8475
|
/**
|
|
6902
8476
|
* Indicador numérico compacto: label + valor grande con ícono, para grillas
|
|
6903
8477
|
* densas de KPIs. Con `cta` en vez de `value` se convierte en un bloque de
|
|
@@ -7451,11 +9025,28 @@ const TOAST_ICON = {
|
|
|
7451
9025
|
loading: 'bell',
|
|
7452
9026
|
};
|
|
7453
9027
|
const DEFAULT_DURATION_MS = 2800;
|
|
9028
|
+
/**
|
|
9029
|
+
* Auto-dismiss time per tone when the caller doesn't pass `duration`. Errors and
|
|
9030
|
+
* warnings stay longer than confirmations: they carry something to read and
|
|
9031
|
+
* act on, while a success only has to be noticed. `success`/`info` keep the
|
|
9032
|
+
* historical 2800ms. `loading` never auto-dismisses.
|
|
9033
|
+
*/
|
|
9034
|
+
const TOAST_DEFAULT_DURATION_MS = {
|
|
9035
|
+
success: DEFAULT_DURATION_MS,
|
|
9036
|
+
info: DEFAULT_DURATION_MS,
|
|
9037
|
+
warning: 5000,
|
|
9038
|
+
error: 8000,
|
|
9039
|
+
};
|
|
7454
9040
|
const EXIT_ANIMATION_MS = 200;
|
|
7455
9041
|
/**
|
|
7456
9042
|
* Shows one toast at a time — a new `show()` call replaces whatever is
|
|
7457
9043
|
* currently up (this mirrors a global status line, not a notification stack).
|
|
7458
9044
|
*
|
|
9045
|
+
* Duration by tone (when `duration` isn't passed): `success`/`info` 2800ms,
|
|
9046
|
+
* `warning` 5000ms, `error` 8000ms (`TOAST_DEFAULT_DURATION_MS`); an explicit
|
|
9047
|
+
* `duration` always wins. The countdown pauses while the pointer or focus is
|
|
9048
|
+
* on the toast. Error toasts also get a larger, always-opaque close button.
|
|
9049
|
+
*
|
|
7459
9050
|
* Angular has no global function that can mount UI on its own, so the
|
|
7460
9051
|
* imperative `showToast(...)` of the reference design system becomes a
|
|
7461
9052
|
* root-provided service holding signal state: `ToastService.show(...)` sets
|
|
@@ -7499,7 +9090,8 @@ class ToastService {
|
|
|
7499
9090
|
show(message, options = {}) {
|
|
7500
9091
|
const id = this.nextId++;
|
|
7501
9092
|
const tone = options.tone ?? 'info';
|
|
7502
|
-
const duration = options.duration ??
|
|
9093
|
+
const duration = options.duration ??
|
|
9094
|
+
(tone === 'loading' ? DEFAULT_DURATION_MS : TOAST_DEFAULT_DURATION_MS[tone]);
|
|
7503
9095
|
clearTimeout(this.dismissTimer);
|
|
7504
9096
|
clearTimeout(this.removeTimer);
|
|
7505
9097
|
this.dismissTimer = undefined;
|
|
@@ -7623,11 +9215,11 @@ class ToastHost {
|
|
|
7623
9215
|
this.dismiss(t.id);
|
|
7624
9216
|
}
|
|
7625
9217
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ToastHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7626
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ToastHost, isStandalone: true, selector: "kf-toast-host", ngImport: i0, template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button
|
|
9218
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ToastHost, isStandalone: true, selector: "kf-toast-host", ngImport: i0, template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button\n type=\"button\"\n class=\"kf-toast__dismiss\"\n [class.kf-toast__dismiss--prominent]=\"t.tone === 'error'\"\n [attr.aria-label]=\"t.tone === 'error' ? 'Cerrar mensaje de error' : 'Cerrar'\"\n (click)=\"dismiss(t.id)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n <span class=\"kf-toast__bar\" [style.--kf-toast-dur]=\"t.duration + 'ms'\"></span>\n </div>\n}\n", styles: ["@keyframes kf-toast-in{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes kf-toast-bar{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes kf-toast-indeterminate{0%{left:-40%;width:40%}50%{left:30%;width:55%}to{left:100%;width:40%}}.kf-toast{--kf-toast-accent: var(--kf-color-info);position:fixed;inset-block-end:var(--kf-space-lg);inset-inline-end:var(--kf-space-lg);z-index:1100;display:flex;align-items:center;gap:var(--kf-space-sm);max-inline-size:360px;padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-md);border-left:4px solid var(--kf-toast-accent);background:var(--kf-color-ink);color:var(--kf-color-white);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;line-height:1.4;overflow:hidden;animation:kf-toast-in .25s ease forwards;transition:transform .2s ease,opacity .2s ease}.kf-toast--leaving{transform:translateY(20px);opacity:0}.kf-toast--success{--kf-toast-accent: var(--kf-color-success)}.kf-toast--error{--kf-toast-accent: var(--kf-color-error)}.kf-toast--warning{--kf-toast-accent: var(--kf-color-warning)}.kf-toast--info,.kf-toast--loading{--kf-toast-accent: var(--kf-color-info)}.kf-toast__icon{flex:none;color:var(--kf-toast-accent)}.kf-toast__message{flex:1}.kf-toast__action{flex:none;padding:2px var(--kf-space-xs);border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-toast-accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.kf-toast__action:hover{opacity:.85}.kf-toast__dismiss{flex:none;display:grid;place-items:center;inline-size:24px;block-size:24px;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer;opacity:.6}.kf-toast__dismiss:hover{opacity:1}.kf-toast__dismiss:focus-visible,.kf-toast__action:focus-visible{outline:2px solid var(--kf-color-white);outline-offset:1px;opacity:1}.kf-toast__dismiss--prominent{inline-size:32px;block-size:32px;opacity:1}.kf-toast__bar{position:absolute;inset-block-end:0;left:0;block-size:3px;inline-size:100%;background:var(--kf-toast-accent);opacity:.7;transform-origin:left;animation:kf-toast-bar var(--kf-toast-dur, 2.8s) linear forwards}.kf-toast--loading .kf-toast__bar{left:-40%;inline-size:40%;opacity:.6;animation:kf-toast-indeterminate 1.4s ease-in-out infinite}.kf-toast:hover .kf-toast__bar,.kf-toast:focus-within .kf-toast__bar{animation-play-state:paused}@media(prefers-reduced-motion:reduce){.kf-toast,.kf-toast__bar,.kf-toast--loading .kf-toast__bar{animation:none}.kf-toast{transition:none}}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7627
9219
|
}
|
|
7628
9220
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ToastHost, decorators: [{
|
|
7629
9221
|
type: Component,
|
|
7630
|
-
args: [{ selector: 'kf-toast-host', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Icon], template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button
|
|
9222
|
+
args: [{ selector: 'kf-toast-host', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Icon], template: "@for (t of toastList(); track t.id) {\n <div\n class=\"kf-toast kf-toast--{{ t.tone }}\"\n [class.kf-toast--leaving]=\"t.leaving\"\n role=\"status\"\n [attr.aria-live]=\"t.tone === 'error' ? 'assertive' : 'polite'\"\n (mouseenter)=\"pause(t, 'hover')\"\n (mouseleave)=\"resume(t, 'hover')\"\n (focusin)=\"pause(t, 'focus')\"\n (focusout)=\"resume(t, 'focus')\"\n >\n <kf-icon class=\"kf-toast__icon\" [name]=\"toastIcon(t.tone)\" size=\"sm\" />\n <span class=\"kf-toast__message\">{{ t.message }}</span>\n @if (t.action; as action) {\n <button type=\"button\" class=\"kf-toast__action\" (click)=\"runAction(t)\">\n {{ action.label }}\n </button>\n }\n <button\n type=\"button\"\n class=\"kf-toast__dismiss\"\n [class.kf-toast__dismiss--prominent]=\"t.tone === 'error'\"\n [attr.aria-label]=\"t.tone === 'error' ? 'Cerrar mensaje de error' : 'Cerrar'\"\n (click)=\"dismiss(t.id)\"\n >\n <kf-icon name=\"x\" size=\"sm\" />\n </button>\n <span class=\"kf-toast__bar\" [style.--kf-toast-dur]=\"t.duration + 'ms'\"></span>\n </div>\n}\n", styles: ["@keyframes kf-toast-in{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes kf-toast-bar{0%{transform:scaleX(1)}to{transform:scaleX(0)}}@keyframes kf-toast-indeterminate{0%{left:-40%;width:40%}50%{left:30%;width:55%}to{left:100%;width:40%}}.kf-toast{--kf-toast-accent: var(--kf-color-info);position:fixed;inset-block-end:var(--kf-space-lg);inset-inline-end:var(--kf-space-lg);z-index:1100;display:flex;align-items:center;gap:var(--kf-space-sm);max-inline-size:360px;padding:var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-md);border-left:4px solid var(--kf-toast-accent);background:var(--kf-color-ink);color:var(--kf-color-white);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;line-height:1.4;overflow:hidden;animation:kf-toast-in .25s ease forwards;transition:transform .2s ease,opacity .2s ease}.kf-toast--leaving{transform:translateY(20px);opacity:0}.kf-toast--success{--kf-toast-accent: var(--kf-color-success)}.kf-toast--error{--kf-toast-accent: var(--kf-color-error)}.kf-toast--warning{--kf-toast-accent: var(--kf-color-warning)}.kf-toast--info,.kf-toast--loading{--kf-toast-accent: var(--kf-color-info)}.kf-toast__icon{flex:none;color:var(--kf-toast-accent)}.kf-toast__message{flex:1}.kf-toast__action{flex:none;padding:2px var(--kf-space-xs);border:none;border-radius:var(--kf-radius-xs);background:transparent;color:var(--kf-toast-accent);font:inherit;font-weight:700;text-decoration:underline;text-underline-offset:2px;cursor:pointer}.kf-toast__action:hover{opacity:.85}.kf-toast__dismiss{flex:none;display:grid;place-items:center;inline-size:24px;block-size:24px;padding:0;border:none;border-radius:var(--kf-radius-xs);background:transparent;color:inherit;font:inherit;line-height:1;cursor:pointer;opacity:.6}.kf-toast__dismiss:hover{opacity:1}.kf-toast__dismiss:focus-visible,.kf-toast__action:focus-visible{outline:2px solid var(--kf-color-white);outline-offset:1px;opacity:1}.kf-toast__dismiss--prominent{inline-size:32px;block-size:32px;opacity:1}.kf-toast__bar{position:absolute;inset-block-end:0;left:0;block-size:3px;inline-size:100%;background:var(--kf-toast-accent);opacity:.7;transform-origin:left;animation:kf-toast-bar var(--kf-toast-dur, 2.8s) linear forwards}.kf-toast--loading .kf-toast__bar{left:-40%;inline-size:40%;opacity:.6;animation:kf-toast-indeterminate 1.4s ease-in-out infinite}.kf-toast:hover .kf-toast__bar,.kf-toast:focus-within .kf-toast__bar{animation-play-state:paused}@media(prefers-reduced-motion:reduce){.kf-toast,.kf-toast__bar,.kf-toast--loading .kf-toast__bar{animation:none}.kf-toast{transition:none}}\n"] }]
|
|
7631
9223
|
}] });
|
|
7632
9224
|
|
|
7633
9225
|
/*
|
|
@@ -7651,5 +9243,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
7651
9243
|
* Generated bundle index. Do not edit.
|
|
7652
9244
|
*/
|
|
7653
9245
|
|
|
7654
|
-
export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentViewer, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, LIST_TOOLBAR_HYSTERESIS, Link, ListToolbar, MetricCard, Modal, MultiSelect, NoteBlock, OptionList, PageHeader, Pagination, Panel, PortalHeader, Progress, PulseDot, RowActions, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, Spinner, StatCard, StatTile, StatusPill, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal, resolveListToolbarMode };
|
|
9246
|
+
export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentStateLoading, ContentViewer, DATA_TABLE_MAX_SKELETON_ROWS, DEFAULT_MILESTONE_TIMELINE_LABELS, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, FileInput, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, LIST_TOOLBAR_HYSTERESIS, Link, ListToolbar, MetricCard, MilestoneTimeline, Modal, MultiSelect, NoteBlock, OptionList, PROGRESS_STATUS, PROGRESS_STATUS_LABEL, PageHeader, Pagination, Panel, PopoverDirective, PortalHeader, Progress, PulseDot, RowActions, SKELETON_PRESET_MAX_COUNT, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, SkeletonPreset, Spinner, StatCard, StatTile, StatusMark, StatusNode, StatusPill, TOAST_DEFAULT_DURATION_MS, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal, formatFileSize, resolveListToolbarMode };
|
|
7655
9247
|
//# sourceMappingURL=kaufmann-lab-ui-core.mjs.map
|