@kaufmann-lab/ui 0.1.9 → 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 +1842 -438
- 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 +617 -139
|
@@ -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, Directive, 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
|
-
* comportamiento en la pantalla actual (cerrar sesión, ejecutar un
|
|
2901
|
-
* comando), no navegan a otra URL.
|
|
3522
|
+
* (no un `<a>`): dispara comportamiento en la pantalla actual, no navega.
|
|
2902
3523
|
*
|
|
2903
|
-
*
|
|
3524
|
+
* `icon` es opcional — sin él, el ítem es solo texto.
|
|
3525
|
+
*
|
|
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;
|
|
@@ -3354,15 +3999,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3354
3999
|
* atributo porque el panel no necesita comportamiento propio, solo marcar
|
|
3355
4000
|
* su presencia).
|
|
3356
4001
|
*
|
|
3357
|
-
* <div
|
|
4002
|
+
* <div kfFolderTabsPanel>...</div>
|
|
3358
4003
|
*/
|
|
3359
4004
|
class FolderTabsPanel {
|
|
3360
4005
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FolderTabsPanel, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3361
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: FolderTabsPanel, isStandalone: true, selector: "[
|
|
4006
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.0", type: FolderTabsPanel, isStandalone: true, selector: "[kfFolderTabsPanel]", ngImport: i0 });
|
|
3362
4007
|
}
|
|
3363
4008
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FolderTabsPanel, decorators: [{
|
|
3364
4009
|
type: Directive,
|
|
3365
|
-
args: [{ selector: '[
|
|
4010
|
+
args: [{ selector: '[kfFolderTabsPanel]' }]
|
|
3366
4011
|
}] });
|
|
3367
4012
|
/**
|
|
3368
4013
|
* Tabs con forma de "carpeta": el borde superior queda redondeado y el borde
|
|
@@ -3404,7 +4049,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3404
4049
|
* evita que cada consumidor repita esas reglas a mano.
|
|
3405
4050
|
*
|
|
3406
4051
|
* <kf-folder-tabs [options]="tabs" [value]="active" (valueChange)="active = $event">
|
|
3407
|
-
* <div
|
|
4052
|
+
* <div kfFolderTabsPanel>contenido de la pestaña activa</div>
|
|
3408
4053
|
* </kf-folder-tabs>
|
|
3409
4054
|
*
|
|
3410
4055
|
* <!-- o, sin contenido, como antes: -->
|
|
@@ -3418,7 +4063,7 @@ class FolderTabs {
|
|
|
3418
4063
|
valueChange = output();
|
|
3419
4064
|
/**
|
|
3420
4065
|
* Detecta si el consumidor proyectó contenido marcado con
|
|
3421
|
-
* `
|
|
4066
|
+
* `kfFolderTabsPanel` — sin `.required`, así que es `undefined`
|
|
3422
4067
|
* cuando no hay nada proyectado (mismo patrón que usa `kf-dropdown` con
|
|
3423
4068
|
* `contentChild(DropdownPanel)` para saber si hay un panel que
|
|
3424
4069
|
* attachear). `hasPanel()` decide si el wrapper del panel participa del
|
|
@@ -3488,7 +4133,7 @@ class FolderTabs {
|
|
|
3488
4133
|
</div>
|
|
3489
4134
|
|
|
3490
4135
|
<div class="kf-folder-tabs__panel" [class.kf-folder-tabs__panel--empty]="!hasPanel()">
|
|
3491
|
-
<ng-content select="[
|
|
4136
|
+
<ng-content select="[kfFolderTabsPanel]" />
|
|
3492
4137
|
</div>
|
|
3493
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"] }] });
|
|
3494
4139
|
}
|
|
@@ -3518,11 +4163,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
3518
4163
|
</div>
|
|
3519
4164
|
|
|
3520
4165
|
<div class="kf-folder-tabs__panel" [class.kf-folder-tabs__panel--empty]="!hasPanel()">
|
|
3521
|
-
<ng-content select="[
|
|
4166
|
+
<ng-content select="[kfFolderTabsPanel]" />
|
|
3522
4167
|
</div>
|
|
3523
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"] }]
|
|
3524
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 }] }] } });
|
|
3525
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 }] }] } });
|
|
4233
|
+
|
|
3526
4234
|
/**
|
|
3527
4235
|
* Input de formulario apilado: label arriba (en mayúsculas vía CSS), campo con
|
|
3528
4236
|
* fondo gris que pasa a blanco con anillo de foco azul al enfocar.
|
|
@@ -4073,18 +4781,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4073
4781
|
|
|
4074
4782
|
let nextId = 0;
|
|
4075
4783
|
/**
|
|
4076
|
-
* Dropdown de selección múltiple con checkboxes internos
|
|
4077
|
-
*
|
|
4078
|
-
*
|
|
4079
|
-
*
|
|
4080
|
-
*
|
|
4081
|
-
*
|
|
4082
|
-
*
|
|
4083
|
-
*
|
|
4084
|
-
*
|
|
4085
|
-
*
|
|
4086
|
-
*
|
|
4087
|
-
*
|
|
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()`).
|
|
4088
4803
|
*
|
|
4089
4804
|
* <kf-multi-select
|
|
4090
4805
|
* label="Sucursales"
|
|
@@ -4107,18 +4822,43 @@ class MultiSelect {
|
|
|
4107
4822
|
placeholder = input('Seleccionar', /* @ts-ignore */
|
|
4108
4823
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
4109
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 */ []));
|
|
4110
4835
|
valuesChange = output();
|
|
4111
4836
|
instanceId = `kf-multi-select-${nextId++}`;
|
|
4112
4837
|
triggerId = `${this.instanceId}-trigger`;
|
|
4113
4838
|
listboxId = `${this.instanceId}-listbox`;
|
|
4114
4839
|
isOpen = signal(false, /* @ts-ignore */
|
|
4115
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 */ []));
|
|
4116
4845
|
selectedCount = computed(() => this.values().length, /* @ts-ignore */
|
|
4117
4846
|
...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
|
|
4118
4847
|
allSelected = computed(() => this.options().length > 0 && this.values().length === this.options().length, /* @ts-ignore */
|
|
4119
4848
|
...(ngDevMode ? [{ debugName: "allSelected" }] : /* istanbul ignore next */ []));
|
|
4120
4849
|
isPartiallySelected = computed(() => this.values().length > 0 && !this.allSelected(), /* @ts-ignore */
|
|
4121
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 */ []));
|
|
4122
4862
|
/**
|
|
4123
4863
|
* Texto del disparador: placeholder si no hay selección, o los nombres
|
|
4124
4864
|
* seleccionados unidos por coma (el conteo va aparte, en el badge). Con
|
|
@@ -4136,7 +4876,37 @@ class MultiSelect {
|
|
|
4136
4876
|
return selectedLabels.join(', ');
|
|
4137
4877
|
}, /* @ts-ignore */
|
|
4138
4878
|
...(ngDevMode ? [{ debugName: "summaryText" }] : /* istanbul ignore next */ []));
|
|
4139
|
-
|
|
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
|
+
}
|
|
4140
4910
|
isSelected(value) {
|
|
4141
4911
|
return this.values().includes(value);
|
|
4142
4912
|
}
|
|
@@ -4153,20 +4923,100 @@ class MultiSelect {
|
|
|
4153
4923
|
: [...current, value];
|
|
4154
4924
|
this.valuesChange.emit(next);
|
|
4155
4925
|
}
|
|
4156
|
-
/**
|
|
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
|
+
*/
|
|
4157
4934
|
toggleAll() {
|
|
4158
4935
|
this.valuesChange.emit(this.allSelected() ? [] : this.options().map((option) => option.value));
|
|
4159
4936
|
}
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
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);
|
|
4163
4953
|
}
|
|
4164
4954
|
}
|
|
4165
4955
|
onEscape() {
|
|
4166
4956
|
this.isOpen.set(false);
|
|
4167
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
|
+
}
|
|
4168
5018
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4169
|
-
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: `
|
|
4170
5020
|
@if (label()) {
|
|
4171
5021
|
<kf-field-label [for]="triggerId">{{ label() }}</kf-field-label>
|
|
4172
5022
|
}
|
|
@@ -4196,55 +5046,78 @@ class MultiSelect {
|
|
|
4196
5046
|
aria-hidden="true"
|
|
4197
5047
|
></span>
|
|
4198
5048
|
</button>
|
|
4199
|
-
|
|
4200
|
-
<
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
(click)="toggleAll()"
|
|
4214
|
-
(keydown.enter)="toggleAll()"
|
|
4215
|
-
(keydown.space)="$event.preventDefault(); toggleAll()"
|
|
4216
|
-
>
|
|
4217
|
-
<span
|
|
4218
|
-
class="kf-multi-select__checkbox"
|
|
4219
|
-
[class.kf-multi-select__checkbox--checked]="allSelected()"
|
|
4220
|
-
[class.kf-multi-select__checkbox--indeterminate]="isPartiallySelected()"
|
|
4221
|
-
></span>
|
|
4222
|
-
<span class="kf-multi-select__option-label">Todas</span>
|
|
4223
|
-
</li>
|
|
4224
|
-
}
|
|
4225
|
-
@for (option of options(); track option.value) {
|
|
4226
|
-
<li
|
|
4227
|
-
class="kf-multi-select__option"
|
|
4228
|
-
[class.kf-multi-select__option--selected]="isSelected(option.value)"
|
|
4229
|
-
role="option"
|
|
4230
|
-
tabindex="0"
|
|
4231
|
-
[attr.aria-selected]="isSelected(option.value)"
|
|
4232
|
-
(click)="toggleOption(option.value)"
|
|
4233
|
-
(keydown.enter)="toggleOption(option.value)"
|
|
4234
|
-
(keydown.space)="$event.preventDefault(); toggleOption(option.value)"
|
|
4235
|
-
>
|
|
4236
|
-
<span
|
|
4237
|
-
class="kf-multi-select__checkbox"
|
|
4238
|
-
[class.kf-multi-select__checkbox--checked]="isSelected(option.value)"
|
|
4239
|
-
></span>
|
|
4240
|
-
<span class="kf-multi-select__option-label">{{ option.label }}</span>
|
|
4241
|
-
</li>
|
|
4242
|
-
} @empty {
|
|
4243
|
-
<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
|
+
/>
|
|
4244
5063
|
}
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
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"] }] });
|
|
4248
5121
|
}
|
|
4249
5122
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MultiSelect, decorators: [{
|
|
4250
5123
|
type: Component,
|
|
@@ -4278,63 +5151,191 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4278
5151
|
aria-hidden="true"
|
|
4279
5152
|
></span>
|
|
4280
5153
|
</button>
|
|
4281
|
-
|
|
4282
|
-
<
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
(click)="toggleAll()"
|
|
4296
|
-
(keydown.enter)="toggleAll()"
|
|
4297
|
-
(keydown.space)="$event.preventDefault(); toggleAll()"
|
|
4298
|
-
>
|
|
4299
|
-
<span
|
|
4300
|
-
class="kf-multi-select__checkbox"
|
|
4301
|
-
[class.kf-multi-select__checkbox--checked]="allSelected()"
|
|
4302
|
-
[class.kf-multi-select__checkbox--indeterminate]="isPartiallySelected()"
|
|
4303
|
-
></span>
|
|
4304
|
-
<span class="kf-multi-select__option-label">Todas</span>
|
|
4305
|
-
</li>
|
|
4306
|
-
}
|
|
4307
|
-
@for (option of options(); track option.value) {
|
|
4308
|
-
<li
|
|
4309
|
-
class="kf-multi-select__option"
|
|
4310
|
-
[class.kf-multi-select__option--selected]="isSelected(option.value)"
|
|
4311
|
-
role="option"
|
|
4312
|
-
tabindex="0"
|
|
4313
|
-
[attr.aria-selected]="isSelected(option.value)"
|
|
4314
|
-
(click)="toggleOption(option.value)"
|
|
4315
|
-
(keydown.enter)="toggleOption(option.value)"
|
|
4316
|
-
(keydown.space)="$event.preventDefault(); toggleOption(option.value)"
|
|
4317
|
-
>
|
|
4318
|
-
<span
|
|
4319
|
-
class="kf-multi-select__checkbox"
|
|
4320
|
-
[class.kf-multi-select__checkbox--checked]="isSelected(option.value)"
|
|
4321
|
-
></span>
|
|
4322
|
-
<span class="kf-multi-select__option-label">{{ option.label }}</span>
|
|
4323
|
-
</li>
|
|
4324
|
-
} @empty {
|
|
4325
|
-
<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
|
+
/>
|
|
4326
5168
|
}
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
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: [{
|
|
4334
5227
|
type: HostListener,
|
|
4335
5228
|
args: ['document:keydown.escape']
|
|
4336
5229
|
}] } });
|
|
4337
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
|
+
|
|
4338
5339
|
/**
|
|
4339
5340
|
* Bloque de título al tope de una página: chip de ícono opcional, título (h1),
|
|
4340
5341
|
* subtítulo opcional y un área de acciones opcional proyectada vía
|
|
@@ -4346,6 +5347,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4346
5347
|
* </kf-page-header>
|
|
4347
5348
|
*
|
|
4348
5349
|
* <kf-page-header icon="settings" title="Configuración" />
|
|
5350
|
+
*
|
|
5351
|
+
* <kf-page-header size="xl" title="Bienvenido de nuevo" />
|
|
4349
5352
|
*/
|
|
4350
5353
|
class PageHeader {
|
|
4351
5354
|
icon = input(undefined, /* @ts-ignore */
|
|
@@ -4354,8 +5357,12 @@ class PageHeader {
|
|
|
4354
5357
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
4355
5358
|
subtitle = input(undefined, /* @ts-ignore */
|
|
4356
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 */ []));
|
|
4357
5364
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PageHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4358
|
-
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: `
|
|
4359
5366
|
@if (icon(); as iconName) {
|
|
4360
5367
|
<span class="kf-page-header__icon">
|
|
4361
5368
|
<kf-icon [name]="iconName" size="lg" />
|
|
@@ -4363,7 +5370,9 @@ class PageHeader {
|
|
|
4363
5370
|
}
|
|
4364
5371
|
|
|
4365
5372
|
<div class="kf-page-header__heading">
|
|
4366
|
-
<h1 class="kf-page-header__title"
|
|
5373
|
+
<kf-text as="h1" class="kf-page-header__title" [variant]="titleVariant()">
|
|
5374
|
+
{{ title() }}
|
|
5375
|
+
</kf-text>
|
|
4367
5376
|
@if (subtitle()) {
|
|
4368
5377
|
<p class="kf-page-header__subtitle">{{ subtitle() }}</p>
|
|
4369
5378
|
}
|
|
@@ -4372,11 +5381,11 @@ class PageHeader {
|
|
|
4372
5381
|
<div class="kf-page-header__actions">
|
|
4373
5382
|
<ng-content select="[actions]" />
|
|
4374
5383
|
</div>
|
|
4375
|
-
`, 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"] }] });
|
|
4376
5385
|
}
|
|
4377
5386
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PageHeader, decorators: [{
|
|
4378
5387
|
type: Component,
|
|
4379
|
-
args: [{ selector: 'kf-page-header', imports: [Icon], template: `
|
|
5388
|
+
args: [{ selector: 'kf-page-header', imports: [Icon, Text], template: `
|
|
4380
5389
|
@if (icon(); as iconName) {
|
|
4381
5390
|
<span class="kf-page-header__icon">
|
|
4382
5391
|
<kf-icon [name]="iconName" size="lg" />
|
|
@@ -4384,7 +5393,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4384
5393
|
}
|
|
4385
5394
|
|
|
4386
5395
|
<div class="kf-page-header__heading">
|
|
4387
|
-
<h1 class="kf-page-header__title"
|
|
5396
|
+
<kf-text as="h1" class="kf-page-header__title" [variant]="titleVariant()">
|
|
5397
|
+
{{ title() }}
|
|
5398
|
+
</kf-text>
|
|
4388
5399
|
@if (subtitle()) {
|
|
4389
5400
|
<p class="kf-page-header__subtitle">{{ subtitle() }}</p>
|
|
4390
5401
|
}
|
|
@@ -4393,8 +5404,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4393
5404
|
<div class="kf-page-header__actions">
|
|
4394
5405
|
<ng-content select="[actions]" />
|
|
4395
5406
|
</div>
|
|
4396
|
-
`, 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{
|
|
4397
|
-
}], 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 }] }] } });
|
|
4398
5409
|
|
|
4399
5410
|
const SIBLINGS = 1;
|
|
4400
5411
|
/**
|
|
@@ -4565,6 +5576,13 @@ function buildPaginationItems(current, total) {
|
|
|
4565
5576
|
* <kf-panel title="Mis documentos por pagar" actionLabel="Ir a pagos" actionHref="/pagos">
|
|
4566
5577
|
* <kf-action-tile-row icon="download" title="Pagos" description="Revisa tu estado de cuenta y realiza pagos." />
|
|
4567
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.
|
|
4568
5586
|
*/
|
|
4569
5587
|
class Panel {
|
|
4570
5588
|
title = input.required(/* @ts-ignore */
|
|
@@ -4590,7 +5608,7 @@ class Panel {
|
|
|
4590
5608
|
<ng-content />
|
|
4591
5609
|
</div>
|
|
4592
5610
|
</kf-card>
|
|
4593
|
-
`, 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"] }] });
|
|
4594
5612
|
}
|
|
4595
5613
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Panel, decorators: [{
|
|
4596
5614
|
type: Component,
|
|
@@ -4610,7 +5628,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4610
5628
|
<ng-content />
|
|
4611
5629
|
</div>
|
|
4612
5630
|
</kf-card>
|
|
4613
|
-
`, 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"] }]
|
|
4614
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 }] }] } });
|
|
4615
5633
|
|
|
4616
5634
|
/**
|
|
@@ -4629,21 +5647,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4629
5647
|
*
|
|
4630
5648
|
* Bajo los 768px, el `<nav>` deja de mostrarse en línea junto al logo y
|
|
4631
5649
|
* colapsa detrás de un botón de menú (ícono `menu`/`x`) que despliega un
|
|
4632
|
-
* drawer
|
|
4633
|
-
*
|
|
4634
|
-
*
|
|
4635
|
-
*
|
|
4636
|
-
*
|
|
4637
|
-
*
|
|
4638
|
-
*
|
|
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.
|
|
4639
5673
|
*
|
|
4640
5674
|
* <kf-portal-header
|
|
4641
5675
|
* [isAdmin]="true"
|
|
4642
5676
|
* [adminActions]="[{ label: 'Consultar deuda clientes', href: '/deuda' }, { label: 'Cambiar DNI', href: '/dni' }]"
|
|
4643
|
-
* [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' }]"
|
|
4644
5678
|
* userInitials="TS"
|
|
4645
5679
|
* >
|
|
4646
|
-
* <
|
|
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()" />
|
|
4647
5687
|
* </kf-portal-header>
|
|
4648
5688
|
*/
|
|
4649
5689
|
class PortalHeader {
|
|
@@ -4657,13 +5697,11 @@ class PortalHeader {
|
|
|
4657
5697
|
userInitials = input.required(/* @ts-ignore */
|
|
4658
5698
|
...(ngDevMode ? [{ debugName: "userInitials" }] : /* istanbul ignore next */ []));
|
|
4659
5699
|
sessionClick = output();
|
|
4660
|
-
/** Visibilidad del drawer de navegación mobile (bajo los 768px).
|
|
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`. */
|
|
4661
5703
|
mobileMenuOpen = signal(false, /* @ts-ignore */
|
|
4662
5704
|
...(ngDevMode ? [{ debugName: "mobileMenuOpen" }] : /* istanbul ignore next */ []));
|
|
4663
|
-
/** Cierra el drawer con Escape — mismo patrón que cualquier drawer/modal. */
|
|
4664
|
-
onEscape() {
|
|
4665
|
-
this.mobileMenuOpen.set(false);
|
|
4666
|
-
}
|
|
4667
5705
|
/**
|
|
4668
5706
|
* `ElementRef` real del botón de sesión (el avatar en la esquina
|
|
4669
5707
|
* derecha), no de todo el header. Pensado para anclar un `kf-dropdown`
|
|
@@ -4676,7 +5714,7 @@ class PortalHeader {
|
|
|
4676
5714
|
*/
|
|
4677
5715
|
sessionButtonRef = viewChild.required('sessionButton', { ...(ngDevMode ? { debugName: "sessionButtonRef" } : /* istanbul ignore next */ {}), read: ElementRef });
|
|
4678
5716
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4679
|
-
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" },
|
|
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: `
|
|
4680
5718
|
@if (isAdmin()) {
|
|
4681
5719
|
<div class="kf-portal-header__admin-bar">
|
|
4682
5720
|
<kf-admin-badge inline />
|
|
@@ -4722,16 +5760,10 @@ class PortalHeader {
|
|
|
4722
5760
|
</button>
|
|
4723
5761
|
</div>
|
|
4724
5762
|
|
|
4725
|
-
|
|
4726
|
-
<div
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
<nav
|
|
4730
|
-
class="kf-portal-header__drawer"
|
|
4731
|
-
[class.kf-portal-header__drawer--open]="mobileMenuOpen()"
|
|
4732
|
-
role="menu"
|
|
4733
|
-
[attr.aria-hidden]="!mobileMenuOpen()"
|
|
4734
|
-
>
|
|
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>
|
|
4735
5767
|
@for (item of navItems(); track item.label) {
|
|
4736
5768
|
<a
|
|
4737
5769
|
class="kf-portal-header__mobile-nav-item"
|
|
@@ -4739,16 +5771,22 @@ class PortalHeader {
|
|
|
4739
5771
|
[href]="item.href"
|
|
4740
5772
|
(click)="mobileMenuOpen.set(false)"
|
|
4741
5773
|
>
|
|
4742
|
-
|
|
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" />
|
|
4743
5779
|
</a>
|
|
4744
5780
|
}
|
|
4745
|
-
<
|
|
4746
|
-
|
|
4747
|
-
|
|
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"] }] });
|
|
4748
5786
|
}
|
|
4749
5787
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: PortalHeader, decorators: [{
|
|
4750
5788
|
type: Component,
|
|
4751
|
-
args: [{ selector: 'kf-portal-header', imports: [AdminBadge, Avatar, Icon], template: `
|
|
5789
|
+
args: [{ selector: 'kf-portal-header', imports: [AdminBadge, Avatar, Icon, Drawer], template: `
|
|
4752
5790
|
@if (isAdmin()) {
|
|
4753
5791
|
<div class="kf-portal-header__admin-bar">
|
|
4754
5792
|
<kf-admin-badge inline />
|
|
@@ -4794,16 +5832,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4794
5832
|
</button>
|
|
4795
5833
|
</div>
|
|
4796
5834
|
|
|
4797
|
-
|
|
4798
|
-
<div
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
<nav
|
|
4802
|
-
class="kf-portal-header__drawer"
|
|
4803
|
-
[class.kf-portal-header__drawer--open]="mobileMenuOpen()"
|
|
4804
|
-
role="menu"
|
|
4805
|
-
[attr.aria-hidden]="!mobileMenuOpen()"
|
|
4806
|
-
>
|
|
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>
|
|
4807
5839
|
@for (item of navItems(); track item.label) {
|
|
4808
5840
|
<a
|
|
4809
5841
|
class="kf-portal-header__mobile-nav-item"
|
|
@@ -4811,16 +5843,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4811
5843
|
[href]="item.href"
|
|
4812
5844
|
(click)="mobileMenuOpen.set(false)"
|
|
4813
5845
|
>
|
|
4814
|
-
|
|
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" />
|
|
4815
5851
|
</a>
|
|
4816
5852
|
}
|
|
4817
|
-
<
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
}], sessionButtonRef: [{ type: i0.ViewChild, args: ['sessionButton', { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
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 }] }] } });
|
|
4824
5859
|
|
|
4825
5860
|
/**
|
|
4826
5861
|
* Estado vacío de reservas en taller con llamada a agendar (página 36):
|
|
@@ -4833,7 +5868,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4833
5868
|
* invitación + las acciones como un bloque compuesto propio del dominio
|
|
4834
5869
|
* "taller", no un placeholder de una sola pieza.
|
|
4835
5870
|
*
|
|
4836
|
-
*
|
|
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
|
|
4837
5877
|
* title="¿Necesitas mantención?" description="Tenemos la mejor red de talleres."
|
|
4838
5878
|
* actionLabel="Agenda tu reserva aquí" actionHref="/taller/agendar">
|
|
4839
5879
|
* <kf-button actions icon="calendar">Reservar hora</kf-button>
|
|
@@ -4851,13 +5891,15 @@ class ReserveBlock {
|
|
|
4851
5891
|
...(ngDevMode ? [undefined, { debugName: "actionLabel" }] : /* istanbul ignore next */ []));
|
|
4852
5892
|
actionHref = input(/* @ts-ignore */
|
|
4853
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 });
|
|
4854
5896
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ReserveBlock, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4855
|
-
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: `
|
|
4856
5898
|
<div class="kf-reserve-block__empty">
|
|
4857
5899
|
<kf-icon name="calendar" size="sm" />
|
|
4858
5900
|
<span>{{ emptyLabel() }}</span>
|
|
4859
5901
|
</div>
|
|
4860
|
-
<div class="kf-reserve-block__invite">
|
|
5902
|
+
<div class="kf-reserve-block__invite" [class.kf-reserve-block__invite--grow]="grow()">
|
|
4861
5903
|
<span class="kf-reserve-block__invite-icon">
|
|
4862
5904
|
<kf-icon name="tool" size="md" />
|
|
4863
5905
|
</span>
|
|
@@ -4877,7 +5919,7 @@ class ReserveBlock {
|
|
|
4877
5919
|
<div class="kf-reserve-block__actions">
|
|
4878
5920
|
<ng-content select="[actions]" />
|
|
4879
5921
|
</div>
|
|
4880
|
-
`, 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"] }] });
|
|
4881
5923
|
}
|
|
4882
5924
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ReserveBlock, decorators: [{
|
|
4883
5925
|
type: Component,
|
|
@@ -4886,7 +5928,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4886
5928
|
<kf-icon name="calendar" size="sm" />
|
|
4887
5929
|
<span>{{ emptyLabel() }}</span>
|
|
4888
5930
|
</div>
|
|
4889
|
-
<div class="kf-reserve-block__invite">
|
|
5931
|
+
<div class="kf-reserve-block__invite" [class.kf-reserve-block__invite--grow]="grow()">
|
|
4890
5932
|
<span class="kf-reserve-block__invite-icon">
|
|
4891
5933
|
<kf-icon name="tool" size="md" />
|
|
4892
5934
|
</span>
|
|
@@ -4906,8 +5948,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
4906
5948
|
<div class="kf-reserve-block__actions">
|
|
4907
5949
|
<ng-content select="[actions]" />
|
|
4908
5950
|
</div>
|
|
4909
|
-
`, 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:
|
|
4910
|
-
}], 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 }] }] } });
|
|
4911
5953
|
|
|
4912
5954
|
/**
|
|
4913
5955
|
* Título de sub-sección con chip de ícono, pensado para anidarse dentro de
|
|
@@ -5072,6 +6114,471 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
5072
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"] }]
|
|
5073
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"] }] } });
|
|
5074
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
|
+
|
|
5075
6582
|
/**
|
|
5076
6583
|
* Indicador numérico compacto (página 34): label + valor grande, con un
|
|
5077
6584
|
* ícono a la derecha del label — más chico y sin badge de esquina que
|
|
@@ -5340,109 +6847,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
5340
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"] }]
|
|
5341
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 }] }] } });
|
|
5342
6849
|
|
|
5343
|
-
/**
|
|
5344
|
-
* Tipografía unificada — separa la etiqueta semántica (`as`, qué es el
|
|
5345
|
-
* texto para el DOM/accesibilidad) del estilo visual (`variant`, cómo se
|
|
5346
|
-
* ve). Antes de este componente, cada pantalla que consume la librería
|
|
5347
|
-
* definía su propio `h1`/`p` con `font-size` suelto para el mismo rol
|
|
5348
|
-
* (título de página, subtítulo, texto secundario) — auditoría sobre
|
|
5349
|
-
* `web-portal-latam-frontend` encontró 9 tamaños distintos entre login y
|
|
5350
|
-
* home sin ningún paso de escala en común. `variant` resuelve eso contra
|
|
5351
|
-
* la escala de `tokens/base.css` (`--kf-font-size-*`).
|
|
5352
|
-
*
|
|
5353
|
-
* `tone` sigue la misma convención cerrada que `Alert`/`Badge`/`Chip`: sin
|
|
5354
|
-
* escape hatch de color libre. Si un caso necesita un color que `tone` no
|
|
5355
|
-
* cubre, es una discusión de design system, no una prop nueva acá.
|
|
5356
|
-
*
|
|
5357
|
-
* <kf-text as="h1" variant="title-lg">Bienvenido de nuevo</kf-text>
|
|
5358
|
-
* <kf-text as="p" variant="body-muted">Ingresá con tu cuenta corporativa.</kf-text>
|
|
5359
|
-
* <kf-text as="span" variant="caption" tone="error">Campo obligatorio</kf-text>
|
|
5360
|
-
*/
|
|
5361
|
-
class Text {
|
|
5362
|
-
/** Etiqueta semántica real que se renderiza — nunca es solo un `div`. */
|
|
5363
|
-
as = input('p', /* @ts-ignore */
|
|
5364
|
-
...(ngDevMode ? [{ debugName: "as" }] : /* istanbul ignore next */ []));
|
|
5365
|
-
variant = input('body', /* @ts-ignore */
|
|
5366
|
-
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
5367
|
-
tone = input('neutral', /* @ts-ignore */
|
|
5368
|
-
...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
5369
|
-
/**
|
|
5370
|
-
* Copia de `as` para uso dentro del template: `as` es palabra reservada
|
|
5371
|
-
* en las expresiones de Angular (`@if (x; as y)`), así que `@switch (as())`
|
|
5372
|
-
* no parsea — NG5002. La prop pública sigue llamándose `as` (es solo un
|
|
5373
|
-
* atributo HTML ahí, `<kf-text as="h1">`, no una expresión).
|
|
5374
|
-
*/
|
|
5375
|
-
tag = computed(() => this.as(), /* @ts-ignore */
|
|
5376
|
-
...(ngDevMode ? [{ debugName: "tag" }] : /* istanbul ignore next */ []));
|
|
5377
|
-
hostClass() {
|
|
5378
|
-
const classes = [`kf-text--${this.variant()}`];
|
|
5379
|
-
if (this.tone() !== 'neutral') {
|
|
5380
|
-
classes.push(`kf-text--${this.tone()}`);
|
|
5381
|
-
}
|
|
5382
|
-
return classes.join(' ');
|
|
5383
|
-
}
|
|
5384
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Text, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5385
|
-
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: `
|
|
5386
|
-
<!--
|
|
5387
|
-
Una sola proyección compartida (ng-template + ngTemplateOutlet) en vez
|
|
5388
|
-
de un <ng-content> por cada rama del @switch: Angular solo conecta la
|
|
5389
|
-
proyección real del consumidor a la PRIMERA instancia de <ng-content>
|
|
5390
|
-
que encuentra en el árbol de compilación — con un <ng-content> por
|
|
5391
|
-
rama, todas menos una quedan vacías en runtime (bug detectado por
|
|
5392
|
-
Text.spec.ts, no algo documentado a simple vista en los docs de Angular).
|
|
5393
|
-
-->
|
|
5394
|
-
<ng-template #projected><ng-content /></ng-template>
|
|
5395
|
-
@switch (tag()) {
|
|
5396
|
-
@case ('h1') {
|
|
5397
|
-
<h1 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h1>
|
|
5398
|
-
}
|
|
5399
|
-
@case ('h2') {
|
|
5400
|
-
<h2 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h2>
|
|
5401
|
-
}
|
|
5402
|
-
@case ('h3') {
|
|
5403
|
-
<h3 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h3>
|
|
5404
|
-
}
|
|
5405
|
-
@case ('span') {
|
|
5406
|
-
<span [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></span>
|
|
5407
|
-
}
|
|
5408
|
-
@default {
|
|
5409
|
-
<p [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></p>
|
|
5410
|
-
}
|
|
5411
|
-
}
|
|
5412
|
-
`, 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"] }] });
|
|
5413
|
-
}
|
|
5414
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Text, decorators: [{
|
|
5415
|
-
type: Component,
|
|
5416
|
-
args: [{ selector: 'kf-text', imports: [NgTemplateOutlet], template: `
|
|
5417
|
-
<!--
|
|
5418
|
-
Una sola proyección compartida (ng-template + ngTemplateOutlet) en vez
|
|
5419
|
-
de un <ng-content> por cada rama del @switch: Angular solo conecta la
|
|
5420
|
-
proyección real del consumidor a la PRIMERA instancia de <ng-content>
|
|
5421
|
-
que encuentra en el árbol de compilación — con un <ng-content> por
|
|
5422
|
-
rama, todas menos una quedan vacías en runtime (bug detectado por
|
|
5423
|
-
Text.spec.ts, no algo documentado a simple vista en los docs de Angular).
|
|
5424
|
-
-->
|
|
5425
|
-
<ng-template #projected><ng-content /></ng-template>
|
|
5426
|
-
@switch (tag()) {
|
|
5427
|
-
@case ('h1') {
|
|
5428
|
-
<h1 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h1>
|
|
5429
|
-
}
|
|
5430
|
-
@case ('h2') {
|
|
5431
|
-
<h2 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h2>
|
|
5432
|
-
}
|
|
5433
|
-
@case ('h3') {
|
|
5434
|
-
<h3 [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></h3>
|
|
5435
|
-
}
|
|
5436
|
-
@case ('span') {
|
|
5437
|
-
<span [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></span>
|
|
5438
|
-
}
|
|
5439
|
-
@default {
|
|
5440
|
-
<p [class]="hostClass()"><ng-container [ngTemplateOutlet]="projected" /></p>
|
|
5441
|
-
}
|
|
5442
|
-
}
|
|
5443
|
-
`, 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"] }]
|
|
5444
|
-
}], 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 }] }] } });
|
|
5445
|
-
|
|
5446
6850
|
/** Fixed icon per tone (page 20 of the design system) — not configurable per call. */
|
|
5447
6851
|
const TOAST_ICON = {
|
|
5448
6852
|
success: 'check-circle',
|
|
@@ -5792,5 +7196,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
|
|
|
5792
7196
|
* Generated bundle index. Do not edit.
|
|
5793
7197
|
*/
|
|
5794
7198
|
|
|
5795
|
-
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, FolderTabsPanel, 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 };
|
|
5796
7200
|
//# sourceMappingURL=kaufmann-lab-ui.mjs.map
|