@kaufmann-lab/ui 0.1.8 → 0.1.11
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.
- package/fesm2022/kaufmann-lab-ui.mjs +2007 -415
- package/fesm2022/kaufmann-lab-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/src/tokens/base.css +22 -0
- package/types/kaufmann-lab-ui.d.ts +697 -131
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, inject, computed, effect, Component, booleanAttribute, model, output, numberAttribute, contentChildren, signal, DestroyRef, viewChild, forwardRef, ViewContainerRef, HostListener, TemplateRef, contentChild, ElementRef, ChangeDetectionStrategy,
|
|
2
|
+
import { input, inject, computed, effect, Component, booleanAttribute, model, output, numberAttribute, contentChildren, signal, DestroyRef, viewChild, forwardRef, ViewContainerRef, afterRenderEffect, HostListener, TemplateRef, contentChild, ElementRef, Directive, ChangeDetectionStrategy, Injector, Input as Input$1, afterNextRender, Injectable } from '@angular/core';
|
|
3
3
|
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
5
5
|
import { Overlay } from '@angular/cdk/overlay';
|
|
6
|
-
import { TemplatePortal } from '@angular/cdk/portal';
|
|
6
|
+
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
|
|
7
|
+
import { FocusTrapFactory } from '@angular/cdk/a11y';
|
|
8
|
+
import { Subscription } from 'rxjs';
|
|
7
9
|
|
|
8
10
|
/** Símbolo de trazo "activity" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
|
|
9
11
|
const activity = {
|
|
@@ -328,6 +330,15 @@ const history = {
|
|
|
328
330
|
`,
|
|
329
331
|
};
|
|
330
332
|
|
|
333
|
+
/** Símbolo de trazo "home" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
|
|
334
|
+
const home = {
|
|
335
|
+
viewBox: '0 0 24 24',
|
|
336
|
+
markup: `
|
|
337
|
+
<path d="M3 10.5 12 3l9 7.5" />
|
|
338
|
+
<path d="M5 9.5V20a1 1 0 0 0 1 1h4v-6h4v6h4a1 1 0 0 0 1-1V9.5" />
|
|
339
|
+
`,
|
|
340
|
+
};
|
|
341
|
+
|
|
331
342
|
/** Símbolo de trazo "inbox" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
|
|
332
343
|
const inbox = {
|
|
333
344
|
viewBox: '0 0 24 24',
|
|
@@ -360,6 +371,16 @@ const list = {
|
|
|
360
371
|
`,
|
|
361
372
|
};
|
|
362
373
|
|
|
374
|
+
/** Símbolo de trazo "log-out" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
|
|
375
|
+
const logOut = {
|
|
376
|
+
viewBox: '0 0 24 24',
|
|
377
|
+
markup: `
|
|
378
|
+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
379
|
+
<path d="M16 17l5-5-5-5" />
|
|
380
|
+
<path d="M21 12H9" />
|
|
381
|
+
`,
|
|
382
|
+
};
|
|
383
|
+
|
|
363
384
|
/** Símbolo de trazo "map" (grilla 24×24) — ver `icons/index.ts` para el registro completo. */
|
|
364
385
|
const map = {
|
|
365
386
|
viewBox: '0 0 24 24',
|
|
@@ -636,9 +657,11 @@ const ICON_DEFINITIONS = {
|
|
|
636
657
|
gift: gift,
|
|
637
658
|
grid: grid,
|
|
638
659
|
history: history,
|
|
660
|
+
home: home,
|
|
639
661
|
inbox: inbox,
|
|
640
662
|
layers: layers,
|
|
641
663
|
list: list,
|
|
664
|
+
'log-out': logOut,
|
|
642
665
|
map: map,
|
|
643
666
|
'map-pin': mapPin,
|
|
644
667
|
menu: menu,
|
|
@@ -713,7 +736,7 @@ function buildIconSprite() {
|
|
|
713
736
|
* nunca se inyecta. Comprobar el propio DOM es la fuente de verdad real. */
|
|
714
737
|
const SPRITE_ROOT_ID = 'kf-icon-sprite-root';
|
|
715
738
|
/**
|
|
716
|
-
* Ícono de trazo del design system —
|
|
739
|
+
* Ícono de trazo del design system — 61 glyphs (59 de la página 5 + `eye-off` y `log-out`), grilla 24×24,
|
|
717
740
|
* grosor de trazo 1.9px. Referencia un sprite SVG embebido (ver
|
|
718
741
|
* `icon-sprite.ts`) vía `<use>`, así que no hay peso de red adicional por
|
|
719
742
|
* cada instancia: el sprite se inyecta al DOM una única vez, la primera
|
|
@@ -1138,6 +1161,139 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
1138
1161
|
`, 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__icon,.kf-alert__close svg{flex:none;width:1.15em;height:1.15em;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}.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)}\n"] }]
|
|
1139
1162
|
}], 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"] }] } });
|
|
1140
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* Layout a pantalla completa para cualquier pantalla de autenticación
|
|
1166
|
+
* (login, recuperar contraseña, confirmación de envío) — split marca a la
|
|
1167
|
+
* izquierda / contenido libre a la derecha en desktop, formulario a
|
|
1168
|
+
* pantalla completa en mobile (el panel de marca completo se oculta, no se
|
|
1169
|
+
* apila arriba: en una pantalla angosta el usuario vino a loguearse, no a
|
|
1170
|
+
* leer la tagline). Un logo compacto (o wordmark de respaldo) sí se
|
|
1171
|
+
* mantiene visible arriba del formulario en mobile — el usuario nunca
|
|
1172
|
+
* pierde de vista qué portal está usando, mismo criterio que ya sigue el
|
|
1173
|
+
* resto del ecosistema (ver la topbar mobile de `css/otd.css`, que muestra
|
|
1174
|
+
* el logo aunque el sidebar completo se oculte).
|
|
1175
|
+
*
|
|
1176
|
+
* Este componente resuelve el "cascarón" — nunca el formulario en sí. Cada
|
|
1177
|
+
* portal arma su propio `<form>` con los componentes de la librería
|
|
1178
|
+
* (`kf-input`, `kf-checkbox`, `kf-button`, `kf-alert`) y lo proyecta en el
|
|
1179
|
+
* slot por defecto. Es la misma separación de responsabilidad que ya usa
|
|
1180
|
+
* `kf-card`: un componente da la estructura visual, el consumidor decide
|
|
1181
|
+
* qué va adentro.
|
|
1182
|
+
*
|
|
1183
|
+
* Antes de este componente, cada portal (LATAM primero, ahora OTD) hubiera
|
|
1184
|
+
* tenido que reimplementar este mismo split desde cero — la librería
|
|
1185
|
+
* había pospuesto esta abstracción a propósito hasta tener un segundo
|
|
1186
|
+
* consumidor real que confirmara qué parte es realmente compartida (el
|
|
1187
|
+
* cascarón) y qué parte es de cada portal (el formulario). OTD es ese
|
|
1188
|
+
* segundo consumidor.
|
|
1189
|
+
*
|
|
1190
|
+
* Ningún input tiene valor por defecto propio de ningún portal — `brand`
|
|
1191
|
+
* es obligatorio (`input.required`) y el resto de props de marca son
|
|
1192
|
+
* opcionales sin fallback hardcodeado, precisamente para que cada
|
|
1193
|
+
* consumidor (LATAM, OTD, Star, o el ERP de Asistencia en Ruta con sus
|
|
1194
|
+
* propios temas) traiga su propio logo/tagline sin que la librería asuma
|
|
1195
|
+
* cuál.
|
|
1196
|
+
*
|
|
1197
|
+
* <kf-auth-layout
|
|
1198
|
+
* brand="Mi Portal"
|
|
1199
|
+
* brandLogoSrc="images/mi-portal-logo-white.png"
|
|
1200
|
+
* brandLogoMobileSrc="images/mi-portal-logo-black.png"
|
|
1201
|
+
* brandTagline="Frase corta de marca, propia de cada portal."
|
|
1202
|
+
* >
|
|
1203
|
+
* <form (ngSubmit)="onSubmit()">
|
|
1204
|
+
* <kf-input label="Correo" type="email" />
|
|
1205
|
+
* <kf-input label="Contraseña" type="password" toggleable />
|
|
1206
|
+
* <kf-button type="submit">Ingresar</kf-button>
|
|
1207
|
+
* </form>
|
|
1208
|
+
* </kf-auth-layout>
|
|
1209
|
+
*/
|
|
1210
|
+
class AuthLayout {
|
|
1211
|
+
/** Nombre de marca/portal, usado como wordmark de respaldo si no hay ningún logo, y como `alt` de ambos logos. */
|
|
1212
|
+
brand = input.required(/* @ts-ignore */
|
|
1213
|
+
...(ngDevMode ? [{ debugName: "brand" }] : /* istanbul ignore next */ []));
|
|
1214
|
+
/** Logo en blanco para el panel oscuro de desktop — si no se pasa, se muestra `brand()` como wordmark tipográfico. */
|
|
1215
|
+
brandLogoSrc = input(/* @ts-ignore */
|
|
1216
|
+
...(ngDevMode ? [undefined, { debugName: "brandLogoSrc" }] : /* istanbul ignore next */ []));
|
|
1217
|
+
/** Frase corta bajo el logo — opcional, cada portal decide su propio texto de marca. */
|
|
1218
|
+
brandTagline = input(/* @ts-ignore */
|
|
1219
|
+
...(ngDevMode ? [undefined, { debugName: "brandTagline" }] : /* istanbul ignore next */ []));
|
|
1220
|
+
/**
|
|
1221
|
+
* Logo compacto para mobile, sobre el fondo claro del panel de contenido
|
|
1222
|
+
* — normalmente la variante oscura/negra del mismo logo, ya que
|
|
1223
|
+
* `brandLogoSrc` suele ser la variante blanca pensada para el gradiente
|
|
1224
|
+
* oscuro. Si no se pasa, reutiliza `brandLogoSrc`; si tampoco hay logo en
|
|
1225
|
+
* absoluto, cae al wordmark tipográfico igual que en desktop.
|
|
1226
|
+
*/
|
|
1227
|
+
brandLogoMobileSrc = input(/* @ts-ignore */
|
|
1228
|
+
...(ngDevMode ? [undefined, { debugName: "brandLogoMobileSrc" }] : /* istanbul ignore next */ []));
|
|
1229
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AuthLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1230
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: AuthLayout, isStandalone: true, selector: "kf-auth-layout", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: true, transformFunction: null }, brandLogoSrc: { classPropertyName: "brandLogoSrc", publicName: "brandLogoSrc", isSignal: true, isRequired: false, transformFunction: null }, brandTagline: { classPropertyName: "brandTagline", publicName: "brandTagline", isSignal: true, isRequired: false, transformFunction: null }, brandLogoMobileSrc: { classPropertyName: "brandLogoMobileSrc", publicName: "brandLogoMobileSrc", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1231
|
+
<aside class="kf-auth-layout__brand">
|
|
1232
|
+
@if (brandLogoSrc()) {
|
|
1233
|
+
<img class="kf-auth-layout__logo" [src]="brandLogoSrc()" [alt]="brand()" />
|
|
1234
|
+
} @else {
|
|
1235
|
+
<span class="kf-auth-layout__wordmark">{{ brand() }}</span>
|
|
1236
|
+
}
|
|
1237
|
+
@if (brandTagline()) {
|
|
1238
|
+
<p class="kf-auth-layout__tagline">{{ brandTagline() }}</p>
|
|
1239
|
+
}
|
|
1240
|
+
</aside>
|
|
1241
|
+
|
|
1242
|
+
<main class="kf-auth-layout__content">
|
|
1243
|
+
<div class="kf-auth-layout__panel">
|
|
1244
|
+
<div class="kf-auth-layout__mobile-brand">
|
|
1245
|
+
@if (brandLogoMobileSrc() || brandLogoSrc()) {
|
|
1246
|
+
<img
|
|
1247
|
+
class="kf-auth-layout__logo kf-auth-layout__logo--mobile"
|
|
1248
|
+
[src]="brandLogoMobileSrc() || brandLogoSrc()"
|
|
1249
|
+
[alt]="brand()"
|
|
1250
|
+
/>
|
|
1251
|
+
} @else {
|
|
1252
|
+
<span class="kf-auth-layout__wordmark kf-auth-layout__wordmark--mobile">{{
|
|
1253
|
+
brand()
|
|
1254
|
+
}}</span>
|
|
1255
|
+
}
|
|
1256
|
+
</div>
|
|
1257
|
+
<ng-content />
|
|
1258
|
+
</div>
|
|
1259
|
+
</main>
|
|
1260
|
+
`, isInline: true, styles: [":host{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);min-height:100dvh;background:var(--kf-color-surface)}.kf-auth-layout__brand{display:flex;flex-direction:column;justify-content:center;gap:var(--kf-space-md);padding:var(--kf-space-xl);background:linear-gradient(190deg,var(--kf-color-primary-active) 0%,var(--kf-color-tertiary-deep) 100%);color:var(--kf-color-white)}.kf-auth-layout__logo{height:1.75rem;width:fit-content}.kf-auth-layout__wordmark{font-family:var(--kf-font-title);font-weight:var(--kf-font-weight-title);font-size:var(--kf-font-size-title-lg);letter-spacing:-.01em}.kf-auth-layout__tagline{max-width:34ch;margin:0;font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);color:#ffffffb8}.kf-auth-layout__content{display:flex;align-items:center;justify-content:center;padding:var(--kf-space-xl)}.kf-auth-layout__panel{width:100%;max-width:360px;display:flex;flex-direction:column;gap:var(--kf-space-lg)}.kf-auth-layout__mobile-brand{display:none}.kf-auth-layout__logo--mobile{height:1.5rem}.kf-auth-layout__wordmark--mobile{color:var(--kf-color-ink)}@media(max-width:900px){:host{grid-template-columns:1fr}.kf-auth-layout__brand{display:none}.kf-auth-layout__content{padding:var(--kf-space-lg)}.kf-auth-layout__mobile-brand{display:block;margin-bottom:var(--kf-space-md)}}@media(max-width:480px){.kf-auth-layout__panel{max-width:none}}\n"] });
|
|
1261
|
+
}
|
|
1262
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AuthLayout, decorators: [{
|
|
1263
|
+
type: Component,
|
|
1264
|
+
args: [{ selector: 'kf-auth-layout', template: `
|
|
1265
|
+
<aside class="kf-auth-layout__brand">
|
|
1266
|
+
@if (brandLogoSrc()) {
|
|
1267
|
+
<img class="kf-auth-layout__logo" [src]="brandLogoSrc()" [alt]="brand()" />
|
|
1268
|
+
} @else {
|
|
1269
|
+
<span class="kf-auth-layout__wordmark">{{ brand() }}</span>
|
|
1270
|
+
}
|
|
1271
|
+
@if (brandTagline()) {
|
|
1272
|
+
<p class="kf-auth-layout__tagline">{{ brandTagline() }}</p>
|
|
1273
|
+
}
|
|
1274
|
+
</aside>
|
|
1275
|
+
|
|
1276
|
+
<main class="kf-auth-layout__content">
|
|
1277
|
+
<div class="kf-auth-layout__panel">
|
|
1278
|
+
<div class="kf-auth-layout__mobile-brand">
|
|
1279
|
+
@if (brandLogoMobileSrc() || brandLogoSrc()) {
|
|
1280
|
+
<img
|
|
1281
|
+
class="kf-auth-layout__logo kf-auth-layout__logo--mobile"
|
|
1282
|
+
[src]="brandLogoMobileSrc() || brandLogoSrc()"
|
|
1283
|
+
[alt]="brand()"
|
|
1284
|
+
/>
|
|
1285
|
+
} @else {
|
|
1286
|
+
<span class="kf-auth-layout__wordmark kf-auth-layout__wordmark--mobile">{{
|
|
1287
|
+
brand()
|
|
1288
|
+
}}</span>
|
|
1289
|
+
}
|
|
1290
|
+
</div>
|
|
1291
|
+
<ng-content />
|
|
1292
|
+
</div>
|
|
1293
|
+
</main>
|
|
1294
|
+
`, styles: [":host{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);min-height:100dvh;background:var(--kf-color-surface)}.kf-auth-layout__brand{display:flex;flex-direction:column;justify-content:center;gap:var(--kf-space-md);padding:var(--kf-space-xl);background:linear-gradient(190deg,var(--kf-color-primary-active) 0%,var(--kf-color-tertiary-deep) 100%);color:var(--kf-color-white)}.kf-auth-layout__logo{height:1.75rem;width:fit-content}.kf-auth-layout__wordmark{font-family:var(--kf-font-title);font-weight:var(--kf-font-weight-title);font-size:var(--kf-font-size-title-lg);letter-spacing:-.01em}.kf-auth-layout__tagline{max-width:34ch;margin:0;font-family:var(--kf-font-body);font-size:var(--kf-font-size-body);color:#ffffffb8}.kf-auth-layout__content{display:flex;align-items:center;justify-content:center;padding:var(--kf-space-xl)}.kf-auth-layout__panel{width:100%;max-width:360px;display:flex;flex-direction:column;gap:var(--kf-space-lg)}.kf-auth-layout__mobile-brand{display:none}.kf-auth-layout__logo--mobile{height:1.5rem}.kf-auth-layout__wordmark--mobile{color:var(--kf-color-ink)}@media(max-width:900px){:host{grid-template-columns:1fr}.kf-auth-layout__brand{display:none}.kf-auth-layout__content{padding:var(--kf-space-lg)}.kf-auth-layout__mobile-brand{display:block;margin-bottom:var(--kf-space-md)}}@media(max-width:480px){.kf-auth-layout__panel{max-width:none}}\n"] }]
|
|
1295
|
+
}], propDecorators: { brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: true }] }], brandLogoSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLogoSrc", required: false }] }], brandTagline: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandTagline", required: false }] }], brandLogoMobileSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLogoMobileSrc", required: false }] }] } });
|
|
1296
|
+
|
|
1141
1297
|
/**
|
|
1142
1298
|
* Avatar de usuario: muestra una foto (`src`) o, si no hay foto, las
|
|
1143
1299
|
* iniciales (`initials`) sobre un fondo con gradiente de marca. `round`
|
|
@@ -1423,11 +1579,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
1423
1579
|
*/
|
|
1424
1580
|
class CarouselSlide {
|
|
1425
1581
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CarouselSlide, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1426
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: CarouselSlide, isStandalone: true, selector: "kf-carousel-slide", ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;flex:0 0 100%;width:100%;height:100
|
|
1582
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: CarouselSlide, isStandalone: true, selector: "kf-carousel-slide", ngImport: i0, template: `<ng-content />`, isInline: true, styles: [":host{display:block;flex:0 0 100%;width:100%;height:100%;min-height:0}\n"] });
|
|
1427
1583
|
}
|
|
1428
1584
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CarouselSlide, decorators: [{
|
|
1429
1585
|
type: Component,
|
|
1430
|
-
args: [{ selector: 'kf-carousel-slide', template: `<ng-content />`, styles: [":host{display:block;flex:0 0 100%;width:100%;height:100
|
|
1586
|
+
args: [{ selector: 'kf-carousel-slide', template: `<ng-content />`, styles: [":host{display:block;flex:0 0 100%;width:100%;height:100%;min-height:0}\n"] }]
|
|
1431
1587
|
}] });
|
|
1432
1588
|
/**
|
|
1433
1589
|
* Carrusel de slides con flechas, dots y autoplay opcional.
|
|
@@ -1858,6 +2014,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
1858
2014
|
`, styles: [":host{display:contents}.kf-field-label{font-family:var(--kf-font-title);font-size:.75rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-field-label__required{color:var(--kf-color-error);margin-inline-start:2px}\n"] }]
|
|
1859
2015
|
}], propDecorators: { for: [{ type: i0.Input, args: [{ isSignal: true, alias: "for", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }] } });
|
|
1860
2016
|
|
|
2017
|
+
/**
|
|
2018
|
+
* Normaliza texto para comparación de búsqueda insensible a mayúsculas y
|
|
2019
|
+
* diacríticos — así "cor" encuentre "Córdoba" sin necesidad de tildes.
|
|
2020
|
+
*
|
|
2021
|
+
* Compartida entre `Select` y `MultiSelect` (los dos componentes con
|
|
2022
|
+
* buscador interno auto-activado por `searchThreshold`): antes vivía
|
|
2023
|
+
* duplicada letra por letra en ambos archivos.
|
|
2024
|
+
*/
|
|
2025
|
+
function normalizeForSearch(text) {
|
|
2026
|
+
return text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1861
2029
|
let nextId$1 = 0;
|
|
1862
2030
|
/**
|
|
1863
2031
|
* Dropdown de selección simple con panel propio — NUNCA usa el `<select>`
|
|
@@ -1893,21 +2061,48 @@ class Select {
|
|
|
1893
2061
|
placeholder = input('Seleccionar', /* @ts-ignore */
|
|
1894
2062
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
1895
2063
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
2064
|
+
/**
|
|
2065
|
+
* Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
|
|
2066
|
+
* se activa solo cuando `options().length` supera `searchThreshold()`. Si
|
|
2067
|
+
* el consumidor define `true`/`false` explícitamente, ese valor manda por
|
|
2068
|
+
* sobre el umbral automático.
|
|
2069
|
+
*/
|
|
2070
|
+
searchable = input(undefined, /* @ts-ignore */
|
|
2071
|
+
...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
|
|
2072
|
+
searchThreshold = input(7, /* @ts-ignore */
|
|
2073
|
+
...(ngDevMode ? [{ debugName: "searchThreshold" }] : /* istanbul ignore next */ []));
|
|
1896
2074
|
valueChange = output();
|
|
1897
2075
|
instanceId = `kf-select-${nextId$1++}`;
|
|
1898
2076
|
triggerId = `${this.instanceId}-trigger`;
|
|
1899
2077
|
listboxId = `${this.instanceId}-listbox`;
|
|
1900
2078
|
isOpen = signal(false, /* @ts-ignore */
|
|
1901
2079
|
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
2080
|
+
searchQuery = signal('', /* @ts-ignore */
|
|
2081
|
+
...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
2082
|
+
activeIndex = signal(0, /* @ts-ignore */
|
|
2083
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
1902
2084
|
/** Resolves the union (`string[] | { value; label }[]`) into a single shape for the template. */
|
|
1903
2085
|
normalizedOptions = computed(() => this.options().map((option) => typeof option === 'string' ? { value: option, label: option } : option), /* @ts-ignore */
|
|
1904
2086
|
...(ngDevMode ? [{ debugName: "normalizedOptions" }] : /* istanbul ignore next */ []));
|
|
1905
2087
|
selectedOption = computed(() => this.normalizedOptions().find((option) => option.value === this.value()), /* @ts-ignore */
|
|
1906
2088
|
...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
|
|
2089
|
+
showSearch = computed(() => this.searchable() ?? this.normalizedOptions().length > this.searchThreshold(), /* @ts-ignore */
|
|
2090
|
+
...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
|
|
2091
|
+
/** Opciones visibles tras aplicar el filtro de búsqueda (case/acento-insensitive). */
|
|
2092
|
+
filteredOptions = computed(() => {
|
|
2093
|
+
const query = normalizeForSearch(this.searchQuery().trim());
|
|
2094
|
+
if (!query) {
|
|
2095
|
+
return this.normalizedOptions();
|
|
2096
|
+
}
|
|
2097
|
+
return this.normalizedOptions().filter((option) => normalizeForSearch(option.label).includes(query));
|
|
2098
|
+
}, /* @ts-ignore */
|
|
2099
|
+
...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
|
|
1907
2100
|
triggerRef = viewChild.required('trigger', /* @ts-ignore */
|
|
1908
2101
|
...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
|
|
1909
2102
|
panelTemplate = viewChild.required('panelTemplate', /* @ts-ignore */
|
|
1910
2103
|
...(ngDevMode ? [{ debugName: "panelTemplate" }] : /* istanbul ignore next */ []));
|
|
2104
|
+
searchInputRef = viewChild('searchInput', /* @ts-ignore */
|
|
2105
|
+
...(ngDevMode ? [{ debugName: "searchInputRef" }] : /* istanbul ignore next */ []));
|
|
1911
2106
|
overlayService = inject(Overlay);
|
|
1912
2107
|
viewContainerRef = inject(ViewContainerRef);
|
|
1913
2108
|
destroyRef = inject(DestroyRef);
|
|
@@ -1919,6 +2114,17 @@ class Select {
|
|
|
1919
2114
|
}
|
|
1920
2115
|
else {
|
|
1921
2116
|
this.detach();
|
|
2117
|
+
this.searchQuery.set('');
|
|
2118
|
+
this.activeIndex.set(0);
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
// Autofocus del buscador al abrir. afterNextRender() NO puede invocarse
|
|
2122
|
+
// dentro de un effect() (NG0203: pierde el contexto de inyección) — hay
|
|
2123
|
+
// que usar afterRenderEffect(), que sí es reactivo a signals y corre
|
|
2124
|
+
// después de que Angular actualizó el DOM del panel recién adjuntado.
|
|
2125
|
+
afterRenderEffect(() => {
|
|
2126
|
+
if (this.isOpen() && this.showSearch()) {
|
|
2127
|
+
this.searchInputRef()?.nativeElement.focus();
|
|
1922
2128
|
}
|
|
1923
2129
|
});
|
|
1924
2130
|
this.destroyRef.onDestroy(() => this.overlayRef?.dispose());
|
|
@@ -1944,6 +2150,30 @@ class Select {
|
|
|
1944
2150
|
this.valueChange.emit(option.value);
|
|
1945
2151
|
}
|
|
1946
2152
|
}
|
|
2153
|
+
onSearchInput(event) {
|
|
2154
|
+
this.searchQuery.set(event.target.value);
|
|
2155
|
+
// Cada cambio de filtro vuelve a arrancar la navegación desde arriba.
|
|
2156
|
+
this.activeIndex.set(0);
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp
|
|
2160
|
+
* simple (sin wraparound): llegar al final o al principio de la lista se
|
|
2161
|
+
* queda ahí en vez de dar la vuelta — es el comportamiento más predecible
|
|
2162
|
+
* cuando se combina con un buscador que puede reducir la lista a 1 opción.
|
|
2163
|
+
*/
|
|
2164
|
+
moveActive(delta) {
|
|
2165
|
+
const count = this.filteredOptions().length;
|
|
2166
|
+
if (count === 0) {
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
this.activeIndex.update((current) => Math.min(Math.max(current + delta, 0), count - 1));
|
|
2170
|
+
}
|
|
2171
|
+
selectActive() {
|
|
2172
|
+
const option = this.filteredOptions()[this.activeIndex()];
|
|
2173
|
+
if (option) {
|
|
2174
|
+
this.select(option);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
1947
2177
|
attach() {
|
|
1948
2178
|
if (this.overlayRef?.hasAttached()) {
|
|
1949
2179
|
return;
|
|
@@ -1956,7 +2186,21 @@ class Select {
|
|
|
1956
2186
|
overlayRef.updateSize({ width: this.triggerRef().nativeElement.getBoundingClientRect().width });
|
|
1957
2187
|
const portal = new TemplatePortal(this.panelTemplate(), this.viewContainerRef);
|
|
1958
2188
|
overlayRef.attach(portal);
|
|
1959
|
-
|
|
2189
|
+
// outsidePointerEvents() dispara en la fase de CAPTURA de `click` sobre
|
|
2190
|
+
// `body` (ver OverlayOutsideClickDispatcher en @angular/cdk), es decir
|
|
2191
|
+
// ANTES de que el evento llegue al trigger y corra (click)="toggle()".
|
|
2192
|
+
// Sin este filtro, un click en el trigger para CERRAR el panel ya abierto
|
|
2193
|
+
// hacía: 1) este subscribe lo cierra primero (el trigger no es parte del
|
|
2194
|
+
// overlay, cuenta como "afuera"), 2) toggle() corre después y lo vuelve a
|
|
2195
|
+
// abrir porque ve isOpen ya en false — un solo click quedaba en no-op y
|
|
2196
|
+
// solo cerraba de verdad un click fuera del trigger.
|
|
2197
|
+
overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
2198
|
+
const target = event.target;
|
|
2199
|
+
if (target && this.triggerRef().nativeElement.contains(target)) {
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
this.isOpen.set(false);
|
|
2203
|
+
});
|
|
1960
2204
|
overlayRef.keydownEvents().subscribe((event) => {
|
|
1961
2205
|
if (event.key === 'Escape') {
|
|
1962
2206
|
this.isOpen.set(false);
|
|
@@ -1990,7 +2234,7 @@ class Select {
|
|
|
1990
2234
|
return this.overlayRef;
|
|
1991
2235
|
}
|
|
1992
2236
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1993
|
-
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 } }, outputs: { valueChange: "valueChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" } }, viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["trigger"], descendants: true, isSignal: true }, { propertyName: "panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2237
|
+
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 }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchThreshold: { classPropertyName: "searchThreshold", publicName: "searchThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { listeners: { "document:keydown.escape": "onDocumentEscape()" } }, 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: `
|
|
1994
2238
|
@if (label()) {
|
|
1995
2239
|
<kf-field-label [for]="triggerId">{{ label() }}</kf-field-label>
|
|
1996
2240
|
}
|
|
@@ -2016,31 +2260,52 @@ class Select {
|
|
|
2016
2260
|
></span>
|
|
2017
2261
|
</button>
|
|
2018
2262
|
<ng-template #panelTemplate>
|
|
2019
|
-
<
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
(click)="select(option)"
|
|
2033
|
-
(keydown.enter)="select(option)"
|
|
2034
|
-
(keydown.space)="$event.preventDefault(); select(option)"
|
|
2035
|
-
>
|
|
2036
|
-
{{ option.label }}
|
|
2037
|
-
</li>
|
|
2038
|
-
} @empty {
|
|
2039
|
-
<li class="kf-select__empty">Sin opciones disponibles</li>
|
|
2263
|
+
<div class="kf-select__panel">
|
|
2264
|
+
@if (showSearch()) {
|
|
2265
|
+
<input
|
|
2266
|
+
#searchInput
|
|
2267
|
+
type="text"
|
|
2268
|
+
class="kf-select__search"
|
|
2269
|
+
placeholder="Buscar…"
|
|
2270
|
+
[value]="searchQuery()"
|
|
2271
|
+
(input)="onSearchInput($event)"
|
|
2272
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
2273
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
2274
|
+
(keydown.enter)="$event.preventDefault(); selectActive()"
|
|
2275
|
+
/>
|
|
2040
2276
|
}
|
|
2041
|
-
|
|
2277
|
+
<ul
|
|
2278
|
+
class="kf-select__list"
|
|
2279
|
+
[id]="listboxId"
|
|
2280
|
+
role="listbox"
|
|
2281
|
+
[attr.aria-labelledby]="triggerId"
|
|
2282
|
+
>
|
|
2283
|
+
@for (option of filteredOptions(); track option.value; let i = $index) {
|
|
2284
|
+
<li
|
|
2285
|
+
class="kf-select__option"
|
|
2286
|
+
[class.kf-select__option--selected]="option.value === value()"
|
|
2287
|
+
[class.kf-select__option--active]="i === activeIndex()"
|
|
2288
|
+
role="option"
|
|
2289
|
+
tabindex="0"
|
|
2290
|
+
[attr.aria-selected]="option.value === value()"
|
|
2291
|
+
(click)="select(option)"
|
|
2292
|
+
(mouseenter)="activeIndex.set(i)"
|
|
2293
|
+
(keydown.enter)="select(option)"
|
|
2294
|
+
(keydown.space)="$event.preventDefault(); select(option)"
|
|
2295
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
2296
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
2297
|
+
>
|
|
2298
|
+
{{ option.label }}
|
|
2299
|
+
</li>
|
|
2300
|
+
} @empty {
|
|
2301
|
+
<li class="kf-select__empty">
|
|
2302
|
+
{{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}
|
|
2303
|
+
</li>
|
|
2304
|
+
}
|
|
2305
|
+
</ul>
|
|
2306
|
+
</div>
|
|
2042
2307
|
</ng-template>
|
|
2043
|
-
`, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:240px;font-family:var(--kf-font-body)}.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__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__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-select__caret--open{transform:rotate(-135deg);margin-top:4px}.kf-select__panel{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;
|
|
2308
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:240px;font-family:var(--kf-font-body)}.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__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__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-select__caret--open{transform:rotate(-135deg);margin-top:4px}.kf-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"], dependencies: [{ kind: "component", type: FieldLabel, selector: "kf-field-label", inputs: ["for", "required"] }] });
|
|
2044
2309
|
}
|
|
2045
2310
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Select, decorators: [{
|
|
2046
2311
|
type: Component,
|
|
@@ -2070,32 +2335,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2070
2335
|
></span>
|
|
2071
2336
|
</button>
|
|
2072
2337
|
<ng-template #panelTemplate>
|
|
2073
|
-
<
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
(click)="select(option)"
|
|
2087
|
-
(keydown.enter)="select(option)"
|
|
2088
|
-
(keydown.space)="$event.preventDefault(); select(option)"
|
|
2089
|
-
>
|
|
2090
|
-
{{ option.label }}
|
|
2091
|
-
</li>
|
|
2092
|
-
} @empty {
|
|
2093
|
-
<li class="kf-select__empty">Sin opciones disponibles</li>
|
|
2338
|
+
<div class="kf-select__panel">
|
|
2339
|
+
@if (showSearch()) {
|
|
2340
|
+
<input
|
|
2341
|
+
#searchInput
|
|
2342
|
+
type="text"
|
|
2343
|
+
class="kf-select__search"
|
|
2344
|
+
placeholder="Buscar…"
|
|
2345
|
+
[value]="searchQuery()"
|
|
2346
|
+
(input)="onSearchInput($event)"
|
|
2347
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
2348
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
2349
|
+
(keydown.enter)="$event.preventDefault(); selectActive()"
|
|
2350
|
+
/>
|
|
2094
2351
|
}
|
|
2095
|
-
|
|
2352
|
+
<ul
|
|
2353
|
+
class="kf-select__list"
|
|
2354
|
+
[id]="listboxId"
|
|
2355
|
+
role="listbox"
|
|
2356
|
+
[attr.aria-labelledby]="triggerId"
|
|
2357
|
+
>
|
|
2358
|
+
@for (option of filteredOptions(); track option.value; let i = $index) {
|
|
2359
|
+
<li
|
|
2360
|
+
class="kf-select__option"
|
|
2361
|
+
[class.kf-select__option--selected]="option.value === value()"
|
|
2362
|
+
[class.kf-select__option--active]="i === activeIndex()"
|
|
2363
|
+
role="option"
|
|
2364
|
+
tabindex="0"
|
|
2365
|
+
[attr.aria-selected]="option.value === value()"
|
|
2366
|
+
(click)="select(option)"
|
|
2367
|
+
(mouseenter)="activeIndex.set(i)"
|
|
2368
|
+
(keydown.enter)="select(option)"
|
|
2369
|
+
(keydown.space)="$event.preventDefault(); select(option)"
|
|
2370
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
2371
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
2372
|
+
>
|
|
2373
|
+
{{ option.label }}
|
|
2374
|
+
</li>
|
|
2375
|
+
} @empty {
|
|
2376
|
+
<li class="kf-select__empty">
|
|
2377
|
+
{{ searchQuery() ? 'Sin resultados' : 'Sin opciones disponibles' }}
|
|
2378
|
+
</li>
|
|
2379
|
+
}
|
|
2380
|
+
</ul>
|
|
2381
|
+
</div>
|
|
2096
2382
|
</ng-template>
|
|
2097
|
-
`, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:240px;font-family:var(--kf-font-body)}.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__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__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-select__caret--open{transform:rotate(-135deg);margin-top:4px}.kf-select__panel{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;
|
|
2098
|
-
}], 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 }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], onDocumentEscape: [{
|
|
2383
|
+
`, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-xs);width:240px;font-family:var(--kf-font-body)}.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__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-select__value--placeholder{color:var(--kf-color-text-muted)}.kf-select__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-select__caret--open{transform:rotate(-135deg);margin-top:4px}.kf-select__panel{box-sizing:border-box;width:100%;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md);overflow:hidden}.kf-select__search{box-sizing:border-box;width:100%;font:inherit;font-family:var(--kf-font-body);color:var(--kf-color-text);padding:var(--kf-space-sm) var(--kf-space-md);border:none;border-bottom:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;outline:none}.kf-select__search:focus-visible{box-shadow:inset 0 0 0 2px var(--kf-color-primary)}.kf-select__list{box-sizing:border-box;width:100%;margin:0;padding:var(--kf-space-xs) 0;list-style:none;max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--kf-color-gray-400) transparent}.kf-select__list::-webkit-scrollbar{width:6px}.kf-select__list::-webkit-scrollbar-track{background:transparent}.kf-select__list::-webkit-scrollbar-thumb{background-color:var(--kf-color-gray-400);border-radius:var(--kf-radius-pill)}.kf-select__list::-webkit-scrollbar-thumb:hover{background-color:var(--kf-color-gray-500)}.kf-select__option{padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-select__option:hover{background:var(--kf-color-primary-soft)}.kf-select__option--selected{background:var(--kf-color-primary-soft);color:var(--kf-color-primary);font-weight:600}.kf-select__option--active{background:var(--kf-color-primary-soft)}.kf-select__empty{text-align:center;padding:var(--kf-space-sm) var(--kf-space-md);color:var(--kf-color-text-muted)}\n"] }]
|
|
2384
|
+
}], 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 }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchThreshold", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['trigger', { isSignal: true }] }], panelTemplate: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], onDocumentEscape: [{
|
|
2099
2385
|
type: HostListener,
|
|
2100
2386
|
args: ['document:keydown.escape']
|
|
2101
2387
|
}] } });
|
|
@@ -2150,7 +2436,7 @@ class CompanySelector {
|
|
|
2150
2436
|
}
|
|
2151
2437
|
</div>
|
|
2152
2438
|
}
|
|
2153
|
-
`, isInline: true, styles: [":host{display:flex;align-items:center;gap:var(--kf-space-lg);flex-wrap:wrap;padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font-family:var(--kf-font-body)}.kf-company-selector__select{flex:1 1 260px;min-width:0}.kf-company-selector__meta{display:flex;gap:var(--kf-space-lg);flex-wrap:wrap}.kf-company-selector__meta-item{display:flex;flex-direction:column;gap:2px}.kf-company-selector__meta-label{font-family:var(--kf-font-title);font-size:.72rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-company-selector__meta-value{font-family:var(--kf-font-mono);font-size:.875rem;color:var(--kf-color-ink)}\n"], dependencies: [{ kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled"], outputs: ["valueChange"] }] });
|
|
2439
|
+
`, isInline: true, styles: [":host{display:flex;align-items:center;gap:var(--kf-space-lg);flex-wrap:wrap;padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font-family:var(--kf-font-body)}.kf-company-selector__select{flex:1 1 260px;min-width:0}.kf-company-selector__meta{display:flex;gap:var(--kf-space-lg);flex-wrap:wrap}.kf-company-selector__meta-item{display:flex;flex-direction:column;gap:2px}.kf-company-selector__meta-label{font-family:var(--kf-font-title);font-size:.72rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--kf-color-text-muted)}.kf-company-selector__meta-value{font-family:var(--kf-font-mono);font-size:.875rem;color:var(--kf-color-ink)}\n"], dependencies: [{ kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "searchable", "searchThreshold"], outputs: ["valueChange"] }] });
|
|
2154
2440
|
}
|
|
2155
2441
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CompanySelector, decorators: [{
|
|
2156
2442
|
type: Component,
|
|
@@ -2894,110 +3180,449 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
2894
3180
|
`, styles: [":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__view-toggle{display:inline-flex;gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm)}.kf-data-table__view-option{font:inherit;font-size:.875em;padding:var(--kf-space-xs) var(--kf-space-sm);border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-xs);cursor:pointer;transition:background .14s,color .14s}.kf-data-table__view-option:hover:not(.kf-data-table__view-option--active){color:var(--kf-color-text)}.kf-data-table__view-option--active{background:var(--kf-color-white);color:var(--kf-color-ink);box-shadow:var(--kf-shadow-xs)}.kf-data-table__view-option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-data-table__scroll{overflow-x:auto}.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__table th{font-family:var(--kf-font-title);font-size:.8125em;text-transform:uppercase;letter-spacing:.02em;color:var(--kf-color-text-muted);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{cursor:pointer;transition:background .14s}.kf-data-table__row:hover{background:var(--kf-color-primary-soft)}.kf-data-table__cell--right{text-align:right}.kf-data-table__empty{padding:var(--kf-space-md)}.kf-data-table__footer{padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border)}.kf-data-table__footer:empty{display:none}.kf-data-table__cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--kf-space-md);padding:var(--kf-space-md)}.kf-data-table__card{display:flex;flex-direction:column;border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);overflow:hidden;transition:border-color .14s,box-shadow .14s}.kf-data-table__card--interactive{cursor:pointer}.kf-data-table__card:hover{border-color:var(--kf-color-border-strong);box-shadow:var(--kf-shadow-xs)}.kf-data-table__card--interactive:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-data-table__card-header{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-md)}.kf-data-table__card-media{flex:none}.kf-data-table__card-media img,.kf-data-table__thumb{display:block;width:48px;height:48px;border-radius:var(--kf-radius-xs);object-fit:cover;background:var(--kf-color-surface)}.kf-data-table__card-heading{display:flex;flex-direction:column;gap:var(--kf-space-xs);min-width:0}.kf-data-table__card-title{font-family:var(--kf-font-title);font-weight:600;color:var(--kf-color-ink);overflow-wrap:anywhere}.kf-data-table__card-statuses,.kf-data-table__card-actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-data-table__card-fields{display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-md)}.kf-data-table__card-header+.kf-data-table__card-fields{padding-top:0}.kf-data-table__card-field{display:flex;justify-content:space-between;gap:var(--kf-space-sm)}.kf-data-table__card-actions{justify-content:flex-end;margin-top:auto;padding:var(--kf-space-sm) var(--kf-space-md);border-top:1px solid var(--kf-color-border);background:var(--kf-color-surface)}.kf-data-table__thumb{width:32px;height:32px}.kf-data-table__card-label{font-family:var(--kf-font-title);font-size:.8125em;text-transform:uppercase;letter-spacing:.02em;color:var(--kf-color-text-muted)}.kf-data-table__card-value{color:var(--kf-color-text);text-align:right}\n"] }]
|
|
2895
3181
|
}], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], cardable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardable", required: false }] }], defaultView: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultView", required: false }] }], render: [{ type: i0.Input, args: [{ isSignal: true, alias: "render", required: false }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }] } });
|
|
2896
3182
|
|
|
3183
|
+
/**
|
|
3184
|
+
* Panel lateral genérico, agnóstico de contenido — no sabe qué es navegación
|
|
3185
|
+
* ni qué es sesión, solo da tres lugares para proyectar contenido
|
|
3186
|
+
* (`[kf-drawer-header]`, default, `[kf-drawer-footer]`) y resuelve lo que
|
|
3187
|
+
* cualquier drawer necesita resolver igual sin importar qué haya adentro:
|
|
3188
|
+
* overlay con blur que cierra al click, `Escape`, atrapado de foco real y
|
|
3189
|
+
* devolución de foco al cerrar.
|
|
3190
|
+
*
|
|
3191
|
+
* Extraído de `PortalHeader`, que hasta acá reimplementaba su propio drawer
|
|
3192
|
+
* embebido (navegación hardcodeada + slot único al final). Cualquier
|
|
3193
|
+
* consumidor que necesite un panel lateral — no solo `PortalHeader` — puede
|
|
3194
|
+
* usar este componente directamente.
|
|
3195
|
+
*
|
|
3196
|
+
* `open` es two-way, mismo patrón que `Dropdown`/`Modal`: el consumidor
|
|
3197
|
+
* decide qué dispara la apertura (un botón de hamburguesa, por ejemplo) y
|
|
3198
|
+
* qué hace con el cierre.
|
|
3199
|
+
*
|
|
3200
|
+
* Foco: al abrir, se crea un `FocusTrap` del CDK sobre el panel (la misma
|
|
3201
|
+
* pieza que usa Angular Material para `mat-sidenav`/`mat-dialog`) — Tab y
|
|
3202
|
+
* Shift+Tab ciclan solo entre los elementos focuseables del drawer, sin
|
|
3203
|
+
* escapar al contenido de fondo. Al cerrar, el foco vuelve al elemento que
|
|
3204
|
+
* tenía el foco antes de abrir (guardado en `document.activeElement`), no
|
|
3205
|
+
* se pierde en el `<body>`.
|
|
3206
|
+
*
|
|
3207
|
+
* `[kf-drawer-header]` tiene fondo oscuro propio (`--kf-color-tertiary-deep`),
|
|
3208
|
+
* distinto del body (`--kf-color-white`) — pensado para contener
|
|
3209
|
+
* `kf-drawer-session`, que asume ese fondo y no lo trae él mismo (ver
|
|
3210
|
+
* `DrawerSession.ts`). Si el slot queda vacío, el contenedor colapsa
|
|
3211
|
+
* (`display: none`) y no deja una franja oscura sin contenido.
|
|
3212
|
+
*
|
|
3213
|
+
* <kf-drawer [(open)]="menuOpen" ariaLabel="Menú de navegación">
|
|
3214
|
+
* <div kf-drawer-header>
|
|
3215
|
+
* <kf-drawer-session name="Tomás Soto" email="tomas@kaufmann.com" (logoutClick)="onLogout()" />
|
|
3216
|
+
* </div>
|
|
3217
|
+
* <a href="/">Home</a>
|
|
3218
|
+
* <a href="/vehiculos">Vehículos</a>
|
|
3219
|
+
* </kf-drawer>
|
|
3220
|
+
*/
|
|
3221
|
+
class Drawer {
|
|
3222
|
+
/** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
|
|
3223
|
+
open = model(false, /* @ts-ignore */
|
|
3224
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
3225
|
+
/** Borde de anclaje del panel. `left` (default) es la convención dominante
|
|
3226
|
+
* (Material/iOS/Android): el botón que abre el drawer y el propio panel
|
|
3227
|
+
* quedan del mismo lado. */
|
|
3228
|
+
side = input('left', /* @ts-ignore */
|
|
3229
|
+
...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
3230
|
+
/** `aria-label` del `<nav>` interno — describe el propósito del panel para
|
|
3231
|
+
* lectores de pantalla (ej. "Menú de navegación"). */
|
|
3232
|
+
ariaLabel = input('Menú', /* @ts-ignore */
|
|
3233
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
3234
|
+
panelRef = viewChild.required('panel', /* @ts-ignore */
|
|
3235
|
+
...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
|
|
3236
|
+
focusTrapFactory = inject(FocusTrapFactory);
|
|
3237
|
+
document = inject(DOCUMENT);
|
|
3238
|
+
focusTrap = null;
|
|
3239
|
+
lastFocusedElement = null;
|
|
3240
|
+
constructor() {
|
|
3241
|
+
afterRenderEffect(() => {
|
|
3242
|
+
if (this.open()) {
|
|
3243
|
+
this.lastFocusedElement = this.document.activeElement;
|
|
3244
|
+
const trap = this.getOrCreateFocusTrap();
|
|
3245
|
+
void trap.focusInitialElement();
|
|
3246
|
+
}
|
|
3247
|
+
else {
|
|
3248
|
+
this.focusTrap?.destroy();
|
|
3249
|
+
this.focusTrap = null;
|
|
3250
|
+
this.lastFocusedElement?.focus();
|
|
3251
|
+
this.lastFocusedElement = null;
|
|
3252
|
+
}
|
|
3253
|
+
});
|
|
3254
|
+
inject(DestroyRef).onDestroy(() => this.focusTrap?.destroy());
|
|
3255
|
+
}
|
|
3256
|
+
/** Cierra el drawer con Escape — mismo patrón que `Dropdown`/`Modal`. */
|
|
3257
|
+
onEscape() {
|
|
3258
|
+
this.open.set(false);
|
|
3259
|
+
}
|
|
3260
|
+
getOrCreateFocusTrap() {
|
|
3261
|
+
if (!this.focusTrap) {
|
|
3262
|
+
this.focusTrap = this.focusTrapFactory.create(this.panelRef().nativeElement);
|
|
3263
|
+
}
|
|
3264
|
+
return this.focusTrap;
|
|
3265
|
+
}
|
|
3266
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3267
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Drawer, isStandalone: true, selector: "kf-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
3268
|
+
@if (open()) {
|
|
3269
|
+
<button
|
|
3270
|
+
type="button"
|
|
3271
|
+
class="kf-drawer__overlay"
|
|
3272
|
+
aria-label="Cerrar menú"
|
|
3273
|
+
(click)="open.set(false)"
|
|
3274
|
+
></button>
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
<nav
|
|
3278
|
+
#panel
|
|
3279
|
+
class="kf-drawer__panel"
|
|
3280
|
+
[class.kf-drawer__panel--open]="open()"
|
|
3281
|
+
[class.kf-drawer__panel--right]="side() === 'right'"
|
|
3282
|
+
[attr.aria-label]="ariaLabel()"
|
|
3283
|
+
[attr.aria-hidden]="!open()"
|
|
3284
|
+
>
|
|
3285
|
+
<div class="kf-drawer__header"><ng-content select="[kf-drawer-header]" /></div>
|
|
3286
|
+
<div class="kf-drawer__body"><ng-content /></div>
|
|
3287
|
+
<div class="kf-drawer__footer"><ng-content select="[kf-drawer-footer]" /></div>
|
|
3288
|
+
</nav>
|
|
3289
|
+
`, isInline: true, styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--open{transform:translate(0);visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__footer{padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}\n"] });
|
|
3290
|
+
}
|
|
3291
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Drawer, decorators: [{
|
|
3292
|
+
type: Component,
|
|
3293
|
+
args: [{ selector: 'kf-drawer', template: `
|
|
3294
|
+
@if (open()) {
|
|
3295
|
+
<button
|
|
3296
|
+
type="button"
|
|
3297
|
+
class="kf-drawer__overlay"
|
|
3298
|
+
aria-label="Cerrar menú"
|
|
3299
|
+
(click)="open.set(false)"
|
|
3300
|
+
></button>
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
<nav
|
|
3304
|
+
#panel
|
|
3305
|
+
class="kf-drawer__panel"
|
|
3306
|
+
[class.kf-drawer__panel--open]="open()"
|
|
3307
|
+
[class.kf-drawer__panel--right]="side() === 'right'"
|
|
3308
|
+
[attr.aria-label]="ariaLabel()"
|
|
3309
|
+
[attr.aria-hidden]="!open()"
|
|
3310
|
+
>
|
|
3311
|
+
<div class="kf-drawer__header"><ng-content select="[kf-drawer-header]" /></div>
|
|
3312
|
+
<div class="kf-drawer__body"><ng-content /></div>
|
|
3313
|
+
<div class="kf-drawer__footer"><ng-content select="[kf-drawer-footer]" /></div>
|
|
3314
|
+
</nav>
|
|
3315
|
+
`, styles: [".kf-drawer__overlay{position:fixed;inset:0;border:none;padding:0;cursor:pointer;background:#171d2566;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:39}.kf-drawer__panel{position:fixed;top:0;left:0;bottom:0;z-index:40;display:flex;flex-direction:column;width:82%;max-width:320px;background:var(--kf-color-white);box-shadow:8px 0 24px -8px #1018284d;transform:translate(-100%);visibility:hidden;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear .24s}.kf-drawer__panel--right{left:auto;right:0;box-shadow:-8px 0 24px -8px #1018284d;transform:translate(100%)}.kf-drawer__panel--open{transform:translate(0);visibility:visible;transition:transform .24s cubic-bezier(.32,.72,0,1),visibility 0s linear 0s}.kf-drawer__header:empty,.kf-drawer__footer:empty{display:none}.kf-drawer__header{padding:var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-drawer__body{flex:1 1 auto;display:flex;flex-direction:column;gap:var(--kf-space-xs);padding:var(--kf-space-lg);overflow-y:auto}.kf-drawer__footer{padding:var(--kf-space-lg);border-top:1px solid var(--kf-color-border)}\n"] }]
|
|
3316
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onEscape: [{
|
|
3317
|
+
type: HostListener,
|
|
3318
|
+
args: ['document:keydown.escape']
|
|
3319
|
+
}] } });
|
|
3320
|
+
|
|
3321
|
+
/**
|
|
3322
|
+
* Botón de cerrar sesión, pensado para proyectarse dentro de
|
|
3323
|
+
* `[kf-drawer-footer]` de `kf-drawer`, o junto a `kf-drawer-session` dentro
|
|
3324
|
+
* de `[kf-sidebar-footer]` de `kf-sidebar-shell` — separado de
|
|
3325
|
+
* `kf-drawer-session` (que solo muestra identidad: brand, avatar, nombre,
|
|
3326
|
+
* email), no anidado dentro de ella.
|
|
3327
|
+
*
|
|
3328
|
+
* Reproduce el mismo botón que antes vivía embebido en `DrawerSession`:
|
|
3329
|
+
* ícono `log-out` + label, color `--kf-color-error` con hover en
|
|
3330
|
+
* `--kf-color-error-soft` y foco visible con `--kf-shadow-focus`. Se separó
|
|
3331
|
+
* en un componente propio porque identidad y logout son responsabilidades
|
|
3332
|
+
* distintas — `kf-drawer-session` no debería saber que existe un botón de
|
|
3333
|
+
* cerrar sesión, y el footer del drawer ya tiene su propio padding/borde
|
|
3334
|
+
* superior (ver `Drawer.ts`) que reemplaza el borde que antes traía el botón
|
|
3335
|
+
* mismo.
|
|
3336
|
+
*
|
|
3337
|
+
* No sabe nada de autenticación real — solo emite `logoutClick` cuando el
|
|
3338
|
+
* consumidor debe cerrar la sesión real.
|
|
3339
|
+
*
|
|
3340
|
+
* `collapsed` oculta el label y centra el ícono, exponiéndolo vía `title`
|
|
3341
|
+
* — mismo criterio que `SidebarNavItem.collapsed` y
|
|
3342
|
+
* `DrawerSession.collapsed`, para cuando este botón vive dentro de un
|
|
3343
|
+
* `kf-sidebar-shell` colapsado.
|
|
3344
|
+
*
|
|
3345
|
+
* <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
|
|
3346
|
+
*/
|
|
3347
|
+
class DrawerLogout {
|
|
3348
|
+
/** Label del botón, en español neutro por defecto. Input opcional por si
|
|
3349
|
+
* algún consumidor futuro necesita otro texto. */
|
|
3350
|
+
label = input('Cerrar sesión', /* @ts-ignore */
|
|
3351
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
3352
|
+
/** Oculta el label y centra el ícono — ver comentario de la clase. */
|
|
3353
|
+
collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3354
|
+
logoutClick = output();
|
|
3355
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DrawerLogout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3356
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DrawerLogout, isStandalone: true, selector: "kf-drawer-logout", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logoutClick: "logoutClick" }, ngImport: i0, template: `
|
|
3357
|
+
<button
|
|
3358
|
+
type="button"
|
|
3359
|
+
class="kf-drawer-logout__button"
|
|
3360
|
+
[class.kf-drawer-logout__button--collapsed]="collapsed()"
|
|
3361
|
+
[attr.title]="collapsed() ? label() : null"
|
|
3362
|
+
(click)="logoutClick.emit()"
|
|
3363
|
+
>
|
|
3364
|
+
<kf-icon name="log-out" size="sm" />
|
|
3365
|
+
@if (!collapsed()) {
|
|
3366
|
+
{{ label() }}
|
|
3367
|
+
}
|
|
3368
|
+
</button>
|
|
3369
|
+
`, isInline: true, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-drawer-logout__button{display:flex;align-items:center;gap:var(--kf-space-xs);width:100%;border:none;background:transparent;padding:0;font:inherit;font-size:.875rem;font-weight:600;color:var(--kf-color-error);cursor:pointer}.kf-drawer-logout__button--collapsed{justify-content:center}.kf-drawer-logout__button:hover{color:var(--kf-color-error-strong);background:var(--kf-color-error-soft)}.kf-drawer-logout__button:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }] });
|
|
3370
|
+
}
|
|
3371
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DrawerLogout, decorators: [{
|
|
3372
|
+
type: Component,
|
|
3373
|
+
args: [{ selector: 'kf-drawer-logout', imports: [Icon], template: `
|
|
3374
|
+
<button
|
|
3375
|
+
type="button"
|
|
3376
|
+
class="kf-drawer-logout__button"
|
|
3377
|
+
[class.kf-drawer-logout__button--collapsed]="collapsed()"
|
|
3378
|
+
[attr.title]="collapsed() ? label() : null"
|
|
3379
|
+
(click)="logoutClick.emit()"
|
|
3380
|
+
>
|
|
3381
|
+
<kf-icon name="log-out" size="sm" />
|
|
3382
|
+
@if (!collapsed()) {
|
|
3383
|
+
{{ label() }}
|
|
3384
|
+
}
|
|
3385
|
+
</button>
|
|
3386
|
+
`, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-drawer-logout__button{display:flex;align-items:center;gap:var(--kf-space-xs);width:100%;border:none;background:transparent;padding:0;font:inherit;font-size:.875rem;font-weight:600;color:var(--kf-color-error);cursor:pointer}.kf-drawer-logout__button--collapsed{justify-content:center}.kf-drawer-logout__button:hover{color:var(--kf-color-error-strong);background:var(--kf-color-error-soft)}.kf-drawer-logout__button:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
|
|
3387
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], logoutClick: [{ type: i0.Output, args: ["logoutClick"] }] } });
|
|
3388
|
+
|
|
3389
|
+
/**
|
|
3390
|
+
* Bloque de identidad para `[kf-drawer-header]` de `kf-drawer` o
|
|
3391
|
+
* `[kf-sidebar-footer]` de `kf-sidebar-shell`: avatar + nombre + email,
|
|
3392
|
+
* brand opcional arriba. Solo identidad — el logout vive aparte, en
|
|
3393
|
+
* `kf-drawer-logout`.
|
|
3394
|
+
*
|
|
3395
|
+
* `collapsed` oculta brand/nombre/email y deja solo el avatar (para un
|
|
3396
|
+
* `kf-sidebar-shell` colapsado). `interactive` convierte la tarjeta en un
|
|
3397
|
+
* botón real que emite `cardClick`, para cuando la identidad es el
|
|
3398
|
+
* disparador de un menú de acciones (ej. envuelta en `kf-dropdown`).
|
|
3399
|
+
*
|
|
3400
|
+
* <kf-dropdown [(open)]="menuOpen">
|
|
3401
|
+
* <kf-drawer-session interactive name="Juan Pérez" email="juan@empresa.com" (cardClick)="menuOpen.set(!menuOpen())" />
|
|
3402
|
+
* <kf-dropdown-panel variant="dark">
|
|
3403
|
+
* <kf-dropdown-item icon="log-out" tone="error" (click)="onLogout()">Cerrar sesión</kf-dropdown-item>
|
|
3404
|
+
* </kf-dropdown-panel>
|
|
3405
|
+
* </kf-dropdown>
|
|
3406
|
+
*/
|
|
3407
|
+
class DrawerSession {
|
|
3408
|
+
name = input.required(/* @ts-ignore */
|
|
3409
|
+
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
3410
|
+
email = input('', /* @ts-ignore */
|
|
3411
|
+
...(ngDevMode ? [{ debugName: "email" }] : /* istanbul ignore next */ []));
|
|
3412
|
+
initials = input('', /* @ts-ignore */
|
|
3413
|
+
...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
|
|
3414
|
+
avatarUrl = input('', /* @ts-ignore */
|
|
3415
|
+
...(ngDevMode ? [{ debugName: "avatarUrl" }] : /* istanbul ignore next */ []));
|
|
3416
|
+
/** Label superior opcional, ej. "Portal Kaufmann LATAM". */
|
|
3417
|
+
brand = input('', /* @ts-ignore */
|
|
3418
|
+
...(ngDevMode ? [{ debugName: "brand" }] : /* istanbul ignore next */ []));
|
|
3419
|
+
/** Oculta brand/nombre/email y centra el avatar — ver comentario de la clase. */
|
|
3420
|
+
collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3421
|
+
/** Convierte la tarjeta en un botón real con ícono de kebab — ver comentario de la clase. */
|
|
3422
|
+
interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3423
|
+
/** Se emite al clickear la tarjeta, solo cuando `interactive` es `true`. */
|
|
3424
|
+
cardClick = output();
|
|
3425
|
+
/** Si no se pasa `initials`, se derivan de `name`: primera letra de cada
|
|
3426
|
+
* una de las primeras 2 palabras, en mayúscula. */
|
|
3427
|
+
displayInitials = computed(() => {
|
|
3428
|
+
const explicit = this.initials();
|
|
3429
|
+
if (explicit) {
|
|
3430
|
+
return explicit;
|
|
3431
|
+
}
|
|
3432
|
+
return this.name()
|
|
3433
|
+
.trim()
|
|
3434
|
+
.split(/\s+/)
|
|
3435
|
+
.slice(0, 2)
|
|
3436
|
+
.map((word) => word.charAt(0).toUpperCase())
|
|
3437
|
+
.join('');
|
|
3438
|
+
}, /* @ts-ignore */
|
|
3439
|
+
...(ngDevMode ? [{ debugName: "displayInitials" }] : /* istanbul ignore next */ []));
|
|
3440
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DrawerSession, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3441
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DrawerSession, isStandalone: true, selector: "kf-drawer-session", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, email: { classPropertyName: "email", publicName: "email", isSignal: true, isRequired: false, transformFunction: null }, initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: false, transformFunction: null }, avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: `
|
|
3442
|
+
@if (brand() && !collapsed()) {
|
|
3443
|
+
<span class="kf-drawer-session__brand">{{ brand() }}</span>
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
@if (interactive()) {
|
|
3447
|
+
<button
|
|
3448
|
+
type="button"
|
|
3449
|
+
class="kf-drawer-session__card kf-drawer-session__card--interactive"
|
|
3450
|
+
[class.kf-drawer-session__card--collapsed]="collapsed()"
|
|
3451
|
+
(click)="cardClick.emit()"
|
|
3452
|
+
>
|
|
3453
|
+
<kf-avatar [initials]="displayInitials()" [src]="avatarUrl()" />
|
|
3454
|
+
@if (!collapsed()) {
|
|
3455
|
+
<div class="kf-drawer-session__identity">
|
|
3456
|
+
<span class="kf-drawer-session__name">{{ name() }}</span>
|
|
3457
|
+
@if (email()) {
|
|
3458
|
+
<span class="kf-drawer-session__email">{{ email() }}</span>
|
|
3459
|
+
}
|
|
3460
|
+
</div>
|
|
3461
|
+
<kf-icon name="more-vertical" size="sm" />
|
|
3462
|
+
}
|
|
3463
|
+
</button>
|
|
3464
|
+
} @else {
|
|
3465
|
+
<div class="kf-drawer-session__card" [class.kf-drawer-session__card--collapsed]="collapsed()">
|
|
3466
|
+
<kf-avatar [initials]="displayInitials()" [src]="avatarUrl()" />
|
|
3467
|
+
@if (!collapsed()) {
|
|
3468
|
+
<div class="kf-drawer-session__identity">
|
|
3469
|
+
<span class="kf-drawer-session__name">{{ name() }}</span>
|
|
3470
|
+
@if (email()) {
|
|
3471
|
+
<span class="kf-drawer-session__email">{{ email() }}</span>
|
|
3472
|
+
}
|
|
3473
|
+
</div>
|
|
3474
|
+
}
|
|
3475
|
+
</div>
|
|
3476
|
+
}
|
|
3477
|
+
`, isInline: true, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-drawer-session__brand{display:block;margin-bottom:var(--kf-space-sm);font-family:var(--kf-font-mono);font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#ffffffa6}.kf-drawer-session__card{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm);border:1px solid rgba(255,255,255,.14);border-radius:var(--kf-radius-md);background:#ffffff0a}.kf-drawer-session__card--interactive{font:inherit;color:inherit;cursor:pointer;text-align:left;transition:background .14s}.kf-drawer-session__card--interactive:hover{background:#ffffff17}.kf-drawer-session__card--interactive:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-drawer-session__card--interactive kf-icon{flex:none;color:#ffffff73}.kf-drawer-session__card--collapsed{justify-content:center;width:40px;height:40px;padding:0;border-color:transparent;background:transparent}.kf-drawer-session__card--collapsed:hover{background:#ffffff17}.kf-drawer-session__identity{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.kf-drawer-session__name{font-size:.9375rem;font-weight:600;color:var(--kf-color-white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-drawer-session__email{font-size:.8125rem;color:#ffffffa6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "component", type: Avatar, selector: "kf-avatar", inputs: ["initials", "src", "round", "pill"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }] });
|
|
3478
|
+
}
|
|
3479
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DrawerSession, decorators: [{
|
|
3480
|
+
type: Component,
|
|
3481
|
+
args: [{ selector: 'kf-drawer-session', imports: [Avatar, Icon], template: `
|
|
3482
|
+
@if (brand() && !collapsed()) {
|
|
3483
|
+
<span class="kf-drawer-session__brand">{{ brand() }}</span>
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
@if (interactive()) {
|
|
3487
|
+
<button
|
|
3488
|
+
type="button"
|
|
3489
|
+
class="kf-drawer-session__card kf-drawer-session__card--interactive"
|
|
3490
|
+
[class.kf-drawer-session__card--collapsed]="collapsed()"
|
|
3491
|
+
(click)="cardClick.emit()"
|
|
3492
|
+
>
|
|
3493
|
+
<kf-avatar [initials]="displayInitials()" [src]="avatarUrl()" />
|
|
3494
|
+
@if (!collapsed()) {
|
|
3495
|
+
<div class="kf-drawer-session__identity">
|
|
3496
|
+
<span class="kf-drawer-session__name">{{ name() }}</span>
|
|
3497
|
+
@if (email()) {
|
|
3498
|
+
<span class="kf-drawer-session__email">{{ email() }}</span>
|
|
3499
|
+
}
|
|
3500
|
+
</div>
|
|
3501
|
+
<kf-icon name="more-vertical" size="sm" />
|
|
3502
|
+
}
|
|
3503
|
+
</button>
|
|
3504
|
+
} @else {
|
|
3505
|
+
<div class="kf-drawer-session__card" [class.kf-drawer-session__card--collapsed]="collapsed()">
|
|
3506
|
+
<kf-avatar [initials]="displayInitials()" [src]="avatarUrl()" />
|
|
3507
|
+
@if (!collapsed()) {
|
|
3508
|
+
<div class="kf-drawer-session__identity">
|
|
3509
|
+
<span class="kf-drawer-session__name">{{ name() }}</span>
|
|
3510
|
+
@if (email()) {
|
|
3511
|
+
<span class="kf-drawer-session__email">{{ email() }}</span>
|
|
3512
|
+
}
|
|
3513
|
+
</div>
|
|
3514
|
+
}
|
|
3515
|
+
</div>
|
|
3516
|
+
}
|
|
3517
|
+
`, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-drawer-session__brand{display:block;margin-bottom:var(--kf-space-sm);font-family:var(--kf-font-mono);font-size:.6875rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#ffffffa6}.kf-drawer-session__card{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm);border:1px solid rgba(255,255,255,.14);border-radius:var(--kf-radius-md);background:#ffffff0a}.kf-drawer-session__card--interactive{font:inherit;color:inherit;cursor:pointer;text-align:left;transition:background .14s}.kf-drawer-session__card--interactive:hover{background:#ffffff17}.kf-drawer-session__card--interactive:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-drawer-session__card--interactive kf-icon{flex:none;color:#ffffff73}.kf-drawer-session__card--collapsed{justify-content:center;width:40px;height:40px;padding:0;border-color:transparent;background:transparent}.kf-drawer-session__card--collapsed:hover{background:#ffffff17}.kf-drawer-session__identity{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.kf-drawer-session__name{font-size:.9375rem;font-weight:600;color:var(--kf-color-white);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kf-drawer-session__email{font-size:.8125rem;color:#ffffffa6;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
|
|
3518
|
+
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], email: [{ type: i0.Input, args: [{ isSignal: true, alias: "email", required: false }] }], initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "initials", required: false }] }], avatarUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUrl", required: false }] }], brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], cardClick: [{ type: i0.Output, args: ["cardClick"] }] } });
|
|
3519
|
+
|
|
2897
3520
|
/**
|
|
2898
3521
|
* Ítem de acción dentro de un `kf-dropdown-panel`. Es un `<button>` nativo
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
*
|
|
3522
|
+
* (no un `<a>`): dispara comportamiento en la pantalla actual, no navega.
|
|
3523
|
+
*
|
|
3524
|
+
* `icon` es opcional — sin él, el ítem es solo texto.
|
|
2902
3525
|
*
|
|
2903
|
-
* <kf-dropdown-item (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
|
|
3526
|
+
* <kf-dropdown-item icon="log-out" (click)="onLogout()" tone="error">Cerrar sesión</kf-dropdown-item>
|
|
2904
3527
|
*/
|
|
2905
3528
|
class DropdownItem {
|
|
2906
3529
|
tone = input('neutral', /* @ts-ignore */
|
|
2907
3530
|
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
3531
|
+
/** Ícono opcional al inicio de la fila. */
|
|
3532
|
+
icon = input(/* @ts-ignore */
|
|
3533
|
+
...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
2908
3534
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DropdownItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2909
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
3535
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: DropdownItem, isStandalone: true, selector: "kf-dropdown-item", inputs: { tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
2910
3536
|
<button
|
|
2911
3537
|
type="button"
|
|
2912
3538
|
class="kf-dropdown-item"
|
|
2913
3539
|
[class.kf-dropdown-item--error]="tone() === 'error'"
|
|
2914
3540
|
role="menuitem"
|
|
2915
3541
|
>
|
|
2916
|
-
|
|
3542
|
+
@if (icon()) {
|
|
3543
|
+
<kf-icon [name]="icon()!" size="sm" />
|
|
3544
|
+
}
|
|
3545
|
+
<span class="kf-dropdown-item__label"><ng-content /></span>
|
|
2917
3546
|
</button>
|
|
2918
|
-
`, isInline: true, styles: [":host{display:block}.kf-dropdown-item{display:
|
|
3547
|
+
`, isInline: true, styles: [":host{display:block}.kf-dropdown-item{display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;background:transparent;font:inherit;text-align:left;color:var(--kf-color-text);cursor:pointer}.kf-dropdown-item kf-icon{flex:none;color:var(--kf-color-text-muted)}.kf-dropdown-item__label{flex:1 1 auto}.kf-dropdown-item:hover{background:var(--kf-color-surface)}.kf-dropdown-item:focus-visible{outline:none;box-shadow:inset var(--kf-shadow-focus)}.kf-dropdown-item--error{color:var(--kf-color-error-strong)}.kf-dropdown-item--error kf-icon{color:var(--kf-color-error-strong)}.kf-dropdown-item--error:hover{background:var(--kf-color-error-soft)}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item{color:#ffffffd9}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item kf-icon{color:#ffffff8c}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item:hover{background:#ffffff14}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item--error{color:var(--kf-color-error)}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item--error kf-icon{color:var(--kf-color-error)}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item--error:hover{background:#bc454529}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }] });
|
|
2919
3548
|
}
|
|
2920
3549
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DropdownItem, decorators: [{
|
|
2921
3550
|
type: Component,
|
|
2922
|
-
args: [{ selector: 'kf-dropdown-item', template: `
|
|
3551
|
+
args: [{ selector: 'kf-dropdown-item', imports: [Icon], template: `
|
|
2923
3552
|
<button
|
|
2924
3553
|
type="button"
|
|
2925
3554
|
class="kf-dropdown-item"
|
|
2926
3555
|
[class.kf-dropdown-item--error]="tone() === 'error'"
|
|
2927
3556
|
role="menuitem"
|
|
2928
3557
|
>
|
|
2929
|
-
|
|
3558
|
+
@if (icon()) {
|
|
3559
|
+
<kf-icon [name]="icon()!" size="sm" />
|
|
3560
|
+
}
|
|
3561
|
+
<span class="kf-dropdown-item__label"><ng-content /></span>
|
|
2930
3562
|
</button>
|
|
2931
|
-
`, styles: [":host{display:block}.kf-dropdown-item{display:
|
|
2932
|
-
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
|
|
3563
|
+
`, styles: [":host{display:block}.kf-dropdown-item{display:flex;align-items:center;gap:var(--kf-space-sm);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;background:transparent;font:inherit;text-align:left;color:var(--kf-color-text);cursor:pointer}.kf-dropdown-item kf-icon{flex:none;color:var(--kf-color-text-muted)}.kf-dropdown-item__label{flex:1 1 auto}.kf-dropdown-item:hover{background:var(--kf-color-surface)}.kf-dropdown-item:focus-visible{outline:none;box-shadow:inset var(--kf-shadow-focus)}.kf-dropdown-item--error{color:var(--kf-color-error-strong)}.kf-dropdown-item--error kf-icon{color:var(--kf-color-error-strong)}.kf-dropdown-item--error:hover{background:var(--kf-color-error-soft)}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item{color:#ffffffd9}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item kf-icon{color:#ffffff8c}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item:hover{background:#ffffff14}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item--error{color:var(--kf-color-error)}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item--error kf-icon{color:var(--kf-color-error)}:host-context(.kf-dropdown-panel--dark) .kf-dropdown-item--error:hover{background:#bc454529}\n"] }]
|
|
3564
|
+
}], propDecorators: { tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
|
|
2933
3565
|
/**
|
|
2934
3566
|
* Contenido del panel flotante de un `kf-dropdown`. Es solo una plantilla
|
|
2935
|
-
* (`<ng-template
|
|
2936
|
-
*
|
|
2937
|
-
*
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
2940
|
-
*
|
|
2941
|
-
*
|
|
2942
|
-
*
|
|
2943
|
-
*
|
|
2944
|
-
*
|
|
3567
|
+
* (`<ng-template>`, capturada vía `TemplateRef`) — `Dropdown` la conecta al
|
|
3568
|
+
* overlay real cuando `open` pasa a `true`, no la renderiza en su propio
|
|
3569
|
+
* lugar del DOM. Esto permite que el panel aparezca pegado al host de
|
|
3570
|
+
* `Dropdown` sin importar dónde esté ese host en la página.
|
|
3571
|
+
*
|
|
3572
|
+
* `variant`: `'solid'` (default, fondo blanco) o `'dark'` (fondo sólido
|
|
3573
|
+
* oscuro, mismo tono que `[kf-drawer-header]`) para cuando el trigger vive
|
|
3574
|
+
* sobre una superficie oscura de marca.
|
|
3575
|
+
*
|
|
3576
|
+
* `dark` es sólida a propósito, no semitransparente: el CDK Overlay
|
|
3577
|
+
* reposiciona el panel según dónde entra en el viewport y puede terminar
|
|
3578
|
+
* cayendo sobre fondo blanco en vez de sobre el rail oscuro — un color
|
|
3579
|
+
* sólido se ve intencional sin importar dónde lo posicione el CDK.
|
|
2945
3580
|
*/
|
|
2946
3581
|
class DropdownPanel {
|
|
3582
|
+
variant = input('solid', /* @ts-ignore */
|
|
3583
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
2947
3584
|
templateRef = viewChild.required(TemplateRef, /* @ts-ignore */
|
|
2948
3585
|
...(ngDevMode ? [{ debugName: "templateRef" }] : /* istanbul ignore next */ []));
|
|
2949
3586
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DropdownPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2950
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: DropdownPanel, isStandalone: true, selector: "kf-dropdown-panel", viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
3587
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: DropdownPanel, isStandalone: true, selector: "kf-dropdown-panel", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
2951
3588
|
<ng-template>
|
|
2952
|
-
<div class="kf-dropdown-panel">
|
|
3589
|
+
<div class="kf-dropdown-panel" [class.kf-dropdown-panel--dark]="variant() === 'dark'">
|
|
2953
3590
|
<div class="kf-dropdown-panel__inner" role="menu">
|
|
2954
3591
|
<ng-content />
|
|
2955
3592
|
</div>
|
|
2956
3593
|
</div>
|
|
2957
3594
|
</ng-template>
|
|
2958
|
-
`, isInline: true, styles: [".kf-dropdown-panel{min-width:220px;max-width:
|
|
3595
|
+
`, isInline: true, styles: [".kf-dropdown-panel{min-width:220px;max-width:min(280px,90vw);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);animation:kf-dropdown-panel-enter .14s ease-out}.kf-dropdown-panel--dark{background:var(--kf-color-tertiary-deep);border-color:#ffffff24}.kf-dropdown-panel__inner{padding:var(--kf-space-xs) 0;overflow:hidden;border-radius:inherit}@media(prefers-reduced-motion:reduce){.kf-dropdown-panel{animation:none}}@keyframes kf-dropdown-panel-enter{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"] });
|
|
2959
3596
|
}
|
|
2960
3597
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DropdownPanel, decorators: [{
|
|
2961
3598
|
type: Component,
|
|
2962
3599
|
args: [{ selector: 'kf-dropdown-panel', template: `
|
|
2963
3600
|
<ng-template>
|
|
2964
|
-
<div class="kf-dropdown-panel">
|
|
3601
|
+
<div class="kf-dropdown-panel" [class.kf-dropdown-panel--dark]="variant() === 'dark'">
|
|
2965
3602
|
<div class="kf-dropdown-panel__inner" role="menu">
|
|
2966
3603
|
<ng-content />
|
|
2967
3604
|
</div>
|
|
2968
3605
|
</div>
|
|
2969
3606
|
</ng-template>
|
|
2970
|
-
`, styles: [".kf-dropdown-panel{min-width:220px;max-width:
|
|
2971
|
-
}], propDecorators: { templateRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
|
|
3607
|
+
`, styles: [".kf-dropdown-panel{min-width:220px;max-width:min(280px,90vw);background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-lg);font-family:var(--kf-font-body);animation:kf-dropdown-panel-enter .14s ease-out}.kf-dropdown-panel--dark{background:var(--kf-color-tertiary-deep);border-color:#ffffff24}.kf-dropdown-panel__inner{padding:var(--kf-space-xs) 0;overflow:hidden;border-radius:inherit}@media(prefers-reduced-motion:reduce){.kf-dropdown-panel{animation:none}}@keyframes kf-dropdown-panel-enter{0%{opacity:0;transform:translateY(-4px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}\n"] }]
|
|
3608
|
+
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], templateRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
|
|
2972
3609
|
/**
|
|
2973
3610
|
* Menú de acciones flotante, conectado a un elemento disparador vía
|
|
2974
3611
|
* `@angular/cdk/overlay` — la misma pieza que usa Angular Material para
|
|
2975
|
-
* `mat-menu`/`mat-select`.
|
|
2976
|
-
* mano en cada consumidor (ver el session-menu de `web-portal-latam-frontend`,
|
|
2977
|
-
* ~80 líneas de HTML/TS/SCSS para toggle + click-afuera + Escape + animación
|
|
2978
|
-
* de entrada, acopladas a una clase interna no pública de `PortalHeader`).
|
|
2979
|
-
*
|
|
2980
|
-
* Se apoya en el CDK en vez de un `position: absolute` casero (como sí hace
|
|
2981
|
-
* `SelectMenu`, que puede permitírselo porque su trigger vive siempre dentro
|
|
2982
|
-
* del propio componente) porque acá el disparador real puede vivir en OTRO
|
|
2983
|
-
* componente por completo — ej. el avatar dentro de `kf-portal-header` — y
|
|
2984
|
-
* el panel tiene que aparecer pegado a ESE elemento sin importar dónde el
|
|
2985
|
-
* consumidor ubique al `Dropdown` en su HTML. El Overlay CDK calcula esa
|
|
2986
|
-
* posición (con fallback si no entra en pantalla) en vez de que cada
|
|
2987
|
-
* consumidor lo recalcule a mano.
|
|
2988
|
-
*
|
|
2989
|
-
* `open` es two-way, igual que `Alert`/`Modal`: el consumidor decide qué
|
|
2990
|
-
* dispara la apertura (puede ser un `(click)` en un botón propio, o un
|
|
2991
|
-
* evento de otro componente como `PortalHeader.sessionClick`) — `Dropdown`
|
|
2992
|
-
* en sí no renderiza ningún trigger, su propio host es el punto de anclaje
|
|
2993
|
-
* visual del panel.
|
|
3612
|
+
* `mat-menu`/`mat-select`.
|
|
2994
3613
|
*
|
|
2995
|
-
*
|
|
2996
|
-
*
|
|
2997
|
-
*
|
|
2998
|
-
*
|
|
2999
|
-
*
|
|
3000
|
-
*
|
|
3614
|
+
* Se apoya en el CDK en vez de un `position: absolute` casero (a diferencia
|
|
3615
|
+
* de `SelectMenu`, que puede permitírselo porque su trigger vive siempre
|
|
3616
|
+
* dentro del propio componente) porque acá el disparador real puede vivir
|
|
3617
|
+
* en OTRO componente por completo — ej. el avatar dentro de
|
|
3618
|
+
* `kf-portal-header` — y el panel tiene que aparecer pegado a ESE elemento
|
|
3619
|
+
* sin importar dónde el consumidor ubique al `Dropdown` en su HTML.
|
|
3620
|
+
*
|
|
3621
|
+
* `open` es two-way: el consumidor decide qué dispara la apertura —
|
|
3622
|
+
* `Dropdown` en sí no renderiza ningún trigger, su propio host es el punto
|
|
3623
|
+
* de anclaje visual del panel.
|
|
3624
|
+
*
|
|
3625
|
+
* <kf-dropdown [(open)]="menuOpen"><kf-dropdown-panel>...</kf-dropdown-panel></kf-dropdown>
|
|
3001
3626
|
*/
|
|
3002
3627
|
class Dropdown {
|
|
3003
3628
|
/** Visibilidad del panel, two-way: `[(open)]="menuOpen"`. */
|
|
@@ -3005,17 +3630,10 @@ class Dropdown {
|
|
|
3005
3630
|
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
3006
3631
|
/**
|
|
3007
3632
|
* Elemento al que se conecta el overlay, si es distinto del propio host
|
|
3008
|
-
* de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es
|
|
3009
|
-
*
|
|
3010
|
-
*
|
|
3011
|
-
*
|
|
3012
|
-
* ensanchar el área de click a algo más grande que el botón (ej. el
|
|
3013
|
-
* avatar de sesión dentro de `PortalHeader`, que convive con logo y
|
|
3014
|
-
* navegación en el mismo host), `anchor` permite apuntar el overlay a la
|
|
3015
|
-
* referencia real de ese componente en vez de al host vacío de
|
|
3016
|
-
* `Dropdown`.
|
|
3017
|
-
*
|
|
3018
|
-
* <kf-dropdown [(open)]="menuOpen" [anchor]="headerRef()">
|
|
3633
|
+
* de `Dropdown`. Sin esto, `Dropdown` asume que ÉL MISMO es el
|
|
3634
|
+
* disparador. Útil cuando el disparador real vive dentro de OTRO
|
|
3635
|
+
* componente que no se puede envolver sin ensanchar el área de click
|
|
3636
|
+
* (ej. el avatar de sesión dentro de `PortalHeader`).
|
|
3019
3637
|
*/
|
|
3020
3638
|
anchor = input(/* @ts-ignore */
|
|
3021
3639
|
...(ngDevMode ? [undefined, { debugName: "anchor" }] : /* istanbul ignore next */ []));
|
|
@@ -3026,6 +3644,20 @@ class Dropdown {
|
|
|
3026
3644
|
viewContainerRef = inject(ViewContainerRef);
|
|
3027
3645
|
destroyRef = inject(DestroyRef);
|
|
3028
3646
|
overlayRef = null;
|
|
3647
|
+
/**
|
|
3648
|
+
* Suscripciones a `outsidePointerEvents()`/`keydownEvents()` del overlay
|
|
3649
|
+
* ACTUALMENTE adjunto. `getOrCreateOverlayRef()` reutiliza el mismo
|
|
3650
|
+
* `overlayRef` entre aperturas (no lo recrea cada vez) — sin esto,
|
|
3651
|
+
* `attach()` se resuscribía a esos observables en cada apertura sin
|
|
3652
|
+
* cancelar la suscripción de la vez anterior, acumulando N suscripciones
|
|
3653
|
+
* activas sobre el mismo overlay persistente. Eso causaba el bug real de
|
|
3654
|
+
* "clicks seguidos": al abrir de nuevo, el propio pointerdown que originó
|
|
3655
|
+
* la apertura llegaba a más de una suscripción acumulada, disparando
|
|
3656
|
+
* `open.set(false)` de forma errática en el mismo tick (patrón observado:
|
|
3657
|
+
* abre cada 2 clicks, cierra cada 1). Se cancelan explícitamente en
|
|
3658
|
+
* `detach()`, antes de la siguiente `attach()`.
|
|
3659
|
+
*/
|
|
3660
|
+
overlaySubscription = null;
|
|
3029
3661
|
constructor() {
|
|
3030
3662
|
effect(() => {
|
|
3031
3663
|
if (this.open()) {
|
|
@@ -3035,16 +3667,16 @@ class Dropdown {
|
|
|
3035
3667
|
this.detach();
|
|
3036
3668
|
}
|
|
3037
3669
|
});
|
|
3038
|
-
this.destroyRef.onDestroy(() =>
|
|
3670
|
+
this.destroyRef.onDestroy(() => {
|
|
3671
|
+
this.overlaySubscription?.unsubscribe();
|
|
3672
|
+
this.overlayRef?.dispose();
|
|
3673
|
+
});
|
|
3039
3674
|
}
|
|
3040
3675
|
/**
|
|
3041
3676
|
* Respaldo de `overlayRef.keydownEvents()`: ese observable del CDK solo
|
|
3042
|
-
* ve teclas
|
|
3043
|
-
*
|
|
3044
|
-
*
|
|
3045
|
-
* vive fuera del overlay), `keydownEvents()` nunca dispara. Este listener
|
|
3046
|
-
* a nivel de documento cubre ese caso; el de `keydownEvents()` sigue
|
|
3047
|
-
* activo para cuando el foco sí está dentro del panel.
|
|
3677
|
+
* ve teclas con el foco DENTRO del panel. Si el foco queda en el botón
|
|
3678
|
+
* disparador (caso común), `keydownEvents()` nunca dispara — este
|
|
3679
|
+
* listener a nivel de documento cubre ese caso.
|
|
3048
3680
|
*/
|
|
3049
3681
|
onDocumentEscape() {
|
|
3050
3682
|
this.open.set(false);
|
|
@@ -3057,14 +3689,38 @@ class Dropdown {
|
|
|
3057
3689
|
const overlayRef = this.getOrCreateOverlayRef();
|
|
3058
3690
|
const portal = new TemplatePortal(panel.templateRef(), this.viewContainerRef);
|
|
3059
3691
|
overlayRef.attach(portal);
|
|
3060
|
-
|
|
3061
|
-
overlayRef.
|
|
3692
|
+
this.overlaySubscription = new Subscription();
|
|
3693
|
+
this.overlaySubscription.add(overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
3694
|
+
if (this.isEventOnTrigger(event)) {
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3697
|
+
this.open.set(false);
|
|
3698
|
+
}));
|
|
3699
|
+
this.overlaySubscription.add(overlayRef.keydownEvents().subscribe((event) => {
|
|
3062
3700
|
if (event.key === 'Escape') {
|
|
3063
3701
|
this.open.set(false);
|
|
3064
3702
|
}
|
|
3065
|
-
});
|
|
3703
|
+
}));
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* El trigger real (el `anchor`, o el propio host si no se pasó ninguno —
|
|
3707
|
+
* ver comentario de la clase) vive FUERA del overlay a propósito: el
|
|
3708
|
+
* overlay recién se crea/adjunta cuando `open` pasa a `true`, así que el
|
|
3709
|
+
* CDK no tiene forma de saber que ese elemento externo "es parte" de su
|
|
3710
|
+
* disparador. Sin este filtro, el mismo `pointerdown` que abre el menú
|
|
3711
|
+
* (el trigger no es hijo del overlay) también llega a
|
|
3712
|
+
* `outsidePointerEvents()`, cerrando el panel en el mismo gesto que lo
|
|
3713
|
+
* abrió — bug real reproducido con Playwright: "abre y cierra" en
|
|
3714
|
+
* clicks consecutivos rápidos sobre la tarjeta de sesión del sidebar.
|
|
3715
|
+
*/
|
|
3716
|
+
isEventOnTrigger(event) {
|
|
3717
|
+
const trigger = this.anchor()?.nativeElement ?? this.host.nativeElement;
|
|
3718
|
+
const target = event.target;
|
|
3719
|
+
return target instanceof Node && trigger.contains(target);
|
|
3066
3720
|
}
|
|
3067
3721
|
detach() {
|
|
3722
|
+
this.overlaySubscription?.unsubscribe();
|
|
3723
|
+
this.overlaySubscription = null;
|
|
3068
3724
|
this.overlayRef?.detach();
|
|
3069
3725
|
}
|
|
3070
3726
|
getOrCreateOverlayRef() {
|
|
@@ -3074,34 +3730,23 @@ class Dropdown {
|
|
|
3074
3730
|
const positionStrategy = this.overlayService
|
|
3075
3731
|
.position()
|
|
3076
3732
|
.flexibleConnectedTo(this.anchor() ?? this.host)
|
|
3077
|
-
// 4 posiciones, no 2: además
|
|
3078
|
-
//
|
|
3079
|
-
//
|
|
3080
|
-
//
|
|
3081
|
-
// fallback horizontal: el panel se salía del viewport en vez de
|
|
3082
|
-
// reacomodarse hacia la izquierda. El CDK prueba cada posición en
|
|
3083
|
-
// orden y usa la primera que entra completa en el viewport.
|
|
3733
|
+
// 4 posiciones, no 2: además del par alineado al borde derecho del
|
|
3734
|
+
// anchor, agrega el mismo par alineado al borde IZQUIERDO — sin esto,
|
|
3735
|
+
// un trigger cerca del borde derecho de pantalla no tenía fallback
|
|
3736
|
+
// horizontal y el panel se salía del viewport.
|
|
3084
3737
|
.withPositions([
|
|
3085
3738
|
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
|
|
3086
3739
|
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
|
|
3087
3740
|
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
3088
3741
|
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
3089
3742
|
])
|
|
3090
|
-
// Margen de seguridad contra los bordes del viewport — sin esto, el
|
|
3091
|
-
// CDK puede considerar una posición "válida" aunque quede pegada al
|
|
3092
|
-
// borde con 0px de aire.
|
|
3093
3743
|
.withViewportMargin(8)
|
|
3094
|
-
// Si ninguna de las 4 posiciones entra completa
|
|
3095
|
-
//
|
|
3096
|
-
// de pantalla sin control.
|
|
3744
|
+
// Si ninguna de las 4 posiciones entra completa, la última se ajusta
|
|
3745
|
+
// con push en vez de desbordarse fuera de pantalla.
|
|
3097
3746
|
.withPush(true);
|
|
3098
3747
|
this.overlayRef = this.overlayService.create({
|
|
3099
3748
|
positionStrategy,
|
|
3100
3749
|
scrollStrategy: this.overlayService.scrollStrategies.reposition(),
|
|
3101
|
-
// Sin esto, el panel puede quedar más angosto que su propio
|
|
3102
|
-
// `min-width` en pantallas chicas si el CDK decide recortar — fija un
|
|
3103
|
-
// techo razonable en vez de dejarlo crecer sin límite ni encogerse
|
|
3104
|
-
// por debajo de lo legible.
|
|
3105
3750
|
maxWidth: '90vw',
|
|
3106
3751
|
});
|
|
3107
3752
|
return this.overlayRef;
|
|
@@ -3345,6 +3990,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3345
3990
|
`, styles: [":host{display:block}.kf-filter-chips{display:flex;flex-wrap:wrap;gap:var(--kf-space-xs)}.kf-filter-chips__chip{font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border-radius:var(--kf-radius-pill);border:1px solid var(--kf-color-border);background:var(--kf-color-surface);color:var(--kf-color-text);cursor:pointer;transition:background .14s,border-color .14s,color .14s}.kf-filter-chips__chip:hover:not(.kf-filter-chips__chip--active){border-color:var(--kf-color-border-strong)}.kf-filter-chips__chip:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-filter-chips__chip--active{background:var(--kf-color-ink);border-color:var(--kf-color-ink);color:var(--kf-color-white)}\n"] }]
|
|
3346
3991
|
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
3347
3992
|
|
|
3993
|
+
/**
|
|
3994
|
+
* Marca el contenido que `kf-folder-tabs` debe proyectar dentro de su
|
|
3995
|
+
* panel. Sin lógica propia — existe solo como token de tipo para que
|
|
3996
|
+
* `contentChild(FolderTabsPanel)` pueda detectar si el consumidor proyectó
|
|
3997
|
+
* algo (mismo mecanismo que `kf-dropdown` usa con `contentChild(DropdownPanel)`;
|
|
3998
|
+
* acá no hace falta un componente completo, alcanza con una directiva de
|
|
3999
|
+
* atributo porque el panel no necesita comportamiento propio, solo marcar
|
|
4000
|
+
* su presencia).
|
|
4001
|
+
*
|
|
4002
|
+
* <div kfFolderTabsPanel>...</div>
|
|
4003
|
+
*/
|
|
4004
|
+
class FolderTabsPanel {
|
|
4005
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FolderTabsPanel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4006
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: FolderTabsPanel, isStandalone: true, selector: "[kfFolderTabsPanel]", ngImport: i0 });
|
|
4007
|
+
}
|
|
4008
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FolderTabsPanel, decorators: [{
|
|
4009
|
+
type: Directive,
|
|
4010
|
+
args: [{ selector: '[kfFolderTabsPanel]' }]
|
|
4011
|
+
}] });
|
|
3348
4012
|
/**
|
|
3349
4013
|
* Tabs con forma de "carpeta": el borde superior queda redondeado y el borde
|
|
3350
4014
|
* inferior de la pestaña activa se funde visualmente con el panel de
|
|
@@ -3355,9 +4019,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3355
4019
|
* `--active` se mueve de un botón a otro, y eso vuelve a disparar el keyframe
|
|
3356
4020
|
* sin tocar JS.
|
|
3357
4021
|
*
|
|
3358
|
-
* El
|
|
3359
|
-
*
|
|
3360
|
-
*
|
|
4022
|
+
* El panel de contenido es un `<ng-content>` OPCIONAL — si el consumidor no
|
|
4023
|
+
* proyecta nada, `kf-folder-tabs` renderiza solo la fila de pestañas (mismo
|
|
4024
|
+
* comportamiento que antes de que existiera este slot, detectado con
|
|
4025
|
+
* `contentChild()` sin `.required`, igual patrón que usa `kf-dropdown` para
|
|
4026
|
+
* saber si hay un `kf-dropdown-panel` proyectado). Si SÍ proyecta contenido,
|
|
4027
|
+
* se envuelve en un panel con la esquina superior-izquierda recta (nace ahí
|
|
4028
|
+
* la primera pestaña, siempre pegada al borde izquierdo de la fila — un
|
|
4029
|
+
* radio ahí competiría visualmente con el propio borde redondeado de esa
|
|
4030
|
+
* pestaña) y las otras tres redondeadas. El `border-bottom` de 2px de la
|
|
4031
|
+
* pestaña activa (ver `.kf-folder-tabs__tab--active` más abajo) es la señal
|
|
4032
|
+
* visual de conexión con el panel para CUALQUIER pestaña activa, sin
|
|
4033
|
+
* necesitar mover ni medir nada.
|
|
4034
|
+
*
|
|
4035
|
+
* El borde izquierdo de toda la fila (y por lo tanto de la esquina recta
|
|
4036
|
+
* del panel) depende de que ninguna pestaña se corra al cambiar de estado
|
|
4037
|
+
* — ver `transform-origin: bottom left` en `.kf-folder-tabs__tab`: con el
|
|
4038
|
+
* origen centrado (como estaba antes), el `scale()` de la pestaña inactiva
|
|
4039
|
+
* encogía el borde izquierdo unos px, y ese corrimiento sub-pixel es lo que
|
|
4040
|
+
* hacía que este mismo diseño (esquina fija) se viera "roto" cuando la
|
|
4041
|
+
* pestaña activa no era la primera. Con el origen anclado a la izquierda,
|
|
4042
|
+
* el corrimiento desaparece y la esquina fija vuelve a ser una solución
|
|
4043
|
+
* válida y simple — sin necesitar medir la pestaña activa por JS.
|
|
4044
|
+
*
|
|
4045
|
+
* Antes de este slot, el patrón era que el consumidor pusiera su propia
|
|
4046
|
+
* card como HERMANO de `kf-folder-tabs` y anulara a mano el radio superior
|
|
4047
|
+
* completo (`border-top-left-radius: 0`, `border-top-right-radius: 0` — ver
|
|
4048
|
+
* el caso original de `web-portal-latam-frontend`). Encapsular esto acá
|
|
4049
|
+
* evita que cada consumidor repita esas reglas a mano.
|
|
4050
|
+
*
|
|
4051
|
+
* <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
|
|
4052
|
+
* <div kfFolderTabsPanel>contenido de la pestaña activa</div>
|
|
4053
|
+
* </kf-folder-tabs>
|
|
4054
|
+
*
|
|
4055
|
+
* <!-- o, sin contenido, como antes: -->
|
|
3361
4056
|
* <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event" />
|
|
3362
4057
|
*/
|
|
3363
4058
|
class FolderTabs {
|
|
@@ -3366,6 +4061,18 @@ class FolderTabs {
|
|
|
3366
4061
|
value = input.required(/* @ts-ignore */
|
|
3367
4062
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
3368
4063
|
valueChange = output();
|
|
4064
|
+
/**
|
|
4065
|
+
* Detecta si el consumidor proyectó contenido marcado con
|
|
4066
|
+
* `kfFolderTabsPanel` — sin `.required`, así que es `undefined`
|
|
4067
|
+
* cuando no hay nada proyectado (mismo patrón que usa `kf-dropdown` con
|
|
4068
|
+
* `contentChild(DropdownPanel)` para saber si hay un panel que
|
|
4069
|
+
* attachear). `hasPanel()` decide si el wrapper del panel participa del
|
|
4070
|
+
* layout (`display: contents` si no).
|
|
4071
|
+
*/
|
|
4072
|
+
projectedPanel = contentChild(FolderTabsPanel, /* @ts-ignore */
|
|
4073
|
+
...(ngDevMode ? [{ debugName: "projectedPanel" }] : /* istanbul ignore next */ []));
|
|
4074
|
+
hasPanel = computed(() => this.projectedPanel() !== undefined, /* @ts-ignore */
|
|
4075
|
+
...(ngDevMode ? [{ debugName: "hasPanel" }] : /* istanbul ignore next */ []));
|
|
3369
4076
|
select(value) {
|
|
3370
4077
|
if (value === this.value()) {
|
|
3371
4078
|
return;
|
|
@@ -3402,7 +4109,7 @@ class FolderTabs {
|
|
|
3402
4109
|
nextButton?.focus();
|
|
3403
4110
|
}
|
|
3404
4111
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FolderTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3405
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FolderTabs, isStandalone: true, selector: "kf-folder-tabs", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: `
|
|
4112
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FolderTabs, isStandalone: true, selector: "kf-folder-tabs", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { valueChange: "valueChange" }, queries: [{ propertyName: "projectedPanel", first: true, predicate: FolderTabsPanel, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
3406
4113
|
<div class="kf-folder-tabs" role="tablist">
|
|
3407
4114
|
@for (option of options(); track option.value) {
|
|
3408
4115
|
<button
|
|
@@ -3424,7 +4131,11 @@ class FolderTabs {
|
|
|
3424
4131
|
</button>
|
|
3425
4132
|
}
|
|
3426
4133
|
</div>
|
|
3427
|
-
|
|
4134
|
+
|
|
4135
|
+
<div class="kf-folder-tabs__panel" [class.kf-folder-tabs__panel--empty]="!hasPanel()">
|
|
4136
|
+
<ng-content select="[kfFolderTabsPanel]" />
|
|
4137
|
+
</div>
|
|
4138
|
+
`, isInline: true, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-folder-tabs{display:flex;align-items:flex-end;gap:var(--kf-space-xs);perspective:900px}.kf-folder-tabs__tab{position:relative;display:inline-flex;align-items:center;gap:var(--kf-space-xs);font:inherit;font-size:.9rem;color:var(--kf-color-text-muted);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-bottom-color:var(--kf-color-border-strong);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;padding:var(--kf-space-sm) var(--kf-space-md);cursor:pointer;transform-origin:bottom left;transform:translateY(5px) scale(.985);transition:background .14s,color .14s,border-color .14s,transform .22s cubic-bezier(.32,.72,0,1)}.kf-folder-tabs__tab:hover:not(.kf-folder-tabs__tab--active){color:var(--kf-color-text);background:var(--kf-color-white);transform:translateY(2px) scale(.99)}.kf-folder-tabs__tab:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus);z-index:1}.kf-folder-tabs__tab--active{z-index:1;color:var(--kf-color-primary);font-weight:700;background:var(--kf-color-white);border-color:var(--kf-color-border);border-bottom:2px solid var(--kf-color-primary);box-shadow:0 -6px 14px -10px #10182866;transform:translateY(1px);animation:kf-folder-tabs-pull .34s cubic-bezier(.22,1.2,.36,1) both}@keyframes kf-folder-tabs-pull{0%{transform:translateY(7px) rotateX(-10deg) scale(.97)}60%{transform:translateY(1px) rotateX(2deg) scale(1.01)}to{transform:translateY(1px) rotateX(0) scale(1)}}@media(prefers-reduced-motion:reduce){.kf-folder-tabs__tab,.kf-folder-tabs__tab--active{animation:none;transition:background .14s,color .14s,border-color .14s}}.kf-folder-tabs__panel--empty{display:contents}.kf-folder-tabs__panel:not(.kf-folder-tabs__panel--empty){background:var(--kf-color-white);border-right:1px solid var(--kf-color-border);border-bottom:1px solid var(--kf-color-border);border-left:1px solid var(--kf-color-border);border-top:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);border-top-left-radius:0;overflow:hidden}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }] });
|
|
3428
4139
|
}
|
|
3429
4140
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FolderTabs, decorators: [{
|
|
3430
4141
|
type: Component,
|
|
@@ -3450,8 +4161,75 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3450
4161
|
</button>
|
|
3451
4162
|
}
|
|
3452
4163
|
</div>
|
|
3453
|
-
|
|
3454
|
-
|
|
4164
|
+
|
|
4165
|
+
<div class="kf-folder-tabs__panel" [class.kf-folder-tabs__panel--empty]="!hasPanel()">
|
|
4166
|
+
<ng-content select="[kfFolderTabsPanel]" />
|
|
4167
|
+
</div>
|
|
4168
|
+
`, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-folder-tabs{display:flex;align-items:flex-end;gap:var(--kf-space-xs);perspective:900px}.kf-folder-tabs__tab{position:relative;display:inline-flex;align-items:center;gap:var(--kf-space-xs);font:inherit;font-size:.9rem;color:var(--kf-color-text-muted);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-bottom-color:var(--kf-color-border-strong);border-radius:var(--kf-radius-sm) var(--kf-radius-sm) 0 0;padding:var(--kf-space-sm) var(--kf-space-md);cursor:pointer;transform-origin:bottom left;transform:translateY(5px) scale(.985);transition:background .14s,color .14s,border-color .14s,transform .22s cubic-bezier(.32,.72,0,1)}.kf-folder-tabs__tab:hover:not(.kf-folder-tabs__tab--active){color:var(--kf-color-text);background:var(--kf-color-white);transform:translateY(2px) scale(.99)}.kf-folder-tabs__tab:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus);z-index:1}.kf-folder-tabs__tab--active{z-index:1;color:var(--kf-color-primary);font-weight:700;background:var(--kf-color-white);border-color:var(--kf-color-border);border-bottom:2px solid var(--kf-color-primary);box-shadow:0 -6px 14px -10px #10182866;transform:translateY(1px);animation:kf-folder-tabs-pull .34s cubic-bezier(.22,1.2,.36,1) both}@keyframes kf-folder-tabs-pull{0%{transform:translateY(7px) rotateX(-10deg) scale(.97)}60%{transform:translateY(1px) rotateX(2deg) scale(1.01)}to{transform:translateY(1px) rotateX(0) scale(1)}}@media(prefers-reduced-motion:reduce){.kf-folder-tabs__tab,.kf-folder-tabs__tab--active{animation:none;transition:background .14s,color .14s,border-color .14s}}.kf-folder-tabs__panel--empty{display:contents}.kf-folder-tabs__panel:not(.kf-folder-tabs__panel--empty){background:var(--kf-color-white);border-right:1px solid var(--kf-color-border);border-bottom:1px solid var(--kf-color-border);border-left:1px solid var(--kf-color-border);border-top:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);border-top-left-radius:0;overflow:hidden}\n"] }]
|
|
4169
|
+
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], projectedPanel: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FolderTabsPanel), { isSignal: true }] }] } });
|
|
4170
|
+
|
|
4171
|
+
/**
|
|
4172
|
+
* Pie de página general del portal (página 38): logo + copyright a la
|
|
4173
|
+
* izquierda, links legales/soporte empujados a la derecha. Logo y copyright
|
|
4174
|
+
* son inputs (cada portal del ecosistema — LATAM, OTD, Star — trae su
|
|
4175
|
+
* propia marca y texto legal); `links` es la lista de accesos, típicamente
|
|
4176
|
+
* placeholders hasta que existan destinos reales.
|
|
4177
|
+
*
|
|
4178
|
+
* <kf-footer
|
|
4179
|
+
* logoSrc="images/logo-kaufmann-dark.png"
|
|
4180
|
+
* logoAlt="Grupo Kaufmann"
|
|
4181
|
+
* copyright="© 2026 Kaufmann LATAM · Todos los derechos reservados"
|
|
4182
|
+
* [links]="[
|
|
4183
|
+
* { label: 'Políticas de privacidad' },
|
|
4184
|
+
* { label: 'Contacto' },
|
|
4185
|
+
* { label: 'Sugerencias y reclamos' },
|
|
4186
|
+
* ]"
|
|
4187
|
+
* />
|
|
4188
|
+
*/
|
|
4189
|
+
class Footer {
|
|
4190
|
+
logoSrc = input(/* @ts-ignore */
|
|
4191
|
+
...(ngDevMode ? [undefined, { debugName: "logoSrc" }] : /* istanbul ignore next */ []));
|
|
4192
|
+
logoAlt = input('', /* @ts-ignore */
|
|
4193
|
+
...(ngDevMode ? [{ debugName: "logoAlt" }] : /* istanbul ignore next */ []));
|
|
4194
|
+
copyright = input.required(/* @ts-ignore */
|
|
4195
|
+
...(ngDevMode ? [{ debugName: "copyright" }] : /* istanbul ignore next */ []));
|
|
4196
|
+
links = input([], /* @ts-ignore */
|
|
4197
|
+
...(ngDevMode ? [{ debugName: "links" }] : /* istanbul ignore next */ []));
|
|
4198
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Footer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4199
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Footer, isStandalone: true, selector: "kf-footer", inputs: { logoSrc: { classPropertyName: "logoSrc", publicName: "logoSrc", isSignal: true, isRequired: false, transformFunction: null }, logoAlt: { classPropertyName: "logoAlt", publicName: "logoAlt", isSignal: true, isRequired: false, transformFunction: null }, copyright: { classPropertyName: "copyright", publicName: "copyright", isSignal: true, isRequired: true, transformFunction: null }, links: { classPropertyName: "links", publicName: "links", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
4200
|
+
<div class="kf-footer__wrap">
|
|
4201
|
+
@if (logoSrc()) {
|
|
4202
|
+
<img class="kf-footer__logo" [src]="logoSrc()" [alt]="logoAlt()" />
|
|
4203
|
+
}
|
|
4204
|
+
<span class="kf-footer__copyright">{{ copyright() }}</span>
|
|
4205
|
+
@if (links().length) {
|
|
4206
|
+
<nav class="kf-footer__links">
|
|
4207
|
+
@for (link of links(); track link.label) {
|
|
4208
|
+
<a [href]="link.href || '#'">{{ link.label }}</a>
|
|
4209
|
+
}
|
|
4210
|
+
</nav>
|
|
4211
|
+
}
|
|
4212
|
+
</div>
|
|
4213
|
+
`, isInline: true, styles: [":host{display:block;margin-top:auto;background:var(--kf-color-tertiary-deep)}.kf-footer__wrap{max-width:var(--kf-footer-max-width, none);margin:0 auto;display:flex;align-items:center;flex-wrap:wrap;gap:var(--kf-space-lg);min-height:4.5rem;padding:var(--kf-space-md) var(--kf-space-lg)}.kf-footer__logo{height:1.375rem;opacity:.9}.kf-footer__copyright{font-size:.8125rem;color:#ffffff8c;font-family:var(--kf-font-body)}.kf-footer__links{margin-left:auto;display:flex;flex-wrap:wrap;gap:var(--kf-space-lg)}.kf-footer__links a{font-size:.8125rem;color:#ffffffa8;text-decoration:none;font-family:var(--kf-font-body)}.kf-footer__links a:hover{color:#fff}@media(max-width:640px){.kf-footer__wrap{flex-direction:column;align-items:flex-start}.kf-footer__links{margin-left:0}}\n"] });
|
|
4214
|
+
}
|
|
4215
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Footer, decorators: [{
|
|
4216
|
+
type: Component,
|
|
4217
|
+
args: [{ selector: 'kf-footer', template: `
|
|
4218
|
+
<div class="kf-footer__wrap">
|
|
4219
|
+
@if (logoSrc()) {
|
|
4220
|
+
<img class="kf-footer__logo" [src]="logoSrc()" [alt]="logoAlt()" />
|
|
4221
|
+
}
|
|
4222
|
+
<span class="kf-footer__copyright">{{ copyright() }}</span>
|
|
4223
|
+
@if (links().length) {
|
|
4224
|
+
<nav class="kf-footer__links">
|
|
4225
|
+
@for (link of links(); track link.label) {
|
|
4226
|
+
<a [href]="link.href || '#'">{{ link.label }}</a>
|
|
4227
|
+
}
|
|
4228
|
+
</nav>
|
|
4229
|
+
}
|
|
4230
|
+
</div>
|
|
4231
|
+
`, styles: [":host{display:block;margin-top:auto;background:var(--kf-color-tertiary-deep)}.kf-footer__wrap{max-width:var(--kf-footer-max-width, none);margin:0 auto;display:flex;align-items:center;flex-wrap:wrap;gap:var(--kf-space-lg);min-height:4.5rem;padding:var(--kf-space-md) var(--kf-space-lg)}.kf-footer__logo{height:1.375rem;opacity:.9}.kf-footer__copyright{font-size:.8125rem;color:#ffffff8c;font-family:var(--kf-font-body)}.kf-footer__links{margin-left:auto;display:flex;flex-wrap:wrap;gap:var(--kf-space-lg)}.kf-footer__links a{font-size:.8125rem;color:#ffffffa8;text-decoration:none;font-family:var(--kf-font-body)}.kf-footer__links a:hover{color:#fff}@media(max-width:640px){.kf-footer__wrap{flex-direction:column;align-items:flex-start}.kf-footer__links{margin-left:0}}\n"] }]
|
|
4232
|
+
}], propDecorators: { logoSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoSrc", required: false }] }], logoAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "logoAlt", required: false }] }], copyright: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyright", required: true }] }], links: [{ type: i0.Input, args: [{ isSignal: true, alias: "links", required: false }] }] } });
|
|
3455
4233
|
|
|
3456
4234
|
/**
|
|
3457
4235
|
* Input de formulario apilado: label arriba (en mayúsculas vía CSS), campo con
|
|
@@ -4003,18 +4781,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4003
4781
|
|
|
4004
4782
|
let nextId = 0;
|
|
4005
4783
|
/**
|
|
4006
|
-
* Dropdown de selección múltiple con checkboxes internos
|
|
4007
|
-
*
|
|
4008
|
-
*
|
|
4009
|
-
*
|
|
4010
|
-
*
|
|
4011
|
-
*
|
|
4012
|
-
*
|
|
4013
|
-
*
|
|
4014
|
-
*
|
|
4015
|
-
*
|
|
4016
|
-
*
|
|
4017
|
-
*
|
|
4784
|
+
* Dropdown de selección múltiple con checkboxes internos. El panel flotante
|
|
4785
|
+
* se conecta al trigger vía `@angular/cdk/overlay` — el mismo mecanismo que
|
|
4786
|
+
* `Select` (ver `../select/Select.ts`) y `Dropdown` (`../dropdown/Dropdown.ts`).
|
|
4787
|
+
*
|
|
4788
|
+
* Hasta la `0.1.9` este panel vivía con `position: absolute` manual sobre el
|
|
4789
|
+
* propio host, sin depender del CDK Overlay. Se migró para unificar el
|
|
4790
|
+
* mecanismo de posicionamiento/cierre con el resto de los dropdowns flotantes
|
|
4791
|
+
* de la librería (4 posiciones de fallback, `withViewportMargin`, `withPush`,
|
|
4792
|
+
* cierre por `outsidePointerEvents()`/`keydownEvents()` en vez de un
|
|
4793
|
+
* `@HostListener('document:click')` manual).
|
|
4794
|
+
*
|
|
4795
|
+
* A diferencia de `Select`, el disparador no muestra un único valor sino los
|
|
4796
|
+
* nombres seleccionados junto a un badge circular con el conteo, y cada
|
|
4797
|
+
* opción del panel se marca/desmarca con un checkbox sin cerrar el panel.
|
|
4798
|
+
* Incluye una opción "Todas" al inicio de la lista, con estado
|
|
4799
|
+
* `indeterminate` cuando hay selección parcial — el mismo patrón de
|
|
4800
|
+
* "seleccionar todos" de `Checkbox`. "Todas" nunca se filtra por el buscador:
|
|
4801
|
+
* queda siempre visible y sigue actuando sobre el universo COMPLETO de
|
|
4802
|
+
* `options()`, no sobre el subconjunto filtrado (ver JSDoc de `toggleAll()`).
|
|
4018
4803
|
*
|
|
4019
4804
|
* <kf-multi-select
|
|
4020
4805
|
* label="Sucursales"
|
|
@@ -4037,18 +4822,43 @@ class MultiSelect {
|
|
|
4037
4822
|
placeholder = input('Seleccionar', /* @ts-ignore */
|
|
4038
4823
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
4039
4824
|
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4825
|
+
/**
|
|
4826
|
+
* Tri-state: si el consumidor no lo especifica (`undefined`), el buscador
|
|
4827
|
+
* se activa solo cuando `options().length` supera `searchThreshold()`. Si
|
|
4828
|
+
* el consumidor define `true`/`false` explícitamente, ese valor manda por
|
|
4829
|
+
* sobre el umbral automático. Mismo criterio que `Select`.
|
|
4830
|
+
*/
|
|
4831
|
+
searchable = input(undefined, /* @ts-ignore */
|
|
4832
|
+
...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
|
|
4833
|
+
searchThreshold = input(7, /* @ts-ignore */
|
|
4834
|
+
...(ngDevMode ? [{ debugName: "searchThreshold" }] : /* istanbul ignore next */ []));
|
|
4040
4835
|
valuesChange = output();
|
|
4041
4836
|
instanceId = `kf-multi-select-${nextId++}`;
|
|
4042
4837
|
triggerId = `${this.instanceId}-trigger`;
|
|
4043
4838
|
listboxId = `${this.instanceId}-listbox`;
|
|
4044
4839
|
isOpen = signal(false, /* @ts-ignore */
|
|
4045
4840
|
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
4841
|
+
searchQuery = signal('', /* @ts-ignore */
|
|
4842
|
+
...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
4843
|
+
activeIndex = signal(0, /* @ts-ignore */
|
|
4844
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
4046
4845
|
selectedCount = computed(() => this.values().length, /* @ts-ignore */
|
|
4047
4846
|
...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
|
|
4048
4847
|
allSelected = computed(() => this.options().length > 0 && this.values().length === this.options().length, /* @ts-ignore */
|
|
4049
4848
|
...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
|
|
4050
4849
|
isPartiallySelected = computed(() => this.values().length > 0 && !this.allSelected(), /* @ts-ignore */
|
|
4051
4850
|
...(ngDevMode ? [{ debugName: "isPartiallySelected" }] : /* istanbul ignore next */ []));
|
|
4851
|
+
showSearch = computed(() => this.searchable() ?? this.options().length > this.searchThreshold(), /* @ts-ignore */
|
|
4852
|
+
...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
|
|
4853
|
+
/** Opciones visibles tras aplicar el filtro de búsqueda ("Todas" nunca se filtra). */
|
|
4854
|
+
filteredOptions = computed(() => {
|
|
4855
|
+
const query = normalizeForSearch(this.searchQuery().trim());
|
|
4856
|
+
if (!query) {
|
|
4857
|
+
return this.options();
|
|
4858
|
+
}
|
|
4859
|
+
return this.options().filter((option) => normalizeForSearch(option.label).includes(query));
|
|
4860
|
+
}, /* @ts-ignore */
|
|
4861
|
+
...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
|
|
4052
4862
|
/**
|
|
4053
4863
|
* Texto del disparador: placeholder si no hay selección, o los nombres
|
|
4054
4864
|
* seleccionados unidos por coma (el conteo va aparte, en el badge). Con
|
|
@@ -4066,37 +4876,147 @@ class MultiSelect {
|
|
|
4066
4876
|
return selectedLabels.join(', ');
|
|
4067
4877
|
}, /* @ts-ignore */
|
|
4068
4878
|
...(ngDevMode ? [{ debugName: "summaryText" }] : /* istanbul ignore next */ []));
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
}
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4879
|
+
triggerRef = viewChild.required('trigger', /* @ts-ignore */
|
|
4880
|
+
...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
|
|
4881
|
+
panelTemplate = viewChild.required('panelTemplate', /* @ts-ignore */
|
|
4882
|
+
...(ngDevMode ? [{ debugName: "panelTemplate" }] : /* istanbul ignore next */ []));
|
|
4883
|
+
searchInputRef = viewChild('searchInput', /* @ts-ignore */
|
|
4884
|
+
...(ngDevMode ? [{ debugName: "searchInputRef" }] : /* istanbul ignore next */ []));
|
|
4885
|
+
overlayService = inject(Overlay);
|
|
4886
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
4887
|
+
destroyRef = inject(DestroyRef);
|
|
4888
|
+
overlayRef = null;
|
|
4889
|
+
constructor() {
|
|
4890
|
+
effect(() => {
|
|
4891
|
+
if (this.isOpen()) {
|
|
4892
|
+
this.attach();
|
|
4893
|
+
}
|
|
4894
|
+
else {
|
|
4895
|
+
this.detach();
|
|
4896
|
+
this.searchQuery.set('');
|
|
4897
|
+
this.activeIndex.set(0);
|
|
4898
|
+
}
|
|
4899
|
+
});
|
|
4900
|
+
// Autofocus del buscador al abrir — mismo patrón que Select. afterNextRender()
|
|
4901
|
+
// no puede invocarse dentro de un effect() (NG0203); afterRenderEffect() sí
|
|
4902
|
+
// es reactivo a signals y corre tras la actualización del DOM del panel.
|
|
4903
|
+
afterRenderEffect(() => {
|
|
4904
|
+
if (this.isOpen() && this.showSearch()) {
|
|
4905
|
+
this.searchInputRef()?.nativeElement.focus();
|
|
4906
|
+
}
|
|
4907
|
+
});
|
|
4908
|
+
this.destroyRef.onDestroy(() => this.overlayRef?.dispose());
|
|
4909
|
+
}
|
|
4910
|
+
isSelected(value) {
|
|
4911
|
+
return this.values().includes(value);
|
|
4912
|
+
}
|
|
4913
|
+
toggle() {
|
|
4914
|
+
if (this.disabled()) {
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4917
|
+
this.isOpen.update((open) => !open);
|
|
4918
|
+
}
|
|
4919
|
+
toggleOption(value) {
|
|
4920
|
+
const current = this.values();
|
|
4921
|
+
const next = current.includes(value)
|
|
4922
|
+
? current.filter((selectedValue) => selectedValue !== value)
|
|
4923
|
+
: [...current, value];
|
|
4924
|
+
this.valuesChange.emit(next);
|
|
4925
|
+
}
|
|
4926
|
+
/**
|
|
4927
|
+
* Alterna entre "todas seleccionadas" y "ninguna seleccionada". Actúa
|
|
4928
|
+
* siempre sobre el universo COMPLETO de `options()`, no sobre
|
|
4929
|
+
* `filteredOptions()` — con un buscador activo, "Todas" no se filtra ni
|
|
4930
|
+
* cambia de significado: seguir marcando/desmarcando únicamente lo visible
|
|
4931
|
+
* sería sorprendente para quien ya tenía otras opciones (no visibles en el
|
|
4932
|
+
* filtro actual) seleccionadas de antes.
|
|
4933
|
+
*/
|
|
4087
4934
|
toggleAll() {
|
|
4088
4935
|
this.valuesChange.emit(this.allSelected() ? [] : this.options().map((option) => option.value));
|
|
4089
4936
|
}
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4937
|
+
onSearchInput(event) {
|
|
4938
|
+
this.searchQuery.set(event.target.value);
|
|
4939
|
+
this.activeIndex.set(0);
|
|
4940
|
+
}
|
|
4941
|
+
/** Mueve `activeIndex` sobre las opciones YA FILTRADAS visibles. Clamp simple, sin wraparound. */
|
|
4942
|
+
moveActive(delta) {
|
|
4943
|
+
const count = this.filteredOptions().length;
|
|
4944
|
+
if (count === 0) {
|
|
4945
|
+
return;
|
|
4946
|
+
}
|
|
4947
|
+
this.activeIndex.update((current) => Math.min(Math.max(current + delta, 0), count - 1));
|
|
4948
|
+
}
|
|
4949
|
+
toggleActive() {
|
|
4950
|
+
const option = this.filteredOptions()[this.activeIndex()];
|
|
4951
|
+
if (option) {
|
|
4952
|
+
this.toggleOption(option.value);
|
|
4093
4953
|
}
|
|
4094
4954
|
}
|
|
4095
4955
|
onEscape() {
|
|
4096
4956
|
this.isOpen.set(false);
|
|
4097
4957
|
}
|
|
4958
|
+
attach() {
|
|
4959
|
+
if (this.overlayRef?.hasAttached()) {
|
|
4960
|
+
return;
|
|
4961
|
+
}
|
|
4962
|
+
const overlayRef = this.getOrCreateOverlayRef();
|
|
4963
|
+
// Se remide el ancho del trigger en cada apertura, no solo la primera —
|
|
4964
|
+
// ver la misma nota en Select: cachear el ancho en la creación del
|
|
4965
|
+
// overlay lo deja desincronizado si el trigger cambia de tamaño entre
|
|
4966
|
+
// una apertura y la siguiente.
|
|
4967
|
+
overlayRef.updateSize({ width: this.triggerRef().nativeElement.getBoundingClientRect().width });
|
|
4968
|
+
const portal = new TemplatePortal(this.panelTemplate(), this.viewContainerRef);
|
|
4969
|
+
overlayRef.attach(portal);
|
|
4970
|
+
// outsidePointerEvents() dispara en la fase de CAPTURA de `click` sobre
|
|
4971
|
+
// `body` (ver OverlayOutsideClickDispatcher en @angular/cdk), ANTES de
|
|
4972
|
+
// que el evento llegue al trigger y corra (click)="toggle()". Sin este
|
|
4973
|
+
// filtro, un click en el trigger para CERRAR el panel ya abierto hacía:
|
|
4974
|
+
// 1) este subscribe lo cierra primero (el trigger no es parte del
|
|
4975
|
+
// overlay, cuenta como "afuera"), 2) toggle() corre después y lo vuelve a
|
|
4976
|
+
// abrir porque ve isOpen ya en false — un solo click quedaba en no-op y
|
|
4977
|
+
// solo cerraba de verdad un click fuera del trigger. Mismo fix que Select
|
|
4978
|
+
// y Dropdown (ver notas ahí).
|
|
4979
|
+
overlayRef.outsidePointerEvents().subscribe((event) => {
|
|
4980
|
+
const target = event.target;
|
|
4981
|
+
if (target && this.triggerRef().nativeElement.contains(target)) {
|
|
4982
|
+
return;
|
|
4983
|
+
}
|
|
4984
|
+
this.isOpen.set(false);
|
|
4985
|
+
});
|
|
4986
|
+
overlayRef.keydownEvents().subscribe((event) => {
|
|
4987
|
+
if (event.key === 'Escape') {
|
|
4988
|
+
this.isOpen.set(false);
|
|
4989
|
+
}
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
detach() {
|
|
4993
|
+
this.overlayRef?.detach();
|
|
4994
|
+
}
|
|
4995
|
+
getOrCreateOverlayRef() {
|
|
4996
|
+
if (this.overlayRef) {
|
|
4997
|
+
return this.overlayRef;
|
|
4998
|
+
}
|
|
4999
|
+
const positionStrategy = this.overlayService
|
|
5000
|
+
.position()
|
|
5001
|
+
.flexibleConnectedTo(this.triggerRef())
|
|
5002
|
+
.withPositions([
|
|
5003
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
5004
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
5005
|
+
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
|
|
5006
|
+
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
|
|
5007
|
+
])
|
|
5008
|
+
.withViewportMargin(8)
|
|
5009
|
+
.withPush(true);
|
|
5010
|
+
this.overlayRef = this.overlayService.create({
|
|
5011
|
+
positionStrategy,
|
|
5012
|
+
scrollStrategy: this.overlayService.scrollStrategies.reposition(),
|
|
5013
|
+
width: this.triggerRef().nativeElement.getBoundingClientRect().width,
|
|
5014
|
+
maxWidth: '90vw',
|
|
5015
|
+
});
|
|
5016
|
+
return this.overlayRef;
|
|
5017
|
+
}
|
|
4098
5018
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4099
|
-
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 } }, outputs: { valuesChange: "valuesChange" }, host: { listeners: { "document:
|
|
5019
|
+
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 }, 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()" } }, 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: `
|
|
4100
5020
|
@if (label()) {
|
|
4101
5021
|
<kf-field-label [for]="triggerId">{{ label() }}</kf-field-label>
|
|
4102
5022
|
}
|
|
@@ -4126,55 +5046,78 @@ class MultiSelect {
|
|
|
4126
5046
|
aria-hidden="true"
|
|
4127
5047
|
></span>
|
|
4128
5048
|
</button>
|
|
4129
|
-
|
|
4130
|
-
<
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
(click)="toggleAll()"
|
|
4144
|
-
(keydown.enter)="toggleAll()"
|
|
4145
|
-
(keydown.space)="$event.preventDefault(); toggleAll()"
|
|
4146
|
-
>
|
|
4147
|
-
<span
|
|
4148
|
-
class="kf-multi-select__checkbox"
|
|
4149
|
-
[class.kf-multi-select__checkbox--checked]="allSelected()"
|
|
4150
|
-
[class.kf-multi-select__checkbox--indeterminate]="isPartiallySelected()"
|
|
4151
|
-
></span>
|
|
4152
|
-
<span class="kf-multi-select__option-label">Todas</span>
|
|
4153
|
-
</li>
|
|
4154
|
-
}
|
|
4155
|
-
@for (option of options(); track option.value) {
|
|
4156
|
-
<li
|
|
4157
|
-
class="kf-multi-select__option"
|
|
4158
|
-
[class.kf-multi-select__option--selected]="isSelected(option.value)"
|
|
4159
|
-
role="option"
|
|
4160
|
-
tabindex="0"
|
|
4161
|
-
[attr.aria-selected]="isSelected(option.value)"
|
|
4162
|
-
(click)="toggleOption(option.value)"
|
|
4163
|
-
(keydown.enter)="toggleOption(option.value)"
|
|
4164
|
-
(keydown.space)="$event.preventDefault(); toggleOption(option.value)"
|
|
4165
|
-
>
|
|
4166
|
-
<span
|
|
4167
|
-
class="kf-multi-select__checkbox"
|
|
4168
|
-
[class.kf-multi-select__checkbox--checked]="isSelected(option.value)"
|
|
4169
|
-
></span>
|
|
4170
|
-
<span class="kf-multi-select__option-label">{{ option.label }}</span>
|
|
4171
|
-
</li>
|
|
4172
|
-
} @empty {
|
|
4173
|
-
<li class="kf-multi-select__empty">Sin opciones disponibles</li>
|
|
5049
|
+
<ng-template #panelTemplate>
|
|
5050
|
+
<div class="kf-multi-select__panel">
|
|
5051
|
+
@if (showSearch()) {
|
|
5052
|
+
<input
|
|
5053
|
+
#searchInput
|
|
5054
|
+
type="text"
|
|
5055
|
+
class="kf-multi-select__search"
|
|
5056
|
+
placeholder="Buscar…"
|
|
5057
|
+
[value]="searchQuery()"
|
|
5058
|
+
(input)="onSearchInput($event)"
|
|
5059
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
5060
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
5061
|
+
(keydown.enter)="$event.preventDefault(); toggleActive()"
|
|
5062
|
+
/>
|
|
4174
5063
|
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
5064
|
+
<ul
|
|
5065
|
+
class="kf-multi-select__list"
|
|
5066
|
+
[id]="listboxId"
|
|
5067
|
+
role="listbox"
|
|
5068
|
+
aria-multiselectable="true"
|
|
5069
|
+
[attr.aria-labelledby]="triggerId"
|
|
5070
|
+
>
|
|
5071
|
+
@if (options().length > 0) {
|
|
5072
|
+
<li
|
|
5073
|
+
class="kf-multi-select__option"
|
|
5074
|
+
role="option"
|
|
5075
|
+
tabindex="0"
|
|
5076
|
+
[attr.aria-selected]="allSelected()"
|
|
5077
|
+
(click)="toggleAll()"
|
|
5078
|
+
(keydown.enter)="toggleAll()"
|
|
5079
|
+
(keydown.space)="$event.preventDefault(); toggleAll()"
|
|
5080
|
+
>
|
|
5081
|
+
<span
|
|
5082
|
+
class="kf-multi-select__checkbox"
|
|
5083
|
+
[class.kf-multi-select__checkbox--checked]="allSelected()"
|
|
5084
|
+
[class.kf-multi-select__checkbox--indeterminate]="isPartiallySelected()"
|
|
5085
|
+
></span>
|
|
5086
|
+
<span class="kf-multi-select__option-label">Todas</span>
|
|
5087
|
+
</li>
|
|
5088
|
+
}
|
|
5089
|
+
@for (option of filteredOptions(); track option.value; let i = $index) {
|
|
5090
|
+
<li
|
|
5091
|
+
class="kf-multi-select__option"
|
|
5092
|
+
[class.kf-multi-select__option--selected]="isSelected(option.value)"
|
|
5093
|
+
[class.kf-multi-select__option--active]="i === activeIndex()"
|
|
5094
|
+
role="option"
|
|
5095
|
+
tabindex="0"
|
|
5096
|
+
[attr.aria-selected]="isSelected(option.value)"
|
|
5097
|
+
(click)="toggleOption(option.value)"
|
|
5098
|
+
(mouseenter)="activeIndex.set(i)"
|
|
5099
|
+
(keydown.enter)="toggleOption(option.value)"
|
|
5100
|
+
(keydown.space)="$event.preventDefault(); toggleOption(option.value)"
|
|
5101
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
5102
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
5103
|
+
>
|
|
5104
|
+
<span
|
|
5105
|
+
class="kf-multi-select__checkbox"
|
|
5106
|
+
[class.kf-multi-select__checkbox--checked]="isSelected(option.value)"
|
|
5107
|
+
></span>
|
|
5108
|
+
<span class="kf-multi-select__option-label">{{ option.label }}</span>
|
|
5109
|
+
</li>
|
|
5110
|
+
} @empty {
|
|
5111
|
+
@if (options().length > 0) {
|
|
5112
|
+
<li class="kf-multi-select__empty">Sin resultados</li>
|
|
5113
|
+
} @else {
|
|
5114
|
+
<li class="kf-multi-select__empty">Sin opciones disponibles</li>
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
</ul>
|
|
5118
|
+
</div>
|
|
5119
|
+
</ng-template>
|
|
5120
|
+
`, isInline: true, styles: [":host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:240px}.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__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-multi-select__caret--open{transform:rotate(-135deg);margin-top:4px}.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;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-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:4px;height:8px;border:solid var(--kf-color-white);border-width:0 2px 2px 0;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:8px;height:2px;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"] }] });
|
|
4178
5121
|
}
|
|
4179
5122
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, decorators: [{
|
|
4180
5123
|
type: Component,
|
|
@@ -4208,63 +5151,191 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4208
5151
|
aria-hidden="true"
|
|
4209
5152
|
></span>
|
|
4210
5153
|
</button>
|
|
4211
|
-
|
|
4212
|
-
<
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
(click)="toggleAll()"
|
|
4226
|
-
(keydown.enter)="toggleAll()"
|
|
4227
|
-
(keydown.space)="$event.preventDefault(); toggleAll()"
|
|
4228
|
-
>
|
|
4229
|
-
<span
|
|
4230
|
-
class="kf-multi-select__checkbox"
|
|
4231
|
-
[class.kf-multi-select__checkbox--checked]="allSelected()"
|
|
4232
|
-
[class.kf-multi-select__checkbox--indeterminate]="isPartiallySelected()"
|
|
4233
|
-
></span>
|
|
4234
|
-
<span class="kf-multi-select__option-label">Todas</span>
|
|
4235
|
-
</li>
|
|
4236
|
-
}
|
|
4237
|
-
@for (option of options(); track option.value) {
|
|
4238
|
-
<li
|
|
4239
|
-
class="kf-multi-select__option"
|
|
4240
|
-
[class.kf-multi-select__option--selected]="isSelected(option.value)"
|
|
4241
|
-
role="option"
|
|
4242
|
-
tabindex="0"
|
|
4243
|
-
[attr.aria-selected]="isSelected(option.value)"
|
|
4244
|
-
(click)="toggleOption(option.value)"
|
|
4245
|
-
(keydown.enter)="toggleOption(option.value)"
|
|
4246
|
-
(keydown.space)="$event.preventDefault(); toggleOption(option.value)"
|
|
4247
|
-
>
|
|
4248
|
-
<span
|
|
4249
|
-
class="kf-multi-select__checkbox"
|
|
4250
|
-
[class.kf-multi-select__checkbox--checked]="isSelected(option.value)"
|
|
4251
|
-
></span>
|
|
4252
|
-
<span class="kf-multi-select__option-label">{{ option.label }}</span>
|
|
4253
|
-
</li>
|
|
4254
|
-
} @empty {
|
|
4255
|
-
<li class="kf-multi-select__empty">Sin opciones disponibles</li>
|
|
5154
|
+
<ng-template #panelTemplate>
|
|
5155
|
+
<div class="kf-multi-select__panel">
|
|
5156
|
+
@if (showSearch()) {
|
|
5157
|
+
<input
|
|
5158
|
+
#searchInput
|
|
5159
|
+
type="text"
|
|
5160
|
+
class="kf-multi-select__search"
|
|
5161
|
+
placeholder="Buscar…"
|
|
5162
|
+
[value]="searchQuery()"
|
|
5163
|
+
(input)="onSearchInput($event)"
|
|
5164
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
5165
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
5166
|
+
(keydown.enter)="$event.preventDefault(); toggleActive()"
|
|
5167
|
+
/>
|
|
4256
5168
|
}
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
5169
|
+
<ul
|
|
5170
|
+
class="kf-multi-select__list"
|
|
5171
|
+
[id]="listboxId"
|
|
5172
|
+
role="listbox"
|
|
5173
|
+
aria-multiselectable="true"
|
|
5174
|
+
[attr.aria-labelledby]="triggerId"
|
|
5175
|
+
>
|
|
5176
|
+
@if (options().length > 0) {
|
|
5177
|
+
<li
|
|
5178
|
+
class="kf-multi-select__option"
|
|
5179
|
+
role="option"
|
|
5180
|
+
tabindex="0"
|
|
5181
|
+
[attr.aria-selected]="allSelected()"
|
|
5182
|
+
(click)="toggleAll()"
|
|
5183
|
+
(keydown.enter)="toggleAll()"
|
|
5184
|
+
(keydown.space)="$event.preventDefault(); toggleAll()"
|
|
5185
|
+
>
|
|
5186
|
+
<span
|
|
5187
|
+
class="kf-multi-select__checkbox"
|
|
5188
|
+
[class.kf-multi-select__checkbox--checked]="allSelected()"
|
|
5189
|
+
[class.kf-multi-select__checkbox--indeterminate]="isPartiallySelected()"
|
|
5190
|
+
></span>
|
|
5191
|
+
<span class="kf-multi-select__option-label">Todas</span>
|
|
5192
|
+
</li>
|
|
5193
|
+
}
|
|
5194
|
+
@for (option of filteredOptions(); track option.value; let i = $index) {
|
|
5195
|
+
<li
|
|
5196
|
+
class="kf-multi-select__option"
|
|
5197
|
+
[class.kf-multi-select__option--selected]="isSelected(option.value)"
|
|
5198
|
+
[class.kf-multi-select__option--active]="i === activeIndex()"
|
|
5199
|
+
role="option"
|
|
5200
|
+
tabindex="0"
|
|
5201
|
+
[attr.aria-selected]="isSelected(option.value)"
|
|
5202
|
+
(click)="toggleOption(option.value)"
|
|
5203
|
+
(mouseenter)="activeIndex.set(i)"
|
|
5204
|
+
(keydown.enter)="toggleOption(option.value)"
|
|
5205
|
+
(keydown.space)="$event.preventDefault(); toggleOption(option.value)"
|
|
5206
|
+
(keydown.arrowdown)="$event.preventDefault(); moveActive(1)"
|
|
5207
|
+
(keydown.arrowup)="$event.preventDefault(); moveActive(-1)"
|
|
5208
|
+
>
|
|
5209
|
+
<span
|
|
5210
|
+
class="kf-multi-select__checkbox"
|
|
5211
|
+
[class.kf-multi-select__checkbox--checked]="isSelected(option.value)"
|
|
5212
|
+
></span>
|
|
5213
|
+
<span class="kf-multi-select__option-label">{{ option.label }}</span>
|
|
5214
|
+
</li>
|
|
5215
|
+
} @empty {
|
|
5216
|
+
@if (options().length > 0) {
|
|
5217
|
+
<li class="kf-multi-select__empty">Sin resultados</li>
|
|
5218
|
+
} @else {
|
|
5219
|
+
<li class="kf-multi-select__empty">Sin opciones disponibles</li>
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
</ul>
|
|
5223
|
+
</div>
|
|
5224
|
+
</ng-template>
|
|
5225
|
+
`, styles: [":host{display:inline-flex;flex-direction:column;gap:var(--kf-space-xs);font:inherit;width:240px}.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__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-multi-select__caret--open{transform:rotate(-135deg);margin-top:4px}.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;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-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:4px;height:8px;border:solid var(--kf-color-white);border-width:0 2px 2px 0;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:8px;height:2px;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"] }]
|
|
5226
|
+
}], 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 }] }], 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 }] }], onEscape: [{
|
|
4264
5227
|
type: HostListener,
|
|
4265
5228
|
args: ['document:keydown.escape']
|
|
4266
5229
|
}] } });
|
|
4267
5230
|
|
|
5231
|
+
/**
|
|
5232
|
+
* Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
|
|
5233
|
+
* texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
|
|
5234
|
+
* ve). Antes de este componente, cada pantalla que consume la librería
|
|
5235
|
+
* definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
|
|
5236
|
+
* (título de página, subtítulo, texto secundario) — auditoría sobre
|
|
5237
|
+
* `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
|
|
5238
|
+
* home sin ningún paso de escala en común. `variant` resuelve eso contra
|
|
5239
|
+
* la escala de `tokens/base.css` (`--kf-font-size-*`).
|
|
5240
|
+
*
|
|
5241
|
+
* `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
|
|
5242
|
+
* escape hatch de color libre. Si un caso necesita un color que `tone` no
|
|
5243
|
+
* cubre, es una discusión de design system, no una prop nueva acá.
|
|
5244
|
+
*
|
|
5245
|
+
* `title-xl`/`title-xxl` son un tier aparte de `title-lg|md|sm`: no vienen
|
|
5246
|
+
* de la auditoría de contenido de página (esa cerró en 2.125rem), son para
|
|
5247
|
+
* títulos protagonistas de pantalla completa (ej. bienvenida del home vía
|
|
5248
|
+
* `kf-page-header[size=xl|xxl]`) — ver tokens/base.css.
|
|
5249
|
+
*
|
|
5250
|
+
* <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
|
|
5251
|
+
* <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
|
|
5252
|
+
* <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
|
|
5253
|
+
*/
|
|
5254
|
+
class Text {
|
|
5255
|
+
/** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
|
|
5256
|
+
as = input('p', /* @ts-ignore */
|
|
5257
|
+
...(ngDevMode ? [{ debugName: "as" }] : /* istanbul ignore next */ []));
|
|
5258
|
+
variant = input('body', /* @ts-ignore */
|
|
5259
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
5260
|
+
tone = input('neutral', /* @ts-ignore */
|
|
5261
|
+
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
5262
|
+
/**
|
|
5263
|
+
* Copia de `as` para uso dentro del template: `as` es palabra reservada
|
|
5264
|
+
* en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
|
|
5265
|
+
* no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
|
|
5266
|
+
* atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
|
|
5267
|
+
*/
|
|
5268
|
+
tag = computed(() => this.as(), /* @ts-ignore */
|
|
5269
|
+
...(ngDevMode ? [{ debugName: "tag" }] : /* istanbul ignore next */ []));
|
|
5270
|
+
hostClass() {
|
|
5271
|
+
const classes = [`kf-text--${this.variant()}`];
|
|
5272
|
+
if (this.tone() !== 'neutral') {
|
|
5273
|
+
classes.push(`kf-text--${this.tone()}`);
|
|
5274
|
+
}
|
|
5275
|
+
return classes.join(' ');
|
|
5276
|
+
}
|
|
5277
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Text, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5278
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Text, isStandalone: true, selector: "kf-text", inputs: { as: { classPropertyName: "as", publicName: "as", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
5279
|
+
<!--
|
|
5280
|
+
Una sola proyección compartida (ng-template + ngTemplateOutlet) en vez
|
|
5281
|
+
de un <ng-content> por cada rama del @switch: Angular solo conecta la
|
|
5282
|
+
proyección real del consumidor a la PRIMERA instancia de <ng-content>
|
|
5283
|
+
que encuentra en el árbol de compilación — con un <ng-content> por
|
|
5284
|
+
rama, todas menos una quedan vacías en runtime (bug detectado por
|
|
5285
|
+
Text.spec.ts, no algo documentado a simple vista en los docs de Angular).
|
|
5286
|
+
-->
|
|
5287
|
+
<ng-template #projected><ng-content /></ng-template>
|
|
5288
|
+
@switch (tag()) {
|
|
5289
|
+
@case ('h1') {
|
|
5290
|
+
<h1 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h1>
|
|
5291
|
+
}
|
|
5292
|
+
@case ('h2') {
|
|
5293
|
+
<h2 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h2>
|
|
5294
|
+
}
|
|
5295
|
+
@case ('h3') {
|
|
5296
|
+
<h3 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h3>
|
|
5297
|
+
}
|
|
5298
|
+
@case ('span') {
|
|
5299
|
+
<span [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></span>
|
|
5300
|
+
}
|
|
5301
|
+
@default {
|
|
5302
|
+
<p [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></p>
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5305
|
+
`, isInline: true, styles: [":host{display:contents}h1,h2,h3,p,span{margin:0;font-family:var(--kf-font-body);color:var(--kf-color-text)}.kf-text--title-xxl{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-xxl);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-xl{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-xl);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-lg{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-lg);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-md{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-sm{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-text--body{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body)}.kf-text--body-muted{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--caption{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--ok{color:var(--kf-color-success-strong)}.kf-text--warn{color:var(--kf-color-warning-strong)}.kf-text--error{color:var(--kf-color-error-strong)}.kf-text--info{color:var(--kf-color-info-strong)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
5306
|
+
}
|
|
5307
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Text, decorators: [{
|
|
5308
|
+
type: Component,
|
|
5309
|
+
args: [{ selector: 'kf-text', imports: [NgTemplateOutlet], template: `
|
|
5310
|
+
<!--
|
|
5311
|
+
Una sola proyección compartida (ng-template + ngTemplateOutlet) en vez
|
|
5312
|
+
de un <ng-content> por cada rama del @switch: Angular solo conecta la
|
|
5313
|
+
proyección real del consumidor a la PRIMERA instancia de <ng-content>
|
|
5314
|
+
que encuentra en el árbol de compilación — con un <ng-content> por
|
|
5315
|
+
rama, todas menos una quedan vacías en runtime (bug detectado por
|
|
5316
|
+
Text.spec.ts, no algo documentado a simple vista en los docs de Angular).
|
|
5317
|
+
-->
|
|
5318
|
+
<ng-template #projected><ng-content /></ng-template>
|
|
5319
|
+
@switch (tag()) {
|
|
5320
|
+
@case ('h1') {
|
|
5321
|
+
<h1 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h1>
|
|
5322
|
+
}
|
|
5323
|
+
@case ('h2') {
|
|
5324
|
+
<h2 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h2>
|
|
5325
|
+
}
|
|
5326
|
+
@case ('h3') {
|
|
5327
|
+
<h3 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h3>
|
|
5328
|
+
}
|
|
5329
|
+
@case ('span') {
|
|
5330
|
+
<span [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></span>
|
|
5331
|
+
}
|
|
5332
|
+
@default {
|
|
5333
|
+
<p [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></p>
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
`, styles: [":host{display:contents}h1,h2,h3,p,span{margin:0;font-family:var(--kf-font-body);color:var(--kf-color-text)}.kf-text--title-xxl{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-xxl);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-xl{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-xl);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-lg{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-lg);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-md{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-sm{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-text--body{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body)}.kf-text--body-muted{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--caption{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--ok{color:var(--kf-color-success-strong)}.kf-text--warn{color:var(--kf-color-warning-strong)}.kf-text--error{color:var(--kf-color-error-strong)}.kf-text--info{color:var(--kf-color-info-strong)}\n"] }]
|
|
5337
|
+
}], propDecorators: { as: [{ type: i0.Input, args: [{ isSignal: true, alias: "as", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
|
|
5338
|
+
|
|
4268
5339
|
/**
|
|
4269
5340
|
* Bloque de título al tope de una página: chip de ícono opcional, título (h1),
|
|
4270
5341
|
* subtítulo opcional y un área de acciones opcional proyectada vía
|
|
@@ -4276,6 +5347,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4276
5347
|
* </kf-page-header>
|
|
4277
5348
|
*
|
|
4278
5349
|
* <kf-page-header icon="settings" title="Configuración" />
|
|
5350
|
+
*
|
|
5351
|
+
* <kf-page-header size="xl" title="Bienvenido de nuevo" />
|
|
4279
5352
|
*/
|
|
4280
5353
|
class PageHeader {
|
|
4281
5354
|
icon = input(undefined, /* @ts-ignore */
|
|
@@ -4284,8 +5357,12 @@ class PageHeader {
|
|
|
4284
5357
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
4285
5358
|
subtitle = input(undefined, /* @ts-ignore */
|
|
4286
5359
|
...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
|
|
5360
|
+
size = input('md', /* @ts-ignore */
|
|
5361
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
5362
|
+
titleVariant = computed(() => `title-${this.size()}`, /* @ts-ignore */
|
|
5363
|
+
...(ngDevMode ? [{ debugName: "titleVariant" }] : /* istanbul ignore next */ []));
|
|
4287
5364
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PageHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4288
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PageHeader, isStandalone: true, selector: "kf-page-header", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
5365
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PageHeader, isStandalone: true, selector: "kf-page-header", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
4289
5366
|
@if (icon(); as iconName) {
|
|
4290
5367
|
<span class="kf-page-header__icon">
|
|
4291
5368
|
<kf-icon [name]="iconName" size="lg" />
|
|
@@ -4293,7 +5370,9 @@ class PageHeader {
|
|
|
4293
5370
|
}
|
|
4294
5371
|
|
|
4295
5372
|
<div class="kf-page-header__heading">
|
|
4296
|
-
<h1 class="kf-page-header__title"
|
|
5373
|
+
<kf-text as="h1" class="kf-page-header__title" [variant]="titleVariant()">
|
|
5374
|
+
{{ title() }}
|
|
5375
|
+
</kf-text>
|
|
4297
5376
|
@if (subtitle()) {
|
|
4298
5377
|
<p class="kf-page-header__subtitle">{{ subtitle() }}</p>
|
|
4299
5378
|
}
|
|
@@ -4302,11 +5381,11 @@ class PageHeader {
|
|
|
4302
5381
|
<div class="kf-page-header__actions">
|
|
4303
5382
|
<ng-content select="[actions]" />
|
|
4304
5383
|
</div>
|
|
4305
|
-
`, isInline: true, styles: [":host{display:flex;align-items:center;gap:var(--kf-space-md);font-family:var(--kf-font-body)}.kf-page-header__icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:48px;height:48px;border-radius:var(--kf-radius-md);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-page-header__heading{display:flex;flex-direction:column;gap:var(--kf-space-xs);flex:1 1 auto;min-width:0}.kf-page-header__title{
|
|
5384
|
+
`, isInline: true, styles: [":host{display:flex;align-items:center;gap:var(--kf-space-md);font-family:var(--kf-font-body)}.kf-page-header__icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:48px;height:48px;border-radius:var(--kf-radius-md);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-page-header__heading{display:flex;flex-direction:column;gap:var(--kf-space-xs);flex:1 1 auto;min-width:0}.kf-page-header__title{line-height:1.2}.kf-page-header__subtitle{margin:0;font-size:.9375rem;color:var(--kf-color-text-muted)}.kf-page-header__actions{flex:none;display:flex;align-items:center;gap:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
|
|
4306
5385
|
}
|
|
4307
5386
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PageHeader, decorators: [{
|
|
4308
5387
|
type: Component,
|
|
4309
|
-
args: [{ selector: 'kf-page-header', imports: [Icon], template: `
|
|
5388
|
+
args: [{ selector: 'kf-page-header', imports: [Icon, Text], template: `
|
|
4310
5389
|
@if (icon(); as iconName) {
|
|
4311
5390
|
<span class="kf-page-header__icon">
|
|
4312
5391
|
<kf-icon [name]="iconName" size="lg" />
|
|
@@ -4314,7 +5393,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4314
5393
|
}
|
|
4315
5394
|
|
|
4316
5395
|
<div class="kf-page-header__heading">
|
|
4317
|
-
<h1 class="kf-page-header__title"
|
|
5396
|
+
<kf-text as="h1" class="kf-page-header__title" [variant]="titleVariant()">
|
|
5397
|
+
{{ title() }}
|
|
5398
|
+
</kf-text>
|
|
4318
5399
|
@if (subtitle()) {
|
|
4319
5400
|
<p class="kf-page-header__subtitle">{{ subtitle() }}</p>
|
|
4320
5401
|
}
|
|
@@ -4323,8 +5404,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4323
5404
|
<div class="kf-page-header__actions">
|
|
4324
5405
|
<ng-content select="[actions]" />
|
|
4325
5406
|
</div>
|
|
4326
|
-
`, styles: [":host{display:flex;align-items:center;gap:var(--kf-space-md);font-family:var(--kf-font-body)}.kf-page-header__icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:48px;height:48px;border-radius:var(--kf-radius-md);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-page-header__heading{display:flex;flex-direction:column;gap:var(--kf-space-xs);flex:1 1 auto;min-width:0}.kf-page-header__title{
|
|
4327
|
-
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }] } });
|
|
5407
|
+
`, styles: [":host{display:flex;align-items:center;gap:var(--kf-space-md);font-family:var(--kf-font-body)}.kf-page-header__icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:48px;height:48px;border-radius:var(--kf-radius-md);background:var(--kf-color-primary-soft);color:var(--kf-color-primary)}.kf-page-header__heading{display:flex;flex-direction:column;gap:var(--kf-space-xs);flex:1 1 auto;min-width:0}.kf-page-header__title{line-height:1.2}.kf-page-header__subtitle{margin:0;font-size:.9375rem;color:var(--kf-color-text-muted)}.kf-page-header__actions{flex:none;display:flex;align-items:center;gap:var(--kf-space-sm)}\n"] }]
|
|
5408
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
4328
5409
|
|
|
4329
5410
|
const SIBLINGS = 1;
|
|
4330
5411
|
/**
|
|
@@ -4495,6 +5576,13 @@ function buildPaginationItems(current, total) {
|
|
|
4495
5576
|
* <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos">
|
|
4496
5577
|
* <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
|
|
4497
5578
|
* </kf-panel>
|
|
5579
|
+
*
|
|
5580
|
+
* El host y el `kf-card` interno son flex column con `flex:1` de punta a
|
|
5581
|
+
* punta — si un grid/flex externo estira `kf-panel` a la altura de un
|
|
5582
|
+
* hermano más alto, el contenido proyectado hereda esa altura en vez de
|
|
5583
|
+
* dejar un hueco vacío bajo el `kf-card`. Sin esa presión externa (altura
|
|
5584
|
+
* "auto" del contenedor), `flex:1` no tiene espacio extra que repartir y el
|
|
5585
|
+
* panel se comporta igual que antes.
|
|
4498
5586
|
*/
|
|
4499
5587
|
class Panel {
|
|
4500
5588
|
title = input.required(/* @ts-ignore */
|
|
@@ -4520,7 +5608,7 @@ class Panel {
|
|
|
4520
5608
|
<ng-content />
|
|
4521
5609
|
</div>
|
|
4522
5610
|
</kf-card>
|
|
4523
|
-
`, isInline: true, styles: [":host{display:
|
|
5611
|
+
`, isInline: true, 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{display:inline-flex;align-items:center;gap:2px;flex:none;font-size:.8125rem;font-weight:600;color:var(--kf-color-primary);text-decoration:none}.kf-panel__action:hover{text-decoration:underline}.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"] }, { kind: "component", type: Divider, selector: "kf-divider", inputs: ["orientation"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }] });
|
|
4524
5612
|
}
|
|
4525
5613
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, decorators: [{
|
|
4526
5614
|
type: Component,
|
|
@@ -4540,7 +5628,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4540
5628
|
<ng-content />
|
|
4541
5629
|
</div>
|
|
4542
5630
|
</kf-card>
|
|
4543
|
-
`, styles: [":host{display:
|
|
5631
|
+
`, 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{display:inline-flex;align-items:center;gap:2px;flex:none;font-size:.8125rem;font-weight:600;color:var(--kf-color-primary);text-decoration:none}.kf-panel__action:hover{text-decoration:underline}.kf-panel__body{display:flex;flex:1;flex-direction:column;gap:var(--kf-space-sm);margin-top:var(--kf-space-sm)}\n"] }]
|
|
4544
5632
|
}], 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 }] }] } });
|
|
4545
5633
|
|
|
4546
5634
|
/**
|
|
@@ -4552,12 +5640,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4552
5640
|
* Es distinto de `kf-page-header`/`kf-page-head` (título de una vista
|
|
4553
5641
|
* interna) — este es el chrome global del portal, se monta una sola vez.
|
|
4554
5642
|
*
|
|
5643
|
+
* `:host` es `position: sticky; top: 0` — el header queda pegado arriba al
|
|
5644
|
+
* hacer scroll. No es `fixed`: como no se superpone al contenido en el
|
|
5645
|
+
* primer render, no hace falta que el consumidor compense con
|
|
5646
|
+
* `padding-top`.
|
|
5647
|
+
*
|
|
5648
|
+
* Bajo los 768px, el `<nav>` deja de mostrarse en línea junto al logo y
|
|
5649
|
+
* colapsa detrás de un botón de menú (ícono `menu`/`x`) que despliega un
|
|
5650
|
+
* `kf-drawer` (panel lateral genérico, ver `Drawer.ts`) anclado al borde
|
|
5651
|
+
* IZQUIERDO — mismo lado que el botón que lo abre, convención dominante
|
|
5652
|
+
* Material/iOS/Android — con los mismos `navItems` en columna dentro del
|
|
5653
|
+
* body (fondo claro) de ese drawer.
|
|
5654
|
+
*
|
|
5655
|
+
* `PortalHeader` expone dos slots propios pensados para que el consumidor
|
|
5656
|
+
* proyecte contenido que en escritorio vive en otro lugar:
|
|
5657
|
+
* - `<ng-content select="[kf-portal-header-extra]">`: identidad de sesión
|
|
5658
|
+
* (ej. `kf-drawer-session`). Cae en `[kf-drawer-header]` de `kf-drawer` —
|
|
5659
|
+
* el único contenedor con fondo oscuro (`--kf-color-tertiary-deep`, ver
|
|
5660
|
+
* `Drawer.ts`) que `kf-drawer-session` espera como fondo (no trae uno
|
|
5661
|
+
* propio, ver `DrawerSession.ts`).
|
|
5662
|
+
* - `<ng-content select="[kf-portal-header-footer]">`: acciones separadas del
|
|
5663
|
+
* resto (ej. `kf-drawer-logout`). Cae en `[kf-drawer-footer]` de
|
|
5664
|
+
* `kf-drawer`, mismo criterio de "no reimplementar nada del drawer": el
|
|
5665
|
+
* footer ya trae su propio padding/separador (ver `Drawer.ts`).
|
|
5666
|
+
*
|
|
5667
|
+
* `PortalHeader` no sabe qué es ninguno de los dos contenidos ni cómo se
|
|
5668
|
+
* abren, solo les da el lugar correcto dentro del drawer ya desplegado.
|
|
5669
|
+
* `kf-drawer` resuelve overlay con blur, cierre con click-afuera/`Escape`,
|
|
5670
|
+
* atrapado de foco real y devolución de foco al cerrar — `PortalHeader` no
|
|
5671
|
+
* reimplementa nada de eso, solo controla su `open` desde el botón de
|
|
5672
|
+
* hamburguesa.
|
|
5673
|
+
*
|
|
4555
5674
|
* <kf-portal-header
|
|
4556
5675
|
* [isAdmin]="true"
|
|
4557
5676
|
* [adminActions]="[{ label: 'Consultar deuda clientes', href: '/deuda' }, { label: 'Cambiar DNI', href: '/dni' }]"
|
|
4558
|
-
* [navItems]="[{ label: 'Home', href: '/', active: true }, { label: 'Vehículos', href: '/vehiculos' }]"
|
|
5677
|
+
* [navItems]="[{ label: 'Home', href: '/', active: true, icon: 'grid' }, { label: 'Vehículos', href: '/vehiculos', icon: 'truck' }]"
|
|
4559
5678
|
* userInitials="TS"
|
|
4560
|
-
*
|
|
5679
|
+
* >
|
|
5680
|
+
* <kf-drawer-session
|
|
5681
|
+
* kf-portal-header-extra
|
|
5682
|
+
* brand="Portal Kaufmann LATAM"
|
|
5683
|
+
* name="Tomás Soto"
|
|
5684
|
+
* email="tomas.soto@kaufmann.com"
|
|
5685
|
+
* />
|
|
5686
|
+
* <kf-drawer-logout kf-portal-header-footer (logoutClick)="onLogout()" />
|
|
5687
|
+
* </kf-portal-header>
|
|
4561
5688
|
*/
|
|
4562
5689
|
class PortalHeader {
|
|
4563
5690
|
brand = input('DIVEMOTOR', /* @ts-ignore */
|
|
@@ -4570,8 +5697,24 @@ class PortalHeader {
|
|
|
4570
5697
|
userInitials = input.required(/* @ts-ignore */
|
|
4571
5698
|
...(ngDevMode ? [{ debugName: "userInitials" }] : /* istanbul ignore next */ []));
|
|
4572
5699
|
sessionClick = output();
|
|
5700
|
+
/** Visibilidad del drawer de navegación mobile (bajo los 768px). Se pasa a
|
|
5701
|
+
* `kf-drawer` como `[(open)]` — `PortalHeader` ya no implementa overlay,
|
|
5702
|
+
* `Escape` ni atrapado de foco por su cuenta, eso vive en `Drawer`. */
|
|
5703
|
+
mobileMenuOpen = signal(false, /* @ts-ignore */
|
|
5704
|
+
...(ngDevMode ? [{ debugName: "mobileMenuOpen" }] : /* istanbul ignore next */ []));
|
|
5705
|
+
/**
|
|
5706
|
+
* `ElementRef` real del botón de sesión (el avatar en la esquina
|
|
5707
|
+
* derecha), no de todo el header. Pensado para anclar un `kf-dropdown`
|
|
5708
|
+
* externo — sin esto, el único `ElementRef` disponible desde afuera es el
|
|
5709
|
+
* del host de `PortalHeader`, que ocupa el ancho completo de la pantalla
|
|
5710
|
+
* (logo + nav + sesión), y el overlay del CDK termina "pegado" a la
|
|
5711
|
+
* esquina del header entero en vez de al botón que el usuario clickeó.
|
|
5712
|
+
*
|
|
5713
|
+
* <kf-dropdown [(open)]="menuOpen" [anchor]="header().sessionButtonRef()">
|
|
5714
|
+
*/
|
|
5715
|
+
sessionButtonRef = viewChild.required('sessionButton', { ...(ngDevMode ? { debugName: "sessionButtonRef" } : /* istanbul ignore next */ {}), read: ElementRef });
|
|
4573
5716
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4574
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PortalHeader, isStandalone: true, selector: "kf-portal-header", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, isAdmin: { classPropertyName: "isAdmin", publicName: "isAdmin", isSignal: true, isRequired: false, transformFunction: null }, adminActions: { classPropertyName: "adminActions", publicName: "adminActions", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, userInitials: { classPropertyName: "userInitials", publicName: "userInitials", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { sessionClick: "sessionClick" }, ngImport: i0, template: `
|
|
5717
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: PortalHeader, isStandalone: true, selector: "kf-portal-header", inputs: { brand: { classPropertyName: "brand", publicName: "brand", isSignal: true, isRequired: false, transformFunction: null }, isAdmin: { classPropertyName: "isAdmin", publicName: "isAdmin", isSignal: true, isRequired: false, transformFunction: null }, adminActions: { classPropertyName: "adminActions", publicName: "adminActions", isSignal: true, isRequired: false, transformFunction: null }, navItems: { classPropertyName: "navItems", publicName: "navItems", isSignal: true, isRequired: false, transformFunction: null }, userInitials: { classPropertyName: "userInitials", publicName: "userInitials", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { sessionClick: "sessionClick" }, viewQueries: [{ propertyName: "sessionButtonRef", first: true, predicate: ["sessionButton"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: `
|
|
4575
5718
|
@if (isAdmin()) {
|
|
4576
5719
|
<div class="kf-portal-header__admin-bar">
|
|
4577
5720
|
<kf-admin-badge inline />
|
|
@@ -4584,6 +5727,7 @@ class PortalHeader {
|
|
|
4584
5727
|
}
|
|
4585
5728
|
<div class="kf-portal-header__brand-bar">
|
|
4586
5729
|
<span class="kf-portal-header__logo">{{ brand() }}</span>
|
|
5730
|
+
|
|
4587
5731
|
<nav class="kf-portal-header__nav">
|
|
4588
5732
|
@for (item of navItems(); track item.label) {
|
|
4589
5733
|
<a
|
|
@@ -4595,15 +5739,54 @@ class PortalHeader {
|
|
|
4595
5739
|
</a>
|
|
4596
5740
|
}
|
|
4597
5741
|
</nav>
|
|
4598
|
-
|
|
5742
|
+
|
|
5743
|
+
<button
|
|
5744
|
+
#sessionButton
|
|
5745
|
+
type="button"
|
|
5746
|
+
class="kf-portal-header__session"
|
|
5747
|
+
(click)="sessionClick.emit()"
|
|
5748
|
+
>
|
|
4599
5749
|
<kf-avatar [initials]="userInitials()" pill />
|
|
4600
5750
|
</button>
|
|
5751
|
+
|
|
5752
|
+
<button
|
|
5753
|
+
type="button"
|
|
5754
|
+
class="kf-portal-header__menu-toggle"
|
|
5755
|
+
[attr.aria-expanded]="mobileMenuOpen()"
|
|
5756
|
+
aria-label="Abrir menú de navegación"
|
|
5757
|
+
(click)="mobileMenuOpen.set(!mobileMenuOpen())"
|
|
5758
|
+
>
|
|
5759
|
+
<kf-icon [name]="mobileMenuOpen() ? 'x' : 'menu'" size="md" />
|
|
5760
|
+
</button>
|
|
4601
5761
|
</div>
|
|
4602
|
-
|
|
5762
|
+
|
|
5763
|
+
<kf-drawer [(open)]="mobileMenuOpen" ariaLabel="Menú de navegación">
|
|
5764
|
+
<div kf-drawer-header>
|
|
5765
|
+
<ng-content select="[kf-portal-header-extra]" />
|
|
5766
|
+
</div>
|
|
5767
|
+
@for (item of navItems(); track item.label) {
|
|
5768
|
+
<a
|
|
5769
|
+
class="kf-portal-header__mobile-nav-item"
|
|
5770
|
+
[class.kf-portal-header__mobile-nav-item--active]="item.active"
|
|
5771
|
+
[href]="item.href"
|
|
5772
|
+
(click)="mobileMenuOpen.set(false)"
|
|
5773
|
+
>
|
|
5774
|
+
@if (item.icon) {
|
|
5775
|
+
<kf-icon [name]="item.icon" size="sm" />
|
|
5776
|
+
}
|
|
5777
|
+
<span class="kf-portal-header__mobile-nav-item-label">{{ item.label }}</span>
|
|
5778
|
+
<kf-icon name="chevron-right" size="sm" />
|
|
5779
|
+
</a>
|
|
5780
|
+
}
|
|
5781
|
+
<div kf-drawer-footer>
|
|
5782
|
+
<ng-content select="[kf-portal-header-footer]" />
|
|
5783
|
+
</div>
|
|
5784
|
+
</kf-drawer>
|
|
5785
|
+
`, isInline: true, styles: [":host{display:block;position:sticky;top:0;z-index:30;font-family:var(--kf-font-body)}.kf-portal-header__admin-bar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);padding:var(--kf-space-xs) var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-portal-header__admin-actions{display:flex;gap:var(--kf-space-md)}.kf-portal-header__admin-action{font-size:.8125rem;font-weight:600;color:#ffffffd9;text-decoration:none}.kf-portal-header__admin-action:hover{color:var(--kf-color-white)}.kf-portal-header__brand-bar{display:flex;align-items:center;gap:var(--kf-space-lg);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-tertiary-deep);border-top:1px solid rgba(255,255,255,.1)}.kf-portal-header__logo{font-family:var(--kf-font-title);font-size:1.25rem;font-weight:700;letter-spacing:.02em;color:var(--kf-color-white);text-transform:uppercase;flex:1 1 auto;min-width:0}.kf-portal-header__nav{display:flex;align-items:center;gap:var(--kf-space-xs);flex:1 1 auto}.kf-portal-header__nav-item{padding:var(--kf-space-xs) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffbf;font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-portal-header__nav-item:hover:not(.kf-portal-header__nav-item--active){color:var(--kf-color-white)}.kf-portal-header__nav-item--active{background:var(--kf-color-white);color:var(--kf-color-ink)}.kf-portal-header__session{flex:none;border:none;background:transparent;padding:0;cursor:pointer}.kf-portal-header__menu-toggle{display:none;flex:none;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-white);cursor:pointer}.kf-portal-header__menu-toggle:hover{background:#ffffff1a}.kf-portal-header__mobile-nav-item{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-xs);color:var(--kf-color-text);font-size:.9375rem;font-weight:600;text-decoration:none;border-radius:var(--kf-radius-sm)}.kf-portal-header__mobile-nav-item:hover{background:var(--kf-color-surface)}.kf-portal-header__mobile-nav-item--active{color:var(--kf-color-primary-active);background:var(--kf-color-primary-soft)}.kf-portal-header__mobile-nav-item-label{flex:1 1 auto}.kf-portal-header__mobile-nav-item kf-icon:last-child{color:#171d2559}@media(max-width:768px){.kf-portal-header__nav,.kf-portal-header__session{display:none}.kf-portal-header__menu-toggle{display:inline-flex}}\n"], dependencies: [{ kind: "component", type: AdminBadge, selector: "kf-admin-badge", inputs: ["inline"] }, { kind: "component", type: Avatar, selector: "kf-avatar", inputs: ["initials", "src", "round", "pill"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }, { kind: "component", type: Drawer, selector: "kf-drawer", inputs: ["open", "side", "ariaLabel"], outputs: ["openChange"] }] });
|
|
4603
5786
|
}
|
|
4604
5787
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, decorators: [{
|
|
4605
5788
|
type: Component,
|
|
4606
|
-
args: [{ selector: 'kf-portal-header', imports: [AdminBadge, Avatar], template: `
|
|
5789
|
+
args: [{ selector: 'kf-portal-header', imports: [AdminBadge, Avatar, Icon, Drawer], template: `
|
|
4607
5790
|
@if (isAdmin()) {
|
|
4608
5791
|
<div class="kf-portal-header__admin-bar">
|
|
4609
5792
|
<kf-admin-badge inline />
|
|
@@ -4616,6 +5799,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4616
5799
|
}
|
|
4617
5800
|
<div class="kf-portal-header__brand-bar">
|
|
4618
5801
|
<span class="kf-portal-header__logo">{{ brand() }}</span>
|
|
5802
|
+
|
|
4619
5803
|
<nav class="kf-portal-header__nav">
|
|
4620
5804
|
@for (item of navItems(); track item.label) {
|
|
4621
5805
|
<a
|
|
@@ -4627,12 +5811,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4627
5811
|
</a>
|
|
4628
5812
|
}
|
|
4629
5813
|
</nav>
|
|
4630
|
-
|
|
5814
|
+
|
|
5815
|
+
<button
|
|
5816
|
+
#sessionButton
|
|
5817
|
+
type="button"
|
|
5818
|
+
class="kf-portal-header__session"
|
|
5819
|
+
(click)="sessionClick.emit()"
|
|
5820
|
+
>
|
|
4631
5821
|
<kf-avatar [initials]="userInitials()" pill />
|
|
4632
5822
|
</button>
|
|
5823
|
+
|
|
5824
|
+
<button
|
|
5825
|
+
type="button"
|
|
5826
|
+
class="kf-portal-header__menu-toggle"
|
|
5827
|
+
[attr.aria-expanded]="mobileMenuOpen()"
|
|
5828
|
+
aria-label="Abrir menú de navegación"
|
|
5829
|
+
(click)="mobileMenuOpen.set(!mobileMenuOpen())"
|
|
5830
|
+
>
|
|
5831
|
+
<kf-icon [name]="mobileMenuOpen() ? 'x' : 'menu'" size="md" />
|
|
5832
|
+
</button>
|
|
4633
5833
|
</div>
|
|
4634
|
-
|
|
4635
|
-
|
|
5834
|
+
|
|
5835
|
+
<kf-drawer [(open)]="mobileMenuOpen" ariaLabel="Menú de navegación">
|
|
5836
|
+
<div kf-drawer-header>
|
|
5837
|
+
<ng-content select="[kf-portal-header-extra]" />
|
|
5838
|
+
</div>
|
|
5839
|
+
@for (item of navItems(); track item.label) {
|
|
5840
|
+
<a
|
|
5841
|
+
class="kf-portal-header__mobile-nav-item"
|
|
5842
|
+
[class.kf-portal-header__mobile-nav-item--active]="item.active"
|
|
5843
|
+
[href]="item.href"
|
|
5844
|
+
(click)="mobileMenuOpen.set(false)"
|
|
5845
|
+
>
|
|
5846
|
+
@if (item.icon) {
|
|
5847
|
+
<kf-icon [name]="item.icon" size="sm" />
|
|
5848
|
+
}
|
|
5849
|
+
<span class="kf-portal-header__mobile-nav-item-label">{{ item.label }}</span>
|
|
5850
|
+
<kf-icon name="chevron-right" size="sm" />
|
|
5851
|
+
</a>
|
|
5852
|
+
}
|
|
5853
|
+
<div kf-drawer-footer>
|
|
5854
|
+
<ng-content select="[kf-portal-header-footer]" />
|
|
5855
|
+
</div>
|
|
5856
|
+
</kf-drawer>
|
|
5857
|
+
`, styles: [":host{display:block;position:sticky;top:0;z-index:30;font-family:var(--kf-font-body)}.kf-portal-header__admin-bar{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-md);padding:var(--kf-space-xs) var(--kf-space-lg);background:var(--kf-color-tertiary-deep)}.kf-portal-header__admin-actions{display:flex;gap:var(--kf-space-md)}.kf-portal-header__admin-action{font-size:.8125rem;font-weight:600;color:#ffffffd9;text-decoration:none}.kf-portal-header__admin-action:hover{color:var(--kf-color-white)}.kf-portal-header__brand-bar{display:flex;align-items:center;gap:var(--kf-space-lg);padding:var(--kf-space-sm) var(--kf-space-lg);background:var(--kf-color-tertiary-deep);border-top:1px solid rgba(255,255,255,.1)}.kf-portal-header__logo{font-family:var(--kf-font-title);font-size:1.25rem;font-weight:700;letter-spacing:.02em;color:var(--kf-color-white);text-transform:uppercase;flex:1 1 auto;min-width:0}.kf-portal-header__nav{display:flex;align-items:center;gap:var(--kf-space-xs);flex:1 1 auto}.kf-portal-header__nav-item{padding:var(--kf-space-xs) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffbf;font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-portal-header__nav-item:hover:not(.kf-portal-header__nav-item--active){color:var(--kf-color-white)}.kf-portal-header__nav-item--active{background:var(--kf-color-white);color:var(--kf-color-ink)}.kf-portal-header__session{flex:none;border:none;background:transparent;padding:0;cursor:pointer}.kf-portal-header__menu-toggle{display:none;flex:none;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--kf-radius-sm);background:transparent;color:var(--kf-color-white);cursor:pointer}.kf-portal-header__menu-toggle:hover{background:#ffffff1a}.kf-portal-header__mobile-nav-item{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-xs);color:var(--kf-color-text);font-size:.9375rem;font-weight:600;text-decoration:none;border-radius:var(--kf-radius-sm)}.kf-portal-header__mobile-nav-item:hover{background:var(--kf-color-surface)}.kf-portal-header__mobile-nav-item--active{color:var(--kf-color-primary-active);background:var(--kf-color-primary-soft)}.kf-portal-header__mobile-nav-item-label{flex:1 1 auto}.kf-portal-header__mobile-nav-item kf-icon:last-child{color:#171d2559}@media(max-width:768px){.kf-portal-header__nav,.kf-portal-header__session{display:none}.kf-portal-header__menu-toggle{display:inline-flex}}\n"] }]
|
|
5858
|
+
}], propDecorators: { brand: [{ type: i0.Input, args: [{ isSignal: true, alias: "brand", required: false }] }], isAdmin: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAdmin", required: false }] }], adminActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminActions", required: false }] }], navItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "navItems", required: false }] }], userInitials: [{ type: i0.Input, args: [{ isSignal: true, alias: "userInitials", required: true }] }], sessionClick: [{ type: i0.Output, args: ["sessionClick"] }], sessionButtonRef: [{ type: i0.ViewChild, args: ['sessionButton', { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
4636
5859
|
|
|
4637
5860
|
/**
|
|
4638
5861
|
* Estado vacío de reservas en taller con llamada a agendar (página 36):
|
|
@@ -4645,7 +5868,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4645
5868
|
* invitación + las acciones como un bloque compuesto propio del dominio
|
|
4646
5869
|
* "taller", no un placeholder de una sola pieza.
|
|
4647
5870
|
*
|
|
4648
|
-
*
|
|
5871
|
+
* `grow` estira el panel de invitación para llenar el espacio vertical
|
|
5872
|
+
* disponible — solo cuando el consumidor comparte fila con una tarjeta más
|
|
5873
|
+
* alta (ej. junto a "Total de vehículos"); en un lugar sin esa presión
|
|
5874
|
+
* (home, debajo del carousel) se deja en su alto natural.
|
|
5875
|
+
*
|
|
5876
|
+
* <kf-reserve-block emptyLabel="No tienes reservas de taller" grow
|
|
4649
5877
|
* title="¿Necesitas mantención?" description="Tenemos la mejor red de talleres."
|
|
4650
5878
|
* actionLabel="Agenda tu reserva aquí" actionHref="/taller/agendar">
|
|
4651
5879
|
* <kf-button actions icon="calendar">Reservar hora</kf-button>
|
|
@@ -4663,13 +5891,15 @@ class ReserveBlock {
|
|
|
4663
5891
|
...(ngDevMode ? [undefined, { debugName: "actionLabel" }] : /* istanbul ignore next */ []));
|
|
4664
5892
|
actionHref = input(/* @ts-ignore */
|
|
4665
5893
|
...(ngDevMode ? [undefined, { debugName: "actionHref" }] : /* istanbul ignore next */ []));
|
|
5894
|
+
/** La invitación se estira para llenar la tarjeta (ej. junto a "Total de vehículos"). */
|
|
5895
|
+
grow = input(false, { ...(ngDevMode ? { debugName: "grow" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4666
5896
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ReserveBlock, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4667
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ReserveBlock, isStandalone: true, selector: "kf-reserve-block", inputs: { emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
5897
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ReserveBlock, isStandalone: true, selector: "kf-reserve-block", inputs: { emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, actionLabel: { classPropertyName: "actionLabel", publicName: "actionLabel", isSignal: true, isRequired: false, transformFunction: null }, actionHref: { classPropertyName: "actionHref", publicName: "actionHref", isSignal: true, isRequired: false, transformFunction: null }, grow: { classPropertyName: "grow", publicName: "grow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
4668
5898
|
<div class="kf-reserve-block__empty">
|
|
4669
5899
|
<kf-icon name="calendar" size="sm" />
|
|
4670
5900
|
<span>{{ emptyLabel() }}</span>
|
|
4671
5901
|
</div>
|
|
4672
|
-
<div class="kf-reserve-block__invite">
|
|
5902
|
+
<div class="kf-reserve-block__invite" [class.kf-reserve-block__invite--grow]="grow()">
|
|
4673
5903
|
<span class="kf-reserve-block__invite-icon">
|
|
4674
5904
|
<kf-icon name="tool" size="md" />
|
|
4675
5905
|
</span>
|
|
@@ -4689,7 +5919,7 @@ class ReserveBlock {
|
|
|
4689
5919
|
<div class="kf-reserve-block__actions">
|
|
4690
5920
|
<ng-content select="[actions]" />
|
|
4691
5921
|
</div>
|
|
4692
|
-
`, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-sm);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font-family:var(--kf-font-body)}.kf-reserve-block__empty{display:flex;align-items:center;gap:var(--kf-space-xs);padding-bottom:var(--kf-space-sm);border-bottom:1px solid var(--kf-color-border);color:var(--kf-color-text-muted);font-size:.875rem}.kf-reserve-block__invite{display:flex;align-items:
|
|
5922
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-sm);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font-family:var(--kf-font-body)}.kf-reserve-block__empty{display:flex;align-items:center;gap:var(--kf-space-xs);padding-bottom:var(--kf-space-sm);border-bottom:1px solid var(--kf-color-border);color:var(--kf-color-text-muted);font-size:.875rem}.kf-reserve-block__invite{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft)}.kf-reserve-block__invite--grow{flex:1}.kf-reserve-block__invite-icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.25rem;height:2.25rem;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white)}.kf-reserve-block__invite-text{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-reserve-block__invite-title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-reserve-block__invite-description{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-reserve-block__invite-action{display:inline-flex;align-items:center;gap:2px;margin-top:var(--kf-space-xs);font-size:.8125rem;font-weight:600;color:var(--kf-color-primary);text-decoration:none}.kf-reserve-block__invite-action:hover{text-decoration:underline}.kf-reserve-block__actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-sm)}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }] });
|
|
4693
5923
|
}
|
|
4694
5924
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ReserveBlock, decorators: [{
|
|
4695
5925
|
type: Component,
|
|
@@ -4698,7 +5928,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4698
5928
|
<kf-icon name="calendar" size="sm" />
|
|
4699
5929
|
<span>{{ emptyLabel() }}</span>
|
|
4700
5930
|
</div>
|
|
4701
|
-
<div class="kf-reserve-block__invite">
|
|
5931
|
+
<div class="kf-reserve-block__invite" [class.kf-reserve-block__invite--grow]="grow()">
|
|
4702
5932
|
<span class="kf-reserve-block__invite-icon">
|
|
4703
5933
|
<kf-icon name="tool" size="md" />
|
|
4704
5934
|
</span>
|
|
@@ -4718,8 +5948,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4718
5948
|
<div class="kf-reserve-block__actions">
|
|
4719
5949
|
<ng-content select="[actions]" />
|
|
4720
5950
|
</div>
|
|
4721
|
-
`, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-sm);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font-family:var(--kf-font-body)}.kf-reserve-block__empty{display:flex;align-items:center;gap:var(--kf-space-xs);padding-bottom:var(--kf-space-sm);border-bottom:1px solid var(--kf-color-border);color:var(--kf-color-text-muted);font-size:.875rem}.kf-reserve-block__invite{display:flex;align-items:
|
|
4722
|
-
}], propDecorators: { emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }] } });
|
|
5951
|
+
`, styles: [":host{display:flex;flex-direction:column;gap:var(--kf-space-sm);padding:var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md);background:var(--kf-color-white);font-family:var(--kf-font-body)}.kf-reserve-block__empty{display:flex;align-items:center;gap:var(--kf-space-xs);padding-bottom:var(--kf-space-sm);border-bottom:1px solid var(--kf-color-border);color:var(--kf-color-text-muted);font-size:.875rem}.kf-reserve-block__invite{display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm);border-radius:var(--kf-radius-sm);background:var(--kf-color-primary-soft)}.kf-reserve-block__invite--grow{flex:1}.kf-reserve-block__invite-icon{display:inline-flex;align-items:center;justify-content:center;flex:none;width:2.25rem;height:2.25rem;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary);color:var(--kf-color-white)}.kf-reserve-block__invite-text{display:flex;flex-direction:column;gap:2px;min-width:0}.kf-reserve-block__invite-title{font-family:var(--kf-font-title);font-weight:700;color:var(--kf-color-ink)}.kf-reserve-block__invite-description{font-size:.8125rem;color:var(--kf-color-text-muted)}.kf-reserve-block__invite-action{display:inline-flex;align-items:center;gap:2px;margin-top:var(--kf-space-xs);font-size:.8125rem;font-weight:600;color:var(--kf-color-primary);text-decoration:none}.kf-reserve-block__invite-action:hover{text-decoration:underline}.kf-reserve-block__actions{display:flex;flex-wrap:wrap;gap:var(--kf-space-sm)}\n"] }]
|
|
5952
|
+
}], propDecorators: { emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], actionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionLabel", required: false }] }], actionHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionHref", required: false }] }], grow: [{ type: i0.Input, args: [{ isSignal: true, alias: "grow", required: false }] }] } });
|
|
4723
5953
|
|
|
4724
5954
|
/**
|
|
4725
5955
|
* Título de sub-sección con chip de ícono, pensado para anidarse dentro de
|
|
@@ -4884,6 +6114,471 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4884
6114
|
`, styles: [":host{display:inline-block}.kf-segmented__track{position:relative;display:grid;grid-template-columns:repeat(var(--kf-segmented-count),1fr);gap:2px;padding:2px;background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-segmented__thumb{position:absolute;top:2px;left:2px;width:calc((100% - 4px) / var(--kf-segmented-count));height:calc(100% - 4px);background:var(--kf-color-white);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-xs);transform:translate(calc(var(--kf-segmented-index, 0) * 100%));transition:transform .2s ease}.kf-segmented__option{position:relative;z-index:1;font:inherit;padding:var(--kf-space-xs) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text-muted);border-radius:var(--kf-radius-sm);cursor:pointer;transition:color .14s}.kf-segmented__option:hover:not(.kf-segmented__option--active){color:var(--kf-color-text)}.kf-segmented__option--active{color:var(--kf-color-ink)}.kf-segmented__option:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}\n"] }]
|
|
4885
6115
|
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
4886
6116
|
|
|
6117
|
+
/**
|
|
6118
|
+
* Contenido visual del tooltip — componente interno, no exportado en
|
|
6119
|
+
* `public-api.ts`. `TooltipDirective` es la única forma pública de usarlo.
|
|
6120
|
+
*/
|
|
6121
|
+
class TooltipContent {
|
|
6122
|
+
text = '';
|
|
6123
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TooltipContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6124
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: TooltipContent, isStandalone: true, selector: "kf-tooltip-content", ngImport: i0, template: `{{ text }}`, isInline: true, styles: [":host{display:block;max-width:220px;padding:4px var(--kf-space-sm);border-radius:var(--kf-radius-sm);background:var(--kf-color-tertiary-deep);color:var(--kf-color-white);font-family:var(--kf-font-body);font-size:.75rem;font-weight:600;line-height:1.4;white-space:nowrap;box-shadow:var(--kf-shadow-lg);animation:kf-tooltip-enter .1s ease-out;pointer-events:none}@media(prefers-reduced-motion:reduce){:host{animation:none}}@keyframes kf-tooltip-enter{0%{opacity:0}to{opacity:1}}\n"] });
|
|
6125
|
+
}
|
|
6126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TooltipContent, decorators: [{
|
|
6127
|
+
type: Component,
|
|
6128
|
+
args: [{ selector: 'kf-tooltip-content', template: `{{ text }}`, styles: [":host{display:block;max-width:220px;padding:4px var(--kf-space-sm);border-radius:var(--kf-radius-sm);background:var(--kf-color-tertiary-deep);color:var(--kf-color-white);font-family:var(--kf-font-body);font-size:.75rem;font-weight:600;line-height:1.4;white-space:nowrap;box-shadow:var(--kf-shadow-lg);animation:kf-tooltip-enter .1s ease-out;pointer-events:none}@media(prefers-reduced-motion:reduce){:host{animation:none}}@keyframes kf-tooltip-enter{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
6129
|
+
}] });
|
|
6130
|
+
/**
|
|
6131
|
+
* Tooltip de hover/focus — reemplaza el atributo `title` nativo del
|
|
6132
|
+
* navegador (lento en aparecer, sin estilo, inconsistente entre
|
|
6133
|
+
* navegadores/SO) por un panel propio, mismo mecanismo de
|
|
6134
|
+
* `@angular/cdk/overlay` que ya usa `Dropdown`.
|
|
6135
|
+
*
|
|
6136
|
+
* Pensado para el caso "ícono sin label visible necesita comunicar qué
|
|
6137
|
+
* es" (ej. `kf-sidebar-nav-item` colapsado). No es two-way: a diferencia
|
|
6138
|
+
* de `Dropdown`, esto se abre/cierra solo con hover/focus, nunca por click.
|
|
6139
|
+
*
|
|
6140
|
+
* <a [kfTooltip]="label()" kfTooltipPosition="right">…</a>
|
|
6141
|
+
*/
|
|
6142
|
+
class TooltipDirective {
|
|
6143
|
+
text = '';
|
|
6144
|
+
kfTooltipPosition = 'right';
|
|
6145
|
+
host = inject((ElementRef));
|
|
6146
|
+
overlayService = inject(Overlay);
|
|
6147
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
6148
|
+
injector = inject(Injector);
|
|
6149
|
+
overlayRef = null;
|
|
6150
|
+
show() {
|
|
6151
|
+
if (!this.text || this.overlayRef?.hasAttached()) {
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
6154
|
+
const positionStrategy = this.overlayService
|
|
6155
|
+
.position()
|
|
6156
|
+
.flexibleConnectedTo(this.host)
|
|
6157
|
+
.withPositions(this.positionsFor(this.kfTooltipPosition))
|
|
6158
|
+
.withViewportMargin(8)
|
|
6159
|
+
.withPush(true);
|
|
6160
|
+
const overlayRef = this.overlayService.create({
|
|
6161
|
+
positionStrategy,
|
|
6162
|
+
scrollStrategy: this.overlayService.scrollStrategies.reposition(),
|
|
6163
|
+
});
|
|
6164
|
+
const portal = new ComponentPortal(TooltipContent, this.viewContainerRef, this.injector);
|
|
6165
|
+
const componentRef = overlayRef.attach(portal);
|
|
6166
|
+
componentRef.instance.text = this.text;
|
|
6167
|
+
this.overlayRef = overlayRef;
|
|
6168
|
+
}
|
|
6169
|
+
hide() {
|
|
6170
|
+
this.overlayRef?.dispose();
|
|
6171
|
+
this.overlayRef = null;
|
|
6172
|
+
}
|
|
6173
|
+
positionsFor(placement) {
|
|
6174
|
+
const gap = 12;
|
|
6175
|
+
switch (placement) {
|
|
6176
|
+
case 'right':
|
|
6177
|
+
return [
|
|
6178
|
+
{
|
|
6179
|
+
originX: 'end',
|
|
6180
|
+
originY: 'center',
|
|
6181
|
+
overlayX: 'start',
|
|
6182
|
+
overlayY: 'center',
|
|
6183
|
+
offsetX: gap,
|
|
6184
|
+
},
|
|
6185
|
+
];
|
|
6186
|
+
case 'left':
|
|
6187
|
+
return [
|
|
6188
|
+
{
|
|
6189
|
+
originX: 'start',
|
|
6190
|
+
originY: 'center',
|
|
6191
|
+
overlayX: 'end',
|
|
6192
|
+
overlayY: 'center',
|
|
6193
|
+
offsetX: -gap,
|
|
6194
|
+
},
|
|
6195
|
+
];
|
|
6196
|
+
case 'top':
|
|
6197
|
+
return [
|
|
6198
|
+
{
|
|
6199
|
+
originX: 'center',
|
|
6200
|
+
originY: 'top',
|
|
6201
|
+
overlayX: 'center',
|
|
6202
|
+
overlayY: 'bottom',
|
|
6203
|
+
offsetY: -gap,
|
|
6204
|
+
},
|
|
6205
|
+
];
|
|
6206
|
+
case 'bottom':
|
|
6207
|
+
default:
|
|
6208
|
+
return [
|
|
6209
|
+
{
|
|
6210
|
+
originX: 'center',
|
|
6211
|
+
originY: 'bottom',
|
|
6212
|
+
overlayX: 'center',
|
|
6213
|
+
overlayY: 'top',
|
|
6214
|
+
offsetY: gap,
|
|
6215
|
+
},
|
|
6216
|
+
];
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6220
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: TooltipDirective, isStandalone: true, selector: "[kfTooltip]", inputs: { text: ["kfTooltip", "text"], kfTooltipPosition: "kfTooltipPosition" }, host: { listeners: { "mouseenter": "show()", "focus": "show()", "mouseleave": "hide()", "blur": "hide()", "document:keydown.escape": "hide()" } }, ngImport: i0 });
|
|
6221
|
+
}
|
|
6222
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TooltipDirective, decorators: [{
|
|
6223
|
+
type: Directive,
|
|
6224
|
+
args: [{
|
|
6225
|
+
selector: '[kfTooltip]',
|
|
6226
|
+
}]
|
|
6227
|
+
}], propDecorators: { text: [{
|
|
6228
|
+
type: Input$1,
|
|
6229
|
+
args: ['kfTooltip']
|
|
6230
|
+
}], kfTooltipPosition: [{
|
|
6231
|
+
type: Input$1
|
|
6232
|
+
}], show: [{
|
|
6233
|
+
type: HostListener,
|
|
6234
|
+
args: ['mouseenter']
|
|
6235
|
+
}, {
|
|
6236
|
+
type: HostListener,
|
|
6237
|
+
args: ['focus']
|
|
6238
|
+
}], hide: [{
|
|
6239
|
+
type: HostListener,
|
|
6240
|
+
args: ['mouseleave']
|
|
6241
|
+
}, {
|
|
6242
|
+
type: HostListener,
|
|
6243
|
+
args: ['blur']
|
|
6244
|
+
}, {
|
|
6245
|
+
type: HostListener,
|
|
6246
|
+
args: ['document:keydown.escape']
|
|
6247
|
+
}] } });
|
|
6248
|
+
|
|
6249
|
+
/**
|
|
6250
|
+
* Ítem de navegación de `kf-sidebar-shell` — ícono + label en fila, estado
|
|
6251
|
+
* activo, sin agrupación. Es el equivalente vertical/persistente del `<a>`
|
|
6252
|
+
* que arma `kf-portal-header` para su `<nav>` horizontal.
|
|
6253
|
+
*
|
|
6254
|
+
* `collapsed` se recibe como input explícito (no vía `:host-context`): el
|
|
6255
|
+
* componente no necesita saber cómo funciona el estado de colapso del
|
|
6256
|
+
* shell, solo qué hacer cuando `collapsed` es `true`.
|
|
6257
|
+
*
|
|
6258
|
+
* No sabe nada de routing — emite `href` como cualquier link y deja que el
|
|
6259
|
+
* consumidor intercepte el click para integrarlo con el Router.
|
|
6260
|
+
*/
|
|
6261
|
+
class SidebarNavItem {
|
|
6262
|
+
icon = input.required(/* @ts-ignore */
|
|
6263
|
+
...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
6264
|
+
label = input.required(/* @ts-ignore */
|
|
6265
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6266
|
+
href = input.required(/* @ts-ignore */
|
|
6267
|
+
...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
|
|
6268
|
+
active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6269
|
+
/** Oculta el label y centra el ícono — lo controla `kf-sidebar-shell` según su propio estado de colapso. */
|
|
6270
|
+
collapsed = input(false, { ...(ngDevMode ? { debugName: "collapsed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6271
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6272
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SidebarNavItem, isStandalone: true, selector: "kf-sidebar-nav-item", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
6273
|
+
<a
|
|
6274
|
+
class="kf-sidebar-nav-item"
|
|
6275
|
+
[class.kf-sidebar-nav-item--active]="active()"
|
|
6276
|
+
[class.kf-sidebar-nav-item--collapsed]="collapsed()"
|
|
6277
|
+
[href]="href()"
|
|
6278
|
+
[attr.aria-current]="active() ? 'page' : null"
|
|
6279
|
+
[kfTooltip]="collapsed() ? label() : ''"
|
|
6280
|
+
>
|
|
6281
|
+
<kf-icon [name]="icon()" size="md" />
|
|
6282
|
+
@if (!collapsed()) {
|
|
6283
|
+
<span class="kf-sidebar-nav-item__label">{{ label() }}</span>
|
|
6284
|
+
}
|
|
6285
|
+
</a>
|
|
6286
|
+
`, isInline: true, styles: [".kf-sidebar-nav-item{box-sizing:border-box;position:relative;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffb3;font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-sidebar-nav-item:hover:not(.kf-sidebar-nav-item--active){background:#ffffff0f;color:var(--kf-color-white)}.kf-sidebar-nav-item--collapsed:hover:not(.kf-sidebar-nav-item--active){background:#ffffff1f}.kf-sidebar-nav-item--active{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-nav-item--active:before{content:\"\";position:absolute;left:calc(-1 * var(--kf-space-xs));top:20%;bottom:20%;width:3px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary-hover)}.kf-sidebar-nav-item--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-nav-item--collapsed.kf-sidebar-nav-item--active:before{left:calc(-1 * var(--kf-space-sm))}\n"], dependencies: [{ kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
|
|
6287
|
+
}
|
|
6288
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarNavItem, decorators: [{
|
|
6289
|
+
type: Component,
|
|
6290
|
+
args: [{ selector: 'kf-sidebar-nav-item', imports: [Icon, TooltipDirective], template: `
|
|
6291
|
+
<a
|
|
6292
|
+
class="kf-sidebar-nav-item"
|
|
6293
|
+
[class.kf-sidebar-nav-item--active]="active()"
|
|
6294
|
+
[class.kf-sidebar-nav-item--collapsed]="collapsed()"
|
|
6295
|
+
[href]="href()"
|
|
6296
|
+
[attr.aria-current]="active() ? 'page' : null"
|
|
6297
|
+
[kfTooltip]="collapsed() ? label() : ''"
|
|
6298
|
+
>
|
|
6299
|
+
<kf-icon [name]="icon()" size="md" />
|
|
6300
|
+
@if (!collapsed()) {
|
|
6301
|
+
<span class="kf-sidebar-nav-item__label">{{ label() }}</span>
|
|
6302
|
+
}
|
|
6303
|
+
</a>
|
|
6304
|
+
`, styles: [".kf-sidebar-nav-item{box-sizing:border-box;position:relative;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border-radius:var(--kf-radius-sm);color:#ffffffb3;font-family:var(--kf-font-body);font-size:.875rem;font-weight:600;text-decoration:none;white-space:nowrap;transition:background .14s,color .14s}.kf-sidebar-nav-item:hover:not(.kf-sidebar-nav-item--active){background:#ffffff0f;color:var(--kf-color-white)}.kf-sidebar-nav-item--collapsed:hover:not(.kf-sidebar-nav-item--active){background:#ffffff1f}.kf-sidebar-nav-item--active{background:#ffffff1f;color:var(--kf-color-white)}.kf-sidebar-nav-item--active:before{content:\"\";position:absolute;left:calc(-1 * var(--kf-space-xs));top:20%;bottom:20%;width:3px;border-radius:var(--kf-radius-pill);background:var(--kf-color-primary-hover)}.kf-sidebar-nav-item--collapsed{justify-content:center;width:40px;height:40px;padding-inline:0}.kf-sidebar-nav-item--collapsed.kf-sidebar-nav-item--active:before{left:calc(-1 * var(--kf-space-sm))}\n"] }]
|
|
6305
|
+
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: true }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }] } });
|
|
6306
|
+
|
|
6307
|
+
/**
|
|
6308
|
+
* Signal reactivo con el estado actual de un media query — para cuando un
|
|
6309
|
+
* componente necesita SABER en qué lado de un breakpoint está (no solo
|
|
6310
|
+
* mostrar/ocultar con CSS). Un `@media` en `styles` no alcanza cuando la
|
|
6311
|
+
* decisión no es puramente visual: por ejemplo, un componente hijo que
|
|
6312
|
+
* recibe un input booleano (`[collapsed]`) no puede "leer" un breakpoint
|
|
6313
|
+
* CSS del padre — necesita que alguien en TypeScript se lo diga.
|
|
6314
|
+
*
|
|
6315
|
+
* Encontrado al construir `SidebarShell`: el estado `collapsed` (colapso
|
|
6316
|
+
* del rail en desktop) se pasaba tal cual a cada `kf-sidebar-nav-item`, sin
|
|
6317
|
+
* importar el viewport. En mobile, donde el rail deslizado siempre muestra
|
|
6318
|
+
* etiquetas completas (ver estilos de `SidebarShell`), los ítems seguían
|
|
6319
|
+
* ocultando su label si el usuario había dejado el rail colapsado en
|
|
6320
|
+
* desktop antes de achicar la ventana — un CSS que dice "en mobile se ve
|
|
6321
|
+
* ancho" no cambia el valor del input que ya llegó al hijo.
|
|
6322
|
+
*
|
|
6323
|
+
* Debe llamarse dentro de un contexto de inyección (el `constructor` de un
|
|
6324
|
+
* componente, o un campo de clase inicializado ahí — mismo requisito que
|
|
6325
|
+
* cualquier función que use `inject()`).
|
|
6326
|
+
*
|
|
6327
|
+
* protected readonly isMobile = matchMediaSignal('(max-width: 900px)');
|
|
6328
|
+
* // ...
|
|
6329
|
+
* [collapsed]="collapsed() && !isMobile()"
|
|
6330
|
+
*/
|
|
6331
|
+
function matchMediaSignal(query) {
|
|
6332
|
+
const mediaQueryList = matchMedia(query);
|
|
6333
|
+
const state = signal(mediaQueryList.matches, /* @ts-ignore */
|
|
6334
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
6335
|
+
const onChange = (event) => state.set(event.matches);
|
|
6336
|
+
mediaQueryList.addEventListener('change', onChange);
|
|
6337
|
+
inject(DestroyRef).onDestroy(() => mediaQueryList.removeEventListener('change', onChange));
|
|
6338
|
+
return state.asReadonly();
|
|
6339
|
+
}
|
|
6340
|
+
|
|
6341
|
+
/** Mismo breakpoint que el `@media` de este componente — ver estilos. */
|
|
6342
|
+
const MOBILE_BREAKPOINT_QUERY = '(max-width: 900px)';
|
|
6343
|
+
/**
|
|
6344
|
+
* Shell de layout con navegación lateral persistente — el complemento
|
|
6345
|
+
* vertical de `kf-portal-header` (navbar horizontal). Pensado para
|
|
6346
|
+
* portales tipo "aplicación interna" (Portal OTD, Servicio Star).
|
|
6347
|
+
*
|
|
6348
|
+
* `navItems` se recibe como array de datos (no como contenido proyectado):
|
|
6349
|
+
* el componente no sabe nada de routing, emite `navItemClick` con el ítem
|
|
6350
|
+
* clickeado y deja que el consumidor decida cómo navegar.
|
|
6351
|
+
*
|
|
6352
|
+
* Slots: `[kf-sidebar-brand]` (logo del rail expandido, visible en desktop
|
|
6353
|
+
* y en el panel mobile — es el MISMO rail), `[kf-sidebar-brand-collapsed]`
|
|
6354
|
+
* (reemplazo opcional cuando el rail está colapsado en desktop),
|
|
6355
|
+
* `[kf-sidebar-brand-mobile]` (logo de la topbar angosta de mobile, fondo
|
|
6356
|
+
* claro) y `[kf-sidebar-footer]` (tarjeta de usuario / acciones al pie).
|
|
6357
|
+
*
|
|
6358
|
+
* Botón de colapso: vive como último ítem de la propia lista de
|
|
6359
|
+
* navegación, no flotando en el borde del rail — mismo criterio de
|
|
6360
|
+
* tap-target grande y predecible que el resto de los ítems.
|
|
6361
|
+
*
|
|
6362
|
+
* Responsive: bajo los 900px el sidebar NO se reemplaza por un panel
|
|
6363
|
+
* distinto — es el MISMO rail el que pasa a `position: fixed` fuera de la
|
|
6364
|
+
* pantalla (`translateX(-100%)`) y se desliza a la vista (`translateX(0)`)
|
|
6365
|
+
* sobre un backdrop cuando `mobileOpen` es `true`. Una topbar angosta con
|
|
6366
|
+
* botón de hamburguesa reemplaza al rail en el flujo del documento
|
|
6367
|
+
* mientras el panel está cerrado.
|
|
6368
|
+
*
|
|
6369
|
+
* A propósito NO reutiliza `kf-drawer` (el panel lateral genérico de la
|
|
6370
|
+
* librería): `kf-drawer` tiene su propio fondo claro fijo y duplicaría el
|
|
6371
|
+
* árbol de navegación en un segundo lugar del DOM. Bug de contraste ya
|
|
6372
|
+
* corregido en el historial: los `kf-sidebar-nav-item`, con texto claro
|
|
6373
|
+
* pensado para el gradiente oscuro, quedaban invisibles sobre el fondo
|
|
6374
|
+
* blanco de `kf-drawer`. Acá hay un solo rail, un solo árbol de nav, que
|
|
6375
|
+
* cambia de posición según el viewport.
|
|
6376
|
+
*/
|
|
6377
|
+
class SidebarShell {
|
|
6378
|
+
/** Ítems del menú del rail. */
|
|
6379
|
+
navItems = input([], /* @ts-ignore */
|
|
6380
|
+
...(ngDevMode ? [{ debugName: "navItems" }] : /* istanbul ignore next */ []));
|
|
6381
|
+
/** Estado de colapso del rail en desktop, two-way. Sin efecto bajo los 900px (ver estilos). */
|
|
6382
|
+
collapsed = model(false, /* @ts-ignore */
|
|
6383
|
+
...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
|
|
6384
|
+
/** Visibilidad del rail deslizado como panel en mobile, two-way. */
|
|
6385
|
+
mobileOpen = model(false, /* @ts-ignore */
|
|
6386
|
+
...(ngDevMode ? [{ debugName: "mobileOpen" }] : /* istanbul ignore next */ []));
|
|
6387
|
+
/** Se emite al clickear un ítem — el consumidor decide cómo navegar (ej. `router.navigateByUrl`). */
|
|
6388
|
+
navItemClick = output();
|
|
6389
|
+
/** `true` bajo los 900px — sin esto, un rail dejado colapsado en desktop
|
|
6390
|
+
* seguía ocultando el label de cada ítem al abrir el panel en mobile. */
|
|
6391
|
+
isMobileViewport = matchMediaSignal(MOBILE_BREAKPOINT_QUERY);
|
|
6392
|
+
/** Estado de colapso que efectivamente se le pasa a cada `kf-sidebar-nav-item`
|
|
6393
|
+
* y al propio botón de colapso. */
|
|
6394
|
+
itemsCollapsed = computed(() => this.collapsed() && !this.isMobileViewport(), /* @ts-ignore */
|
|
6395
|
+
...(ngDevMode ? [{ debugName: "itemsCollapsed" }] : /* istanbul ignore next */ []));
|
|
6396
|
+
/** Cierra el panel mobile con Escape — mismo criterio que `Dropdown`/`Modal`/`Drawer`. */
|
|
6397
|
+
onEscape() {
|
|
6398
|
+
this.mobileOpen.set(false);
|
|
6399
|
+
}
|
|
6400
|
+
onNavItemClick(event, item) {
|
|
6401
|
+
event.preventDefault();
|
|
6402
|
+
this.mobileOpen.set(false);
|
|
6403
|
+
this.navItemClick.emit(item);
|
|
6404
|
+
}
|
|
6405
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6406
|
+
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: `
|
|
6407
|
+
@if (mobileOpen()) {
|
|
6408
|
+
<button
|
|
6409
|
+
type="button"
|
|
6410
|
+
class="kf-sidebar-shell__backdrop"
|
|
6411
|
+
aria-label="Cerrar menú"
|
|
6412
|
+
(click)="mobileOpen.set(false)"
|
|
6413
|
+
></button>
|
|
6414
|
+
}
|
|
6415
|
+
|
|
6416
|
+
<aside
|
|
6417
|
+
class="kf-sidebar-shell__rail"
|
|
6418
|
+
[class.kf-sidebar-shell__rail--collapsed]="collapsed()"
|
|
6419
|
+
[class.kf-sidebar-shell__rail--mobile-open]="mobileOpen()"
|
|
6420
|
+
>
|
|
6421
|
+
<div class="kf-sidebar-shell__brand">
|
|
6422
|
+
<div class="kf-sidebar-shell__brand-full">
|
|
6423
|
+
<ng-content select="[kf-sidebar-brand]" />
|
|
6424
|
+
</div>
|
|
6425
|
+
<div class="kf-sidebar-shell__brand-collapsed">
|
|
6426
|
+
<ng-content select="[kf-sidebar-brand-collapsed]" />
|
|
6427
|
+
</div>
|
|
6428
|
+
|
|
6429
|
+
<button
|
|
6430
|
+
type="button"
|
|
6431
|
+
class="kf-sidebar-shell__mobile-close"
|
|
6432
|
+
aria-label="Cerrar menú"
|
|
6433
|
+
(click)="mobileOpen.set(false)"
|
|
6434
|
+
>
|
|
6435
|
+
<kf-icon name="x" size="md" />
|
|
6436
|
+
</button>
|
|
6437
|
+
</div>
|
|
6438
|
+
|
|
6439
|
+
<nav class="kf-sidebar-shell__nav">
|
|
6440
|
+
@for (item of navItems(); track item.href) {
|
|
6441
|
+
<kf-sidebar-nav-item
|
|
6442
|
+
[icon]="item.icon"
|
|
6443
|
+
[label]="item.label"
|
|
6444
|
+
[href]="item.href"
|
|
6445
|
+
[active]="!!item.active"
|
|
6446
|
+
[collapsed]="itemsCollapsed()"
|
|
6447
|
+
(click)="onNavItemClick($event, item)"
|
|
6448
|
+
/>
|
|
6449
|
+
}
|
|
6450
|
+
</nav>
|
|
6451
|
+
|
|
6452
|
+
<div class="kf-sidebar-shell__bottom">
|
|
6453
|
+
<button
|
|
6454
|
+
type="button"
|
|
6455
|
+
class="kf-sidebar-shell__collapse-toggle"
|
|
6456
|
+
[class.kf-sidebar-shell__collapse-toggle--collapsed]="itemsCollapsed()"
|
|
6457
|
+
[attr.aria-expanded]="!collapsed()"
|
|
6458
|
+
[attr.aria-label]="collapsed() ? 'Expandir menú' : 'Colapsar menú'"
|
|
6459
|
+
[kfTooltip]="itemsCollapsed() ? (collapsed() ? 'Expandir menú' : 'Colapsar menú') : ''"
|
|
6460
|
+
(click)="collapsed.set(!collapsed())"
|
|
6461
|
+
>
|
|
6462
|
+
<kf-icon [name]="collapsed() ? 'chevron-right' : 'chevron-left'" size="sm" />
|
|
6463
|
+
@if (!itemsCollapsed()) {
|
|
6464
|
+
<span class="kf-sidebar-shell__collapse-toggle-label">
|
|
6465
|
+
{{ collapsed() ? 'Expandir menú' : 'Colapsar menú' }}
|
|
6466
|
+
</span>
|
|
6467
|
+
}
|
|
6468
|
+
</button>
|
|
6469
|
+
|
|
6470
|
+
<div class="kf-sidebar-shell__footer">
|
|
6471
|
+
<ng-content select="[kf-sidebar-footer]" />
|
|
6472
|
+
</div>
|
|
6473
|
+
</div>
|
|
6474
|
+
</aside>
|
|
6475
|
+
|
|
6476
|
+
<div class="kf-sidebar-shell__topbar">
|
|
6477
|
+
<button
|
|
6478
|
+
type="button"
|
|
6479
|
+
class="kf-sidebar-shell__menu-toggle"
|
|
6480
|
+
aria-label="Abrir menú de navegación"
|
|
6481
|
+
(click)="mobileOpen.set(true)"
|
|
6482
|
+
>
|
|
6483
|
+
<kf-icon name="menu" size="md" />
|
|
6484
|
+
</button>
|
|
6485
|
+
<div class="kf-sidebar-shell__topbar-brand">
|
|
6486
|
+
<ng-content select="[kf-sidebar-brand-mobile]" />
|
|
6487
|
+
</div>
|
|
6488
|
+
</div>
|
|
6489
|
+
`, isInline: true, styles: [":host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:relative;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{position:relative;display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;width:100%}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none}.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:block}.kf-sidebar-shell__mobile-close{display:none;margin-left:auto;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__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:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--mobile-open{transform:translate(0)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:block}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed,.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__mobile-close{display:flex}.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"] }, { kind: "component", type: SidebarNavItem, selector: "kf-sidebar-nav-item", inputs: ["icon", "label", "href", "active", "collapsed"] }, { kind: "directive", type: TooltipDirective, selector: "[kfTooltip]", inputs: ["kfTooltip", "kfTooltipPosition"] }] });
|
|
6490
|
+
}
|
|
6491
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SidebarShell, decorators: [{
|
|
6492
|
+
type: Component,
|
|
6493
|
+
args: [{ selector: 'kf-sidebar-shell', imports: [Icon, SidebarNavItem, TooltipDirective], template: `
|
|
6494
|
+
@if (mobileOpen()) {
|
|
6495
|
+
<button
|
|
6496
|
+
type="button"
|
|
6497
|
+
class="kf-sidebar-shell__backdrop"
|
|
6498
|
+
aria-label="Cerrar menú"
|
|
6499
|
+
(click)="mobileOpen.set(false)"
|
|
6500
|
+
></button>
|
|
6501
|
+
}
|
|
6502
|
+
|
|
6503
|
+
<aside
|
|
6504
|
+
class="kf-sidebar-shell__rail"
|
|
6505
|
+
[class.kf-sidebar-shell__rail--collapsed]="collapsed()"
|
|
6506
|
+
[class.kf-sidebar-shell__rail--mobile-open]="mobileOpen()"
|
|
6507
|
+
>
|
|
6508
|
+
<div class="kf-sidebar-shell__brand">
|
|
6509
|
+
<div class="kf-sidebar-shell__brand-full">
|
|
6510
|
+
<ng-content select="[kf-sidebar-brand]" />
|
|
6511
|
+
</div>
|
|
6512
|
+
<div class="kf-sidebar-shell__brand-collapsed">
|
|
6513
|
+
<ng-content select="[kf-sidebar-brand-collapsed]" />
|
|
6514
|
+
</div>
|
|
6515
|
+
|
|
6516
|
+
<button
|
|
6517
|
+
type="button"
|
|
6518
|
+
class="kf-sidebar-shell__mobile-close"
|
|
6519
|
+
aria-label="Cerrar menú"
|
|
6520
|
+
(click)="mobileOpen.set(false)"
|
|
6521
|
+
>
|
|
6522
|
+
<kf-icon name="x" size="md" />
|
|
6523
|
+
</button>
|
|
6524
|
+
</div>
|
|
6525
|
+
|
|
6526
|
+
<nav class="kf-sidebar-shell__nav">
|
|
6527
|
+
@for (item of navItems(); track item.href) {
|
|
6528
|
+
<kf-sidebar-nav-item
|
|
6529
|
+
[icon]="item.icon"
|
|
6530
|
+
[label]="item.label"
|
|
6531
|
+
[href]="item.href"
|
|
6532
|
+
[active]="!!item.active"
|
|
6533
|
+
[collapsed]="itemsCollapsed()"
|
|
6534
|
+
(click)="onNavItemClick($event, item)"
|
|
6535
|
+
/>
|
|
6536
|
+
}
|
|
6537
|
+
</nav>
|
|
6538
|
+
|
|
6539
|
+
<div class="kf-sidebar-shell__bottom">
|
|
6540
|
+
<button
|
|
6541
|
+
type="button"
|
|
6542
|
+
class="kf-sidebar-shell__collapse-toggle"
|
|
6543
|
+
[class.kf-sidebar-shell__collapse-toggle--collapsed]="itemsCollapsed()"
|
|
6544
|
+
[attr.aria-expanded]="!collapsed()"
|
|
6545
|
+
[attr.aria-label]="collapsed() ? 'Expandir menú' : 'Colapsar menú'"
|
|
6546
|
+
[kfTooltip]="itemsCollapsed() ? (collapsed() ? 'Expandir menú' : 'Colapsar menú') : ''"
|
|
6547
|
+
(click)="collapsed.set(!collapsed())"
|
|
6548
|
+
>
|
|
6549
|
+
<kf-icon [name]="collapsed() ? 'chevron-right' : 'chevron-left'" size="sm" />
|
|
6550
|
+
@if (!itemsCollapsed()) {
|
|
6551
|
+
<span class="kf-sidebar-shell__collapse-toggle-label">
|
|
6552
|
+
{{ collapsed() ? 'Expandir menú' : 'Colapsar menú' }}
|
|
6553
|
+
</span>
|
|
6554
|
+
}
|
|
6555
|
+
</button>
|
|
6556
|
+
|
|
6557
|
+
<div class="kf-sidebar-shell__footer">
|
|
6558
|
+
<ng-content select="[kf-sidebar-footer]" />
|
|
6559
|
+
</div>
|
|
6560
|
+
</div>
|
|
6561
|
+
</aside>
|
|
6562
|
+
|
|
6563
|
+
<div class="kf-sidebar-shell__topbar">
|
|
6564
|
+
<button
|
|
6565
|
+
type="button"
|
|
6566
|
+
class="kf-sidebar-shell__menu-toggle"
|
|
6567
|
+
aria-label="Abrir menú de navegación"
|
|
6568
|
+
(click)="mobileOpen.set(true)"
|
|
6569
|
+
>
|
|
6570
|
+
<kf-icon name="menu" size="md" />
|
|
6571
|
+
</button>
|
|
6572
|
+
<div class="kf-sidebar-shell__topbar-brand">
|
|
6573
|
+
<ng-content select="[kf-sidebar-brand-mobile]" />
|
|
6574
|
+
</div>
|
|
6575
|
+
</div>
|
|
6576
|
+
`, styles: [":host{display:contents}.kf-sidebar-shell__backdrop{display:none}.kf-sidebar-shell__rail{box-sizing:border-box;display:flex;flex-direction:column;position:relative;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{position:relative;display:flex;align-items:center;justify-content:center;margin-bottom:var(--kf-space-xl)}.kf-sidebar-shell__brand-full{display:flex;justify-content:center;width:100%}.kf-sidebar-shell__brand-full ::ng-deep img{display:block;height:1.5rem}.kf-sidebar-shell__brand-collapsed{display:none}.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:block}.kf-sidebar-shell__mobile-close{display:none;margin-left:auto;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__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:var(--kf-shadow-lg)}.kf-sidebar-shell__rail--mobile-open{transform:translate(0)}.kf-sidebar-shell__rail--collapsed{width:284px;max-width:86vw}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-full{display:block}.kf-sidebar-shell__rail--collapsed .kf-sidebar-shell__brand-collapsed,.kf-sidebar-shell__collapse-toggle{display:none}.kf-sidebar-shell__mobile-close{display:flex}.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"] }]
|
|
6577
|
+
}], 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: [{
|
|
6578
|
+
type: HostListener,
|
|
6579
|
+
args: ['document:keydown.escape']
|
|
6580
|
+
}] } });
|
|
6581
|
+
|
|
4887
6582
|
/**
|
|
4888
6583
|
* Indicador numérico compacto (página 34): label + valor grande, con un
|
|
4889
6584
|
* ícono a la derecha del label — más chico y sin badge de esquina que
|
|
@@ -5152,109 +6847,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
5152
6847
|
`, styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-tabs{position:relative;display:flex;align-items:stretch;gap:var(--kf-space-md);border-bottom:1px solid var(--kf-color-border)}.kf-tabs__tab{display:inline-flex;align-items:center;gap:var(--kf-space-xs);font:inherit;font-size:.9rem;color:var(--kf-color-text-muted);background:transparent;border:none;padding:var(--kf-space-sm) 0;cursor:pointer;transition:color .14s}.kf-tabs__tab:hover:not(.kf-tabs__tab--active){color:var(--kf-color-text)}.kf-tabs__tab:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus);border-radius:var(--kf-radius-xs)}.kf-tabs__tab--active{color:var(--kf-color-primary);font-size:1rem;font-weight:700}.kf-tabs__indicator{position:absolute;bottom:-1px;left:0;height:2px;background:var(--kf-color-primary);border-radius:2px 2px 0 0;pointer-events:none;transition:transform .28s cubic-bezier(.32,.72,0,1),width .28s cubic-bezier(.32,.72,0,1),opacity .14s}@media(prefers-reduced-motion:reduce){.kf-tabs__indicator{transition:opacity .14s}}\n"] }]
|
|
5153
6848
|
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
|
|
5154
6849
|
|
|
5155
|
-
/**
|
|
5156
|
-
* Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
|
|
5157
|
-
* texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
|
|
5158
|
-
* ve). Antes de este componente, cada pantalla que consume la librería
|
|
5159
|
-
* definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
|
|
5160
|
-
* (título de página, subtítulo, texto secundario) — auditoría sobre
|
|
5161
|
-
* `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
|
|
5162
|
-
* home sin ningún paso de escala en común. `variant` resuelve eso contra
|
|
5163
|
-
* la escala de `tokens/base.css` (`--kf-font-size-*`).
|
|
5164
|
-
*
|
|
5165
|
-
* `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
|
|
5166
|
-
* escape hatch de color libre. Si un caso necesita un color que `tone` no
|
|
5167
|
-
* cubre, es una discusión de design system, no una prop nueva acá.
|
|
5168
|
-
*
|
|
5169
|
-
* <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
|
|
5170
|
-
* <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
|
|
5171
|
-
* <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
|
|
5172
|
-
*/
|
|
5173
|
-
class Text {
|
|
5174
|
-
/** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
|
|
5175
|
-
as = input('p', /* @ts-ignore */
|
|
5176
|
-
...(ngDevMode ? [{ debugName: "as" }] : /* istanbul ignore next */ []));
|
|
5177
|
-
variant = input('body', /* @ts-ignore */
|
|
5178
|
-
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
5179
|
-
tone = input('neutral', /* @ts-ignore */
|
|
5180
|
-
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
5181
|
-
/**
|
|
5182
|
-
* Copia de `as` para uso dentro del template: `as` es palabra reservada
|
|
5183
|
-
* en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
|
|
5184
|
-
* no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
|
|
5185
|
-
* atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
|
|
5186
|
-
*/
|
|
5187
|
-
tag = computed(() => this.as(), /* @ts-ignore */
|
|
5188
|
-
...(ngDevMode ? [{ debugName: "tag" }] : /* istanbul ignore next */ []));
|
|
5189
|
-
hostClass() {
|
|
5190
|
-
const classes = [`kf-text--${this.variant()}`];
|
|
5191
|
-
if (this.tone() !== 'neutral') {
|
|
5192
|
-
classes.push(`kf-text--${this.tone()}`);
|
|
5193
|
-
}
|
|
5194
|
-
return classes.join(' ');
|
|
5195
|
-
}
|
|
5196
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Text, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5197
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: Text, isStandalone: true, selector: "kf-text", inputs: { as: { classPropertyName: "as", publicName: "as", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
5198
|
-
<!--
|
|
5199
|
-
Una sola proyección compartida (ng-template + ngTemplateOutlet) en vez
|
|
5200
|
-
de un <ng-content> por cada rama del @switch: Angular solo conecta la
|
|
5201
|
-
proyección real del consumidor a la PRIMERA instancia de <ng-content>
|
|
5202
|
-
que encuentra en el árbol de compilación — con un <ng-content> por
|
|
5203
|
-
rama, todas menos una quedan vacías en runtime (bug detectado por
|
|
5204
|
-
Text.spec.ts, no algo documentado a simple vista en los docs de Angular).
|
|
5205
|
-
-->
|
|
5206
|
-
<ng-template #projected><ng-content /></ng-template>
|
|
5207
|
-
@switch (tag()) {
|
|
5208
|
-
@case ('h1') {
|
|
5209
|
-
<h1 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h1>
|
|
5210
|
-
}
|
|
5211
|
-
@case ('h2') {
|
|
5212
|
-
<h2 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h2>
|
|
5213
|
-
}
|
|
5214
|
-
@case ('h3') {
|
|
5215
|
-
<h3 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h3>
|
|
5216
|
-
}
|
|
5217
|
-
@case ('span') {
|
|
5218
|
-
<span [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></span>
|
|
5219
|
-
}
|
|
5220
|
-
@default {
|
|
5221
|
-
<p [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></p>
|
|
5222
|
-
}
|
|
5223
|
-
}
|
|
5224
|
-
`, isInline: true, styles: [":host{display:contents}h1,h2,h3,p,span{margin:0;font-family:var(--kf-font-body);color:var(--kf-color-text)}.kf-text--title-lg{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-lg);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-md{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-sm{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-text--body{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body)}.kf-text--body-muted{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--caption{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--ok{color:var(--kf-color-success-strong)}.kf-text--warn{color:var(--kf-color-warning-strong)}.kf-text--error{color:var(--kf-color-error-strong)}.kf-text--info{color:var(--kf-color-info-strong)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
5225
|
-
}
|
|
5226
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Text, decorators: [{
|
|
5227
|
-
type: Component,
|
|
5228
|
-
args: [{ selector: 'kf-text', imports: [NgTemplateOutlet], template: `
|
|
5229
|
-
<!--
|
|
5230
|
-
Una sola proyección compartida (ng-template + ngTemplateOutlet) en vez
|
|
5231
|
-
de un <ng-content> por cada rama del @switch: Angular solo conecta la
|
|
5232
|
-
proyección real del consumidor a la PRIMERA instancia de <ng-content>
|
|
5233
|
-
que encuentra en el árbol de compilación — con un <ng-content> por
|
|
5234
|
-
rama, todas menos una quedan vacías en runtime (bug detectado por
|
|
5235
|
-
Text.spec.ts, no algo documentado a simple vista en los docs de Angular).
|
|
5236
|
-
-->
|
|
5237
|
-
<ng-template #projected><ng-content /></ng-template>
|
|
5238
|
-
@switch (tag()) {
|
|
5239
|
-
@case ('h1') {
|
|
5240
|
-
<h1 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h1>
|
|
5241
|
-
}
|
|
5242
|
-
@case ('h2') {
|
|
5243
|
-
<h2 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h2>
|
|
5244
|
-
}
|
|
5245
|
-
@case ('h3') {
|
|
5246
|
-
<h3 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h3>
|
|
5247
|
-
}
|
|
5248
|
-
@case ('span') {
|
|
5249
|
-
<span [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></span>
|
|
5250
|
-
}
|
|
5251
|
-
@default {
|
|
5252
|
-
<p [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></p>
|
|
5253
|
-
}
|
|
5254
|
-
}
|
|
5255
|
-
`, styles: [":host{display:contents}h1,h2,h3,p,span{margin:0;font-family:var(--kf-font-body);color:var(--kf-color-text)}.kf-text--title-lg{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-lg);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-md{font-family:var(--kf-font-title);font-size:var(--kf-font-size-title-md);font-weight:var(--kf-font-weight-title);color:var(--kf-color-ink)}.kf-text--title-sm{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-text--body{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body)}.kf-text--body-muted{font-size:var(--kf-font-size-body);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--caption{font-size:var(--kf-font-size-caption);font-weight:var(--kf-font-weight-body);color:var(--kf-color-text-muted)}.kf-text--ok{color:var(--kf-color-success-strong)}.kf-text--warn{color:var(--kf-color-warning-strong)}.kf-text--error{color:var(--kf-color-error-strong)}.kf-text--info{color:var(--kf-color-info-strong)}\n"] }]
|
|
5256
|
-
}], propDecorators: { as: [{ type: i0.Input, args: [{ isSignal: true, alias: "as", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
|
|
5257
|
-
|
|
5258
6850
|
/** Fixed icon per tone (page 20 of the design system) — not configurable per call. */
|
|
5259
6851
|
const TOAST_ICON = {
|
|
5260
6852
|
success: 'check-circle',
|
|
@@ -5604,5 +7196,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
5604
7196
|
* Generated bundle index. Do not edit.
|
|
5605
7197
|
*/
|
|
5606
7198
|
|
|
5607
|
-
export { ActionTile, AdminBadge, Alert, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, VehicleHeader, ViewToggle };
|
|
7199
|
+
export { ActionTile, AdminBadge, Alert, AuthLayout, Avatar, Badge, Breadcrumb, Button, Card, Carousel, CarouselSlide, Checkbox, Chip, CompanySelector, ContentSlot, ContentState, DataTable, Divider, Drawer, DrawerLogout, DrawerSession, Dropdown, DropdownItem, DropdownPanel, EmptyState, FeatureCard, Field, FieldLabel, FilterChips, FolderTabs, FolderTabsPanel, Footer, Icon, Input, Link, MetricCard, Modal, MultiSelect, PageHeader, Pagination, Panel, PortalHeader, ReserveBlock, SectionHeader, Segmented, Select, SidebarNavItem, SidebarShell, Skeleton, Spinner, StatTile, StatusPill, Tabs, Text, ToastHost, ToastService, TooltipDirective, VehicleHeader, ViewToggle };
|
|
5608
7200
|
//# sourceMappingURL=kaufmann-lab-ui.mjs.map
|