@kaufmann-lab/ui 0.1.26 → 0.1.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaufmann-lab/ui",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Shared Angular component library for the Kaufmann frontends",
5
5
  "license": "MIT",
6
6
  "author": "Kaufmann Lab",
@@ -220,11 +220,44 @@ declare class Button {
220
220
  type AlertTone = 'info' | 'ok' | 'warn' | 'error';
221
221
  /** Forma del aviso: `card` (default, con borde y esquinas) o `bar` (franja compacta de ancho completo). */
222
222
  type AlertVariant = 'card' | 'bar';
223
+ /**
224
+ * Apariencia de la variante `card`: `tile` (default), `tint` o `rail`. Ver el
225
+ * JSDoc de `Alert` para cuándo usar cada una.
226
+ */
227
+ type AlertAppearance = 'tile' | 'tint' | 'rail';
223
228
  /**
224
229
  * Banner contextual que vive dentro del flujo: validaciones, avisos de
225
230
  * vencimiento, confirmaciones. Persiste hasta que se descarta o hasta que la
226
231
  * condición que lo causó desaparece — para feedback efímero está el toast.
227
232
  *
233
+ * Diseño sobrio: el título va en peso 600 con la tipografía de cuerpo y el
234
+ * mensaje con medida de lectura limitada, ambos en los neutros del sistema (el
235
+ * color del tono nunca se usa para texto). El ícono va centrado en vertical
236
+ * respecto al aviso. El color sale siempre de los tokens (`*-strong`), nunca de
237
+ * un input. Ninguna apariencia lleva borde ni acento lateral de color.
238
+ *
239
+ * `appearance` elige cómo se muestra el tono (solo aplica a `variant="card"`):
240
+ *
241
+ * - `tile` (default): aviso estándar dentro de pantallas y cards. Tarjeta de
242
+ * superficie y borde neutros; el color vive solo en el ícono, un tile sólido de
243
+ * 40px con glifo blanco.
244
+ *
245
+ * <kf-alert tone="warn" title="SOAT por vencer">Vence el 15/08.</kf-alert>
246
+ *
247
+ * - `tint`: aviso liviano sobre fondos neutros o en zonas densas, donde el tinte
248
+ * basta. Relleno suave del tono, sin borde ni tile; glifo de 24px en el color
249
+ * fuerte del tono.
250
+ *
251
+ * <kf-alert appearance="tint" tone="info">Cita confirmada para el jueves.</kf-alert>
252
+ *
253
+ * - `rail`: aviso destacado que debe notarse al primer vistazo, p. ej. errores o
254
+ * bloqueos. Panel de ícono de 56px a todo el alto, con el fondo del tono más
255
+ * intenso, y cuerpo neutro con borde neutro.
256
+ *
257
+ * <kf-alert appearance="rail" tone="error" title="Pago rechazado">
258
+ * Tu banco no autorizó el cargo.
259
+ * </kf-alert>
260
+ *
228
261
  * `open` es two-way: el botón de cerrar lo pone en `false` y el componente
229
262
  * deja de renderizar. El consumidor puede volver a abrirlo poniéndolo en
230
263
  * `true` (ej. cuando la validación falla otra vez).
@@ -238,15 +271,32 @@ type AlertVariant = 'card' | 'bar';
238
271
  * tonos y mismo `role` (`alert` para `error`/`warn`, `status` para el resto)
239
272
  * que la variante `card`. Pensada para el borde de una página o de un panel
240
273
  * (ej. "Estás viendo datos de otro cliente"), donde una tarjeta con margen
241
- * sobra.
274
+ * sobra. `variant="bar"` ignora `appearance`: la franja siempre usa su propia
275
+ * superficie tintada.
242
276
  *
243
277
  * <kf-alert variant="bar" tone="warn" title="Modo suplantación">Viendo como Ana Pérez.</kf-alert>
278
+ *
279
+ * Los elementos con el atributo `kfAlertActions` se proyectan a la derecha del
280
+ * mensaje cuando hay ancho y, si no caben, en una fila propia bajo él, apilados
281
+ * y alineados al inicio (en la variante `bar` siempre van en fila propia). Si los
282
+ * envuelves en un solo `<div kfAlertActions>`, el aviso separa a sus hijos (12px).
283
+ * Sin ellos la zona no existe y el aviso queda igual.
284
+ *
285
+ * <kf-alert tone="warn" title="SOAT por vencer">
286
+ * Vence el 15/08.
287
+ * <div kfAlertActions><kf-button size="sm">Renovar</kf-button></div>
288
+ * </kf-alert>
244
289
  */
245
290
  declare class Alert {
246
291
  readonly tone: _angular_core.InputSignal<AlertTone>;
247
292
  /** `card` (default) o `bar` (franja compacta de ancho completo). */
248
293
  readonly variant: _angular_core.InputSignal<AlertVariant>;
249
- /** Línea en negrita arriba del mensaje. Vacío deja solo el contenido proyectado. */
294
+ /**
295
+ * `tile` (default), `tint` o `rail`. Solo aplica a `variant="card"`; `bar` la
296
+ * ignora. Se expone en el host como `data-appearance`.
297
+ */
298
+ readonly appearance: _angular_core.InputSignal<AlertAppearance>;
299
+ /** Título (peso 600) arriba del mensaje. Vacío deja solo el contenido proyectado. */
250
300
  readonly title: _angular_core.InputSignal<string>;
251
301
  readonly dismissible: _angular_core.InputSignalWithTransform<boolean, unknown>;
252
302
  /** Visibilidad, two-way: `[(open)]="hasError"`. */
@@ -257,8 +307,10 @@ declare class Alert {
257
307
  */
258
308
  protected readonly ariaRole: _angular_core.Signal<"alert" | "status">;
259
309
  protected readonly toneIcon: _angular_core.Signal<IconName>;
310
+ /** Glifo: 16px en `bar`, 20px en `tile` y 24px en `tint`/`rail`. */
311
+ protected readonly iconSize: _angular_core.Signal<IconSize>;
260
312
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Alert, never>;
261
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Alert, "kf-alert", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["*"], true, never>;
313
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Alert, "kf-alert", never, { "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; }, { "open": "openChange"; }, never, ["*", "[kfAlertActions]"], true, never>;
262
314
  }
263
315
 
264
316
  type AlertRowTone = 'warn' | 'error' | 'neutral';
@@ -938,6 +990,12 @@ type DataTableDensity = 'comfortable' | 'compact';
938
990
  /** `key:direction` — mismo formato que `sort` de `kf-quote-picker`, para
939
991
  * que un consumidor que ya conoce ese patrón no aprenda uno nuevo acá. */
940
992
  type DataTableSort = `${string}:${'asc' | 'desc'}`;
993
+ /** Nombre de la entidad listada: un `string` (plural fijo) o `{ one, other }`
994
+ * para usar el singular cuando el total es 1. */
995
+ type DataTableItemLabel = string | {
996
+ one: string;
997
+ other: string;
998
+ };
941
999
  /**
942
1000
  * Tabla estandarizada con soporte opcional de toggle tabla/cards.
943
1001
  *
@@ -1002,9 +1060,11 @@ type DataTableSort = `${string}:${'asc' | 'desc'}`;
1002
1060
  * por página" viene activo por defecto con `DEFAULT_PAGE_SIZE_OPTIONS`
1003
1061
  * ([10, 25, 50], estándar del portal) — pasar `pageSizeOptions` propio lo
1004
1062
  * pisa, y pasar `[]` lo desactiva (paginación sin selector). `itemLabel`
1005
- * completa el texto informativo ("1–10 de 42 beneficios").
1063
+ * completa el texto informativo ("1–10 de 42 beneficios"); con
1064
+ * `{ one, other }` usa el singular cuando el total es 1 ("1–1 de 1 beneficio").
1006
1065
  *
1007
1066
  * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" itemLabel="beneficios" />
1067
+ * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" [itemLabel]="{ one: 'unidad', other: 'unidades' }" />
1008
1068
  * <kf-data-table [columns]="columns" [rows]="rows" [pageSize]="25" [pageSizeOptions]="[]" />
1009
1069
  *
1010
1070
  * La página es `[(page)]` (el consumidor puede llevarla a la URL) y se
@@ -1225,10 +1285,13 @@ declare class DataTable<T = unknown> {
1225
1285
  readonly pageSizeChange: _angular_core.OutputEmitterRef<number>;
1226
1286
  /** Nombre de la entidad listada (ej. "beneficios", "facturas"), en
1227
1287
  * plural — se agrega al final del texto informativo de la paginación
1228
- * interna ("1–5 de 7 beneficios"). Sin esto, el texto queda solo con
1229
- * números ("1–5 de 7"): la librería no impone un idioma ni un nombre
1230
- * fijo, cada consumidor decide el suyo. */
1231
- readonly itemLabel: _angular_core.InputSignal<string>;
1288
+ * interna ("1–5 de 7 beneficios"). Acepta `{ one, other }` para usar el
1289
+ * singular cuando el total es 1 ("1–1 de 1 beneficio"); un `string`
1290
+ * conserva el comportamiento de siempre (mismo texto para cualquier
1291
+ * total). Sin esto, el texto queda solo con números ("1–5 de 7"): la
1292
+ * librería no impone un idioma ni un nombre fijo, cada consumidor decide
1293
+ * el suyo. */
1294
+ readonly itemLabel: _angular_core.InputSignal<DataTableItemLabel>;
1232
1295
  private readonly internalPageSize;
1233
1296
  /** Tamaño de página efectivo: el que el usuario elija en el selector
1234
1297
  * (si `pageSizeOptions` está activo), o el `pageSize` del consumidor. */
@@ -1438,7 +1501,7 @@ declare class SkeletonPreset {
1438
1501
  protected readonly items: _angular_core.Signal<number[]>;
1439
1502
  /** Columnas de la tabla sintética (encabezados en blanco: lo real aún no llegó). */
1440
1503
  protected readonly tableColumns: _angular_core.Signal<DataTableColumn[]>;
1441
- protected readonly formColumns: _angular_core.Signal<1 | 2>;
1504
+ protected readonly formColumns: _angular_core.Signal<2 | 1>;
1442
1505
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SkeletonPreset, never>;
1443
1506
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SkeletonPreset, "kf-skeleton-preset", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "count": { "alias": "count"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1444
1507
  }
@@ -4341,6 +4404,12 @@ declare class Select {
4341
4404
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
4342
4405
  /** Borde en rojo y `aria-invalid` en el trigger. Ver `errorMessage` para el mensaje asociado — mismo contrato que `kf-input`. */
4343
4406
  readonly invalid: _angular_core.InputSignalWithTransform<boolean, unknown>;
4407
+ /**
4408
+ * Pone el asterisco rojo en la etiqueta (igual que `kf-input`) y
4409
+ * `aria-required="true"` en el trigger. Solo señaliza: la validación sigue
4410
+ * en el formulario del consumidor.
4411
+ */
4412
+ readonly required: _angular_core.InputSignalWithTransform<boolean, unknown>;
4344
4413
  /** Mensaje de error con `role="alert"`, debajo del trigger. */
4345
4414
  readonly errorMessage: _angular_core.InputSignal<string | undefined>;
4346
4415
  readonly variant: _angular_core.InputSignal<"field" | "compact">;
@@ -4501,7 +4570,7 @@ declare class Select {
4501
4570
  private scrollActiveIntoView;
4502
4571
  selectActive(): void;
4503
4572
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Select, never>;
4504
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "hideLabel": { "alias": "hideLabel"; "required": false; "isSignal": true; }; "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "panelTitle": { "alias": "panelTitle"; "required": false; "isSignal": true; }; "panelWidth": { "alias": "panelWidth"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
4573
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Select, "kf-select", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "hideLabel": { "alias": "hideLabel"; "required": false; "isSignal": true; }; "prefix": { "alias": "prefix"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchThreshold": { "alias": "searchThreshold"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "panelTitle": { "alias": "panelTitle"; "required": false; "isSignal": true; }; "panelWidth": { "alias": "panelWidth"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
4505
4574
  }
4506
4575
 
4507
4576
  interface SelectableCardOption {
@@ -4629,8 +4698,13 @@ declare class SidebarShell {
4629
4698
  * muestra todo expandido.
4630
4699
  */
4631
4700
  readonly itemsCollapsed: _angular_core.Signal<boolean>;
4632
- /** Cierra el panel mobile con Escape — mismo criterio que `Dropdown`/`Modal`/`Drawer`. */
4633
- protected onEscape(): void;
4701
+ /**
4702
+ * Cierra el panel mobile con Escape — mismo criterio que `Drawer`: si un
4703
+ * panel flotante de adentro (`kf-dropdown`, `kf-select`) ya consumió la
4704
+ * tecla en captura (`defaultPrevented`), el panel mobile sigue abierto y
4705
+ * solo cierra la capa superior.
4706
+ */
4707
+ protected onEscape(event: Event): void;
4634
4708
  protected onNavItemClick(event: Event, item: SidebarNavItemData): void;
4635
4709
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SidebarShell, never>;
4636
4710
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SidebarShell, "kf-sidebar-shell", never, { "navItems": { "alias": "navItems"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "mobileOpen": { "alias": "mobileOpen"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "mobileOpen": "mobileOpenChange"; "navItemClick": "navItemClick"; }, never, ["[kf-sidebar-brand]", "[kf-sidebar-brand-collapsed]", "[kf-sidebar-footer]", "[kf-sidebar-brand-mobile]"], true, never>;
@@ -4689,6 +4763,8 @@ declare class Spinner {
4689
4763
  }
4690
4764
 
4691
4765
  type StatCardTone = 'primary' | 'success' | 'error' | 'purple' | 'warn' | 'info' | 'neutral';
4766
+ /** Tono de la insignia (`badge`): los roles semánticos de `kf-badge` salvo `service`. */
4767
+ type StatCardBadgeTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
4692
4768
  /** Densidad de la card: `'compact'` reduce el padding, el ícono y el valor. */
4693
4769
  type StatCardDensity = 'default' | 'compact';
4694
4770
  /**
@@ -4715,6 +4791,21 @@ type StatCardDensity = 'default' | 'compact';
4715
4791
  * <kf-stat-card icon="clock" tone="warn" value="7" label="Por vencer" [bar]="35" hint="7 de 20"
4716
4792
  * clickable [pressed]="filter() === 'due'" (cardClick)="toggle('due')" />
4717
4793
  *
4794
+ * - `badge` (+ `badgeTone`, default `'neutral'`): insignia (`kf-badge`) a la
4795
+ * derecha de la etiqueta, para un estado puntual ("Urgente", "Nuevo"). Es
4796
+ * texto real en el DOM, así que un lector de pantalla lo lee tras la
4797
+ * etiqueta: el tono refuerza el significado, no lo porta solo. Si el
4798
+ * ancho no alcanza, baja a la línea siguiente en vez de recortarse. Sin
4799
+ * `badge` (o con texto vacío) la card no cambia. Funciona con `density`,
4800
+ * `clickable` y `loading`.
4801
+ *
4802
+ * <kf-stat-card icon="clock" tone="warn" value="7" label="Por vencer" badge="Urgente" badgeTone="error" />
4803
+ *
4804
+ * Alto: la card ocupa el 100% del alto de su contenedor cuando este lo fija
4805
+ * (celda de una grilla o ítem estirado de un flex), de modo que las cards de
4806
+ * una misma fila miden igual sin `::ng-deep`. En un contenedor de alto
4807
+ * automático mide lo que su contenido, como siempre.
4808
+ *
4718
4809
  * `density` (`'default'` | `'compact'`): `'compact'` es la variante para
4719
4810
  * pantallas angostas — padding de `--kf-space-md`, ícono de 40px y valor de
4720
4811
  * `--kf-font-size-title-sm`, con el mismo contenido. Si la card mide 210px de
@@ -4730,7 +4821,7 @@ type StatCardDensity = 'default' | 'compact';
4730
4821
  * `loading` (default `false`): mientras es `true` la card dibuja su esqueleto
4731
4822
  * en vez del contenido, con el MISMO cascarón (borde, padding, ícono de 50px)
4732
4823
  * y líneas del alto real de cada texto — valor, etiqueta y, si se pasaron,
4733
- * nota, barra y `hint` —, así que al llegar los datos no hay salto de
4824
+ * nota, barra, `hint` e insignia —, así que al llegar los datos no hay salto de
4734
4825
  * layout. Pasa los mismos inputs que usarás cargada para que el esqueleto
4735
4826
  * reserve las mismas líneas. Marca la card con `aria-busy="true"`, no es
4736
4827
  * clicable ni expone su texto a lectores de pantalla mientras carga.
@@ -4747,6 +4838,10 @@ declare class StatCard {
4747
4838
  readonly bar: _angular_core.InputSignal<number | null | undefined>;
4748
4839
  /** Texto secundario bajo la barra (o bajo la nota si no hay barra). */
4749
4840
  readonly hint: _angular_core.InputSignal<string | undefined>;
4841
+ /** Texto de la insignia a la derecha de la etiqueta; vacío/ausente = sin insignia. */
4842
+ readonly badge: _angular_core.InputSignal<string | undefined>;
4843
+ /** Tono semántico de la insignia (`kf-badge`). */
4844
+ readonly badgeTone: _angular_core.InputSignal<StatCardBadgeTone>;
4750
4845
  /**
4751
4846
  * Estado de alternancia (`aria-pressed`) cuando la card es `clickable`.
4752
4847
  * `null` (default) = no aplica: el atributo no se emite.
@@ -4762,7 +4857,7 @@ declare class StatCard {
4762
4857
  protected readonly barTone: _angular_core.Signal<ProgressTone>;
4763
4858
  protected readonly hasBar: _angular_core.Signal<boolean>;
4764
4859
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatCard, never>;
4765
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatCard, "kf-stat-card", never, { "icon": { "alias": "icon"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "note": { "alias": "note"; "required": false; "isSignal": true; }; "bar": { "alias": "bar"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "pressed": { "alias": "pressed"; "required": false; "isSignal": true; }; "density": { "alias": "density"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; }, { "cardClick": "cardClick"; }, never, never, true, never>;
4860
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatCard, "kf-stat-card", never, { "icon": { "alias": "icon"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "note": { "alias": "note"; "required": false; "isSignal": true; }; "bar": { "alias": "bar"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeTone": { "alias": "badgeTone"; "required": false; "isSignal": true; }; "pressed": { "alias": "pressed"; "required": false; "isSignal": true; }; "density": { "alias": "density"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; }, { "cardClick": "cardClick"; }, never, never, true, never>;
4766
4861
  }
4767
4862
 
4768
4863
  /** Vocabulario semántico único de tono, compartido con `TextTone`. */
@@ -4811,6 +4906,11 @@ declare class StatTile {
4811
4906
  readonly tone: _angular_core.InputSignal<StatTileTone>;
4812
4907
  /** Tercera línea bajo el valor. Vacía cae en `notePlaceholder`. */
4813
4908
  readonly note: _angular_core.InputSignal<string | null | undefined>;
4909
+ /** Máximo de líneas visibles de la nota antes de la elipsis (`…`); el texto
4910
+ * completo sigue en el DOM y en el `title`. Default 2, como siempre. Un
4911
+ * valor menor a 1 o no numérico no limita la altura: la nota se muestra
4912
+ * entera. */
4913
+ readonly noteLines: _angular_core.InputSignalWithTransform<number, unknown>;
4814
4914
  /** Qué dice la tercera línea cuando no hay nota. Cada consumidor debería
4815
4915
  * pasar el motivo real de SU dato ausente, no un texto genérico. */
4816
4916
  readonly notePlaceholder: _angular_core.InputSignal<string | undefined>;
@@ -4858,9 +4958,11 @@ declare class StatTile {
4858
4958
  */
4859
4959
  protected onKeydown(event: KeyboardEvent): void;
4860
4960
  protected onKeyup(event: KeyboardEvent): void;
4961
+ /** Valor de `line-clamp`: entero ≥ 1, o `none` para mostrar la nota entera. */
4962
+ protected readonly noteClamp: _angular_core.Signal<string>;
4861
4963
  protected readonly noteIsPlaceholder: _angular_core.Signal<boolean>;
4862
4964
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<StatTile, never>;
4863
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatTile, "kf-stat-tile", never, { "cta": { "alias": "cta"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "note": { "alias": "note"; "required": false; "isSignal": true; }; "notePlaceholder": { "alias": "notePlaceholder"; "required": false; "isSignal": true; }; "valueMissing": { "alias": "valueMissing"; "required": false; "isSignal": true; }; "hasProjectedValue": { "alias": "hasProjectedValue"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "pressed": { "alias": "pressed"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "actionClick": "actionClick"; "tileClick": "tileClick"; }, never, ["[valueContent]"], true, never>;
4965
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<StatTile, "kf-stat-tile", never, { "cta": { "alias": "cta"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "note": { "alias": "note"; "required": false; "isSignal": true; }; "noteLines": { "alias": "noteLines"; "required": false; "isSignal": true; }; "notePlaceholder": { "alias": "notePlaceholder"; "required": false; "isSignal": true; }; "valueMissing": { "alias": "valueMissing"; "required": false; "isSignal": true; }; "hasProjectedValue": { "alias": "hasProjectedValue"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "actionLabel": { "alias": "actionLabel"; "required": false; "isSignal": true; }; "actionHref": { "alias": "actionHref"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "pressed": { "alias": "pressed"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "actionClick": "actionClick"; "tileClick": "tileClick"; }, never, ["[valueContent]"], true, never>;
4864
4966
  }
4865
4967
 
4866
4968
  /**
@@ -4954,12 +5056,19 @@ type SwitchSize = 'sm' | 'md';
4954
5056
  * switch hace que el valor llegue dos veces al consumidor: usar una sola de
4955
5057
  * las dos vías, o ignorar en el handler el eco que no cambia el valor.
4956
5058
  *
5059
+ * `describedBy` recibe el `id` de un texto de ayuda externo (ej. el `<p>` de
5060
+ * una fila de preferencias) y lo enlaza con `aria-describedby` del botón, para
5061
+ * que el lector de pantalla lo anuncie junto al nombre y el estado. Sin valor,
5062
+ * el atributo no se renderiza.
5063
+ *
4957
5064
  * El color sale de roles (`--kf-color-primary` encendido,
4958
5065
  * `--kf-color-border-control` apagado) y el foco de `--kf-shadow-focus`; no
4959
5066
  * recibe colores.
4960
5067
  *
4961
5068
  * <kf-switch label="Fijar indicadores" [(checked)]="pinned" />
4962
5069
  * <kf-switch label="Mapa junto a la tabla" hideLabel size="sm" formControlName="splitMap" />
5070
+ * <kf-switch label="Alertas por correo" describedBy="mail-hint" [(checked)]="mail" />
5071
+ * <p id="mail-hint">Se envía un resumen cada hora.</p>
4963
5072
  */
4964
5073
  declare class Switch implements ControlValueAccessor {
4965
5074
  /** Nombre accesible y texto visible (salvo con `hideLabel`). */
@@ -4968,6 +5077,8 @@ declare class Switch implements ControlValueAccessor {
4968
5077
  readonly hideLabel: _angular_core.InputSignalWithTransform<boolean, unknown>;
4969
5078
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
4970
5079
  readonly size: _angular_core.InputSignal<SwitchSize>;
5080
+ /** `id` del texto de ayuda que describe al interruptor (`aria-describedby`). */
5081
+ readonly describedBy: _angular_core.InputSignal<string | null>;
4971
5082
  /** Estado encendido, two-way: `[(checked)]="pinned"`. */
4972
5083
  readonly checked: _angular_core.ModelSignal<boolean>;
4973
5084
  private readonly disabledByForm;
@@ -4981,7 +5092,7 @@ declare class Switch implements ControlValueAccessor {
4981
5092
  registerOnTouched(fn: () => void): void;
4982
5093
  setDisabledState(isDisabled: boolean): void;
4983
5094
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Switch, never>;
4984
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Switch, "kf-switch", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "hideLabel": { "alias": "hideLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "checked": { "alias": "checked"; "required": false; "isSignal": true; }; }, { "checked": "checkedChange"; }, never, never, true, never>;
5095
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Switch, "kf-switch", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "hideLabel": { "alias": "hideLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "describedBy": { "alias": "describedBy"; "required": false; "isSignal": true; }; "checked": { "alias": "checked"; "required": false; "isSignal": true; }; }, { "checked": "checkedChange"; }, never, never, true, never>;
4985
5096
  }
4986
5097
 
4987
5098
  interface TabOption {
@@ -5073,6 +5184,16 @@ declare class FolderTabs {
5073
5184
  *
5074
5185
  * <kf-textarea label="Detalle" formControlName="detail" [maxRows]="12" />
5075
5186
  *
5187
+ * `maxlength` limita el texto (`maxlength` nativo, entero positivo) y
5188
+ * `showCounter` agrega el contador «0 / 500» pegado bajo el campo, en la misma
5189
+ * fila que el `hint`/`errorMessage`. Ambos son opt-in: sin ellos el campo no
5190
+ * cambia. El contador sin `maxlength` no se muestra (no hay tope que contar).
5191
+ * Visualmente pasa a rojo al llegar al tope; para lectores de pantalla solo
5192
+ * anuncia (`aria-live="polite"`) al cruzar el 90 % y al llegar al límite, no
5193
+ * en cada tecla.
5194
+ *
5195
+ * <kf-textarea label="Motivo" formControlName="reason" [maxlength]="500" showCounter />
5196
+ *
5076
5197
  * `invalid` + `errorMessage` y `readonly`/`disabled` siguen el mismo
5077
5198
  * contrato que `kf-input` — ver su JSDoc para la diferencia entre ambos
5078
5199
  * estados.
@@ -5099,15 +5220,29 @@ declare class Textarea implements ControlValueAccessor, AfterViewInit {
5099
5220
  readonly minRows: _angular_core.InputSignal<number>;
5100
5221
  /** Filas máximas antes de que el contenido pase a scroll interno en vez de seguir creciendo. */
5101
5222
  readonly maxRows: _angular_core.InputSignal<number>;
5223
+ /** Largo máximo del texto (`maxlength` nativo), entero positivo. */
5224
+ readonly maxlength: _angular_core.InputSignalWithTransform<number | null, string | number | null | undefined>;
5225
+ /** Muestra el contador «n / maxlength» bajo el campo. Requiere `maxlength`. */
5226
+ readonly showCounter: _angular_core.InputSignalWithTransform<boolean, unknown>;
5102
5227
  protected readonly textareaId: string;
5103
5228
  protected readonly hintId: string;
5104
5229
  protected readonly errorId: string;
5230
+ protected readonly counterId: string;
5105
5231
  protected readonly value: _angular_core.WritableSignal<string>;
5106
5232
  protected readonly focused: _angular_core.WritableSignal<boolean>;
5107
5233
  /** Deshabilitado por Reactive Forms (formControl.disable()). */
5108
5234
  private readonly disabledByForm;
5109
5235
  /** Disabled real: por input() directo o por el FormControl asociado. */
5110
5236
  protected readonly isDisabled: _angular_core.Signal<boolean>;
5237
+ protected readonly counterVisible: _angular_core.Signal<boolean>;
5238
+ protected readonly atLimit: _angular_core.Signal<boolean>;
5239
+ /**
5240
+ * Texto del `aria-live`: cambia solo al cruzar un umbral (90 % y tope), así
5241
+ * el lector no lee cada carácter escrito cerca del límite.
5242
+ */
5243
+ protected readonly counterAnnouncement: _angular_core.Signal<string>;
5244
+ /** Hint o error, más el contador cuando se muestra. */
5245
+ protected readonly describedBy: _angular_core.Signal<string | null>;
5111
5246
  private onChange;
5112
5247
  private onTouched;
5113
5248
  ngAfterViewInit(): void;
@@ -5126,7 +5261,7 @@ declare class Textarea implements ControlValueAccessor, AfterViewInit {
5126
5261
  */
5127
5262
  private adjustHeight;
5128
5263
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<Textarea, never>;
5129
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<Textarea, "kf-textarea", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "minRows": { "alias": "minRows"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
5264
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<Textarea, "kf-textarea", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "minRows": { "alias": "minRows"; "required": false; "isSignal": true; }; "maxRows": { "alias": "maxRows"; "required": false; "isSignal": true; }; "maxlength": { "alias": "maxlength"; "required": false; "isSignal": true; }; "showCounter": { "alias": "showCounter"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
5130
5265
  }
5131
5266
 
5132
5267
  /**
@@ -5466,4 +5601,4 @@ declare class ViewToggle {
5466
5601
  }
5467
5602
 
5468
5603
  export { ActionTile, AdminBadge, Alert, AlertRow, Avatar, Badge, Breadcrumb, BucketProgress, Button, Calendar, Card, Carousel, CarouselSlide, Checkbox, Chip, Collapsible, ContactPopover, ContentSlot, ContentState, ContentStateLoading, ContentViewer, CursorPager, DATA_TABLE_MAX_SKELETON_ROWS, DEFAULT_MILESTONE_TIMELINE_LABELS, DEFAULT_PAGE_SIZE_OPTIONS, DataTable, Divider, DocumentRow, Drawer, Dropdown, DropdownItem, DropdownPanel, EmptyState, EventTimeline, FeatureCard, Field, FieldLabel, FileInput, Filter, FilterChips, FolderTabs, FolderTabsPanel, Footer, FormatInputDirective, Icon, Input, JourneyStepper, LIST_TOOLBAR_HYSTERESIS, Link, ListToolbar, MetricCard, MilestoneTimeline, Modal, MultiSelect, NoteBlock, OptionList, PROGRESS_STATUS, PROGRESS_STATUS_LABEL, PageHeader, Pagination, Panel, PopoverDirective, PortalHeader, Progress, PulseDot, RowActions, SKELETON_PRESET_MAX_COUNT, SectionHeader, SectionNav, SectionNavItem, SectionNavSubsection, Segmented, Select, SelectableCardGroup, SidebarShell, Skeleton, SkeletonPreset, Spinner, StatCard, StatTile, StatusMark, StatusNode, StatusPill, Switch, TOAST_DEFAULT_DURATION_MS, Tabs, Text, Textarea, TileGrid, TimeSlotPicker, ToastHost, ToastService, TooltipDirective, ViewToggle, dataTableViewSignal, formatFileSize, resolveListToolbarMode };
5469
- export type { AlertRowTone, AlertTone, AlertVariant, BreadcrumbItem, BucketProgressItem, CalendarDay, ContentStateKind, ContentStateLoad, ContentViewerType, DataTableCardRole, DataTableCellClick, DataTableColumn, DataTableDensity, DataTableSort, DataTableView, DividerOrientation, DocumentRowTone, DrawerRole, DrawerSide, DropdownItemTone, DropdownPanelRole, DropdownPanelVariant, EmptyStateTone, EventTimelineItem, EventTimelineTone, FileRejection, FileRejectionReason, FilterChipOption, FilterFieldChange, FilterFieldConfig, FilterFieldType, FilterValue, FolderTabOption, FooterLink, IconName, IconRotation, IconSize, InputBound, JourneyStep, JourneyStepStatus, JourneyStepperColorScheme, JourneyStepperOrientation, JourneyStepperSurface, LinkVariant, ListToolbarMode, MilestoneGroup, MilestoneItem, MilestoneTimelineLabels, ModalSize, MultiSelectOption, NoteBlockTone, OptionListOption, PageHeaderTitleSize, PaginationItem, PopoverPlacement, PortalHeaderAdminAction, PortalHeaderNavItem, ProgressSize, ProgressStatus, ProgressTone, RowAction, RowActionKind, RowActionsMode, SectionNavSubsectionData, SegmentedOption, SelectOption, SelectableCardOption, SidebarNavItemData, SkeletonPresetVariant, SkeletonVariant, SpinnerSize, StatCardDensity, StatCardTone, StatTileTone, StatTileVariant, SwitchSize, TabOption, TextAs, TextTone, TextVariant, TimeSlot, ToastAction, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, TooltipPosition, ViewToggleOption };
5604
+ export type { AlertAppearance, AlertRowTone, AlertTone, AlertVariant, BreadcrumbItem, BucketProgressItem, CalendarDay, ContentStateKind, ContentStateLoad, ContentViewerType, DataTableCardRole, DataTableCellClick, DataTableColumn, DataTableDensity, DataTableItemLabel, DataTableSort, DataTableView, DividerOrientation, DocumentRowTone, DrawerRole, DrawerSide, DropdownItemTone, DropdownPanelRole, DropdownPanelVariant, EmptyStateTone, EventTimelineItem, EventTimelineTone, FileRejection, FileRejectionReason, FilterChipOption, FilterFieldChange, FilterFieldConfig, FilterFieldType, FilterValue, FolderTabOption, FooterLink, IconName, IconRotation, IconSize, InputBound, JourneyStep, JourneyStepStatus, JourneyStepperColorScheme, JourneyStepperOrientation, JourneyStepperSurface, LinkVariant, ListToolbarMode, MilestoneGroup, MilestoneItem, MilestoneTimelineLabels, ModalSize, MultiSelectOption, NoteBlockTone, OptionListOption, PageHeaderTitleSize, PaginationItem, PopoverPlacement, PortalHeaderAdminAction, PortalHeaderNavItem, ProgressSize, ProgressStatus, ProgressTone, RowAction, RowActionKind, RowActionsMode, SectionNavSubsectionData, SegmentedOption, SelectOption, SelectableCardOption, SidebarNavItemData, SkeletonPresetVariant, SkeletonVariant, SpinnerSize, StatCardBadgeTone, StatCardDensity, StatCardTone, StatTileTone, StatTileVariant, SwitchSize, TabOption, TextAs, TextTone, TextVariant, TimeSlot, ToastAction, ToastOptions, ToastPromiseMessages, ToastState, ToastTone, TooltipPosition, ViewToggleOption };
@@ -306,6 +306,7 @@ interface MapMarker {
306
306
  * <kf-map label="Ubicación del vehículo y del destino" [markers]="markers()" />
307
307
  * <kf-map size="sm" variant="preview" label="Vista previa de la ruta" [route]="route()" />
308
308
  * <kf-map label="Flota completa" [markers]="fleet()" [controls]="['zoom', 'recenter', 'cluster', 'follow']" />
309
+ * <kf-map label="Flota completa" [markers]="fleet()" [clusterAbove]="50" />
309
310
  */
310
311
  declare class MapView {
311
312
  readonly size: _angular_core.InputSignal<MapSize>;
@@ -329,6 +330,15 @@ declare class MapView {
329
330
  readonly theme: _angular_core.InputSignal<MapTheme>;
330
331
  /** Controles flotantes a mostrar — ver JSDoc de la clase y `MapControls.ts`. */
331
332
  readonly controls: _angular_core.InputSignal<MapControlKind[]>;
333
+ /**
334
+ * Umbral de agrupado automático: con `markers().length > clusterAbove` el
335
+ * mapa entra en modo cluster (el mismo que activa el control `cluster`),
336
+ * en vez de apilar cientos de pins y de paradas de Tab. `null` (default)
337
+ * conserva el comportamiento de siempre: nunca agrupa solo. Con el control
338
+ * `cluster` visible, el botón sigue mandando: un clic reemplaza lo
339
+ * automático hasta que se destruya el mapa.
340
+ */
341
+ readonly clusterAbove: _angular_core.InputSignal<number | null>;
332
342
  /**
333
343
  * Descripción accesible real del mapa — el `<div>` donde Mapbox renderiza
334
344
  * su canvas queda vacío de semántica para un lector de pantalla. Requerido
@@ -369,7 +379,10 @@ declare class MapView {
369
379
  /** Igual que `resolvedTheme()` pero público para el template — lo necesita
370
380
  * `MapThemePicker` (vía `MapControls`) para marcar la opción activa. */
371
381
  protected readonly activeTheme: _angular_core.Signal<MapTheme>;
372
- protected readonly clusterActive: _angular_core.WritableSignal<boolean>;
382
+ /** Elección manual del botón `cluster`; `null` = sin elegir, manda `clusterAbove`. */
383
+ private readonly clusterChoice;
384
+ private readonly clusterAutomatic;
385
+ protected readonly clusterActive: _angular_core.Signal<boolean>;
373
386
  protected readonly followActive: _angular_core.WritableSignal<boolean>;
374
387
  protected readonly hasLiveMarker: _angular_core.Signal<boolean>;
375
388
  protected readonly isFullscreen: _angular_core.WritableSignal<boolean>;
@@ -624,7 +637,7 @@ declare class MapView {
624
637
  protected handleThemeSelect(theme: MapTheme): void;
625
638
  protected handleFullscreenToggle(): void;
626
639
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MapView, never>;
627
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MapView, "kf-map", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "markers": { "alias": "markers"; "required": false; "isSignal": true; }; "route": { "alias": "route"; "required": false; "isSignal": true; }; "fitToMarkers": { "alias": "fitToMarkers"; "required": false; "isSignal": true; }; "center": { "alias": "center"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; }, { "markerSelect": "markerSelect"; "markerAction": "markerAction"; }, never, never, true, never>;
640
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MapView, "kf-map", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "markers": { "alias": "markers"; "required": false; "isSignal": true; }; "route": { "alias": "route"; "required": false; "isSignal": true; }; "fitToMarkers": { "alias": "fitToMarkers"; "required": false; "isSignal": true; }; "center": { "alias": "center"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "clusterAbove": { "alias": "clusterAbove"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; }, { "markerSelect": "markerSelect"; "markerAction": "markerAction"; }, never, never, true, never>;
628
641
  }
629
642
 
630
643
  export { MAP_CONFIG, MapControls, MapView, STYLE_BY_THEME, THEME_LABEL, THEME_ORDER, provideMapConfig };