@kaufmann-lab/ui 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, Component, model, signal, inject, DestroyRef, viewChild, computed, afterNextRender, booleanAttribute } from '@angular/core';
3
- import { Divider, Select, Text, Icon, Modal, DataTable, EmptyState, Pagination, Link } from '@kaufmann-lab/ui/core';
2
+ import { input, output, Component, model, booleanAttribute, signal, inject, DestroyRef, viewChild, computed, afterRenderEffect, untracked, afterNextRender } from '@angular/core';
3
+ import { Divider, Select, Text, Button, ContentState, FilterChips, Icon, Input, Modal, DataTable, EmptyState, Pagination, Skeleton, Link } from '@kaufmann-lab/ui/core';
4
4
  import { NgTemplateOutlet } from '@angular/common';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
5
7
  import { matchMediaSignal, normalizeForSearch } from '@kaufmann-lab/ui/shared';
6
8
 
7
9
  /**
@@ -45,7 +47,7 @@ class CompanySelector {
45
47
  ...(ngDevMode ? [{ debugName: "meta" }] : /* istanbul ignore next */ []));
46
48
  valueChange = output();
47
49
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CompanySelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
48
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: CompanySelector, isStandalone: true, selector: "kf-company-selector", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, companies: { classPropertyName: "companies", publicName: "companies", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<kf-select\n class=\"kf-company-selector__select\"\n [label]=\"label()\"\n [options]=\"companies()\"\n [value]=\"value()\"\n (valueChange)=\"valueChange.emit($event)\"\n/>\n@if (meta().length) {\n <div class=\"kf-company-selector__meta\">\n @for (item of meta(); track item.label; let last = $last) {\n <div class=\"kf-company-selector__meta-item\">\n <kf-text as=\"span\" variant=\"overline\">{{ item.label }}</kf-text>\n <span class=\"kf-company-selector__meta-value\">{{ item.value }}</span>\n </div>\n @if (!last) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n </div>\n}\n", 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-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", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold"], outputs: ["valueChange"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
50
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: CompanySelector, isStandalone: true, selector: "kf-company-selector", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, companies: { classPropertyName: "companies", publicName: "companies", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<kf-select\n class=\"kf-company-selector__select\"\n [label]=\"label()\"\n [options]=\"companies()\"\n [value]=\"value()\"\n (valueChange)=\"valueChange.emit($event)\"\n/>\n@if (meta().length) {\n <div class=\"kf-company-selector__meta\">\n @for (item of meta(); track item.label; let last = $last) {\n <div class=\"kf-company-selector__meta-item\">\n <kf-text as=\"span\" variant=\"overline\">{{ item.label }}</kf-text>\n <span class=\"kf-company-selector__meta-value\">{{ item.value }}</span>\n </div>\n @if (!last) {\n <kf-divider orientation=\"vertical\" />\n }\n }\n </div>\n}\n", 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-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", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold", "ariaLabel", "icon", "panelTitle", "panelWidth"], outputs: ["valueChange"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
49
51
  }
50
52
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CompanySelector, decorators: [{
51
53
  type: Component,
@@ -95,10 +97,8 @@ const PAGE_SIZE = 10;
95
97
  * este modo necesita. Compone también `kf-data-table` para la tabla
96
98
  * completa (columnas configurables, fade de overflow horizontal ya
97
99
  * resuelto ahí) y `kf-pagination` para paginar — ninguno de los tres se
98
- * reimplementa. Agrega un dropdown propio de "Ordenar por" (calibrado
99
- * visualmente contra el trigger de `kf-select`, pero compacto: `kf-select`
100
- * fuerza un ancho fijo de 240px pensado para formularios, no para una
101
- * barra de herramientas de tabla — ver JSDoc de `sortOpen` más abajo).
100
+ * reimplementa, igual que el buscador (`kf-input`), el orden (`kf-select`)
101
+ * y los chips (`kf-filter-chips`).
102
102
  *
103
103
  * En mobile, `kf-data-table[cardable]` con `defaultView` forzado a
104
104
  * `'cards'` decide la vista (mismo criterio que `matchMediaSignal` ya
@@ -106,12 +106,70 @@ const PAGE_SIZE = 10;
106
106
  * no tiene sentido ofrecer un toggle tabla/cards que en mobile daría una
107
107
  * tabla angosta con scroll horizontal.
108
108
  *
109
- * DEUDA TÉCNICA CONOCIDA (confirmada con el usuario, ver CHANGELOG.md): el
110
- * diseño validado con mockups pedía que en mobile el modal ocupe el 100%
111
- * de la pantalla (botón "atrás", sin overlay centrado) — `kf-modal` no
112
- * tiene ese modo, así que hoy en mobile se ve como un diálogo centrado
113
- * angosto (`width: min(480px, ...)`). Es funcional (cards legibles,
114
- * búsqueda, chips, paginación), solo visualmente distinto del mockup.
109
+ * El modal mide `min(modalWidth, 100vw - 32px)` (default 1040px) con alto
110
+ * máximo de 85vh: encabezado (título + conteo "12 de 86 cotizaciones" en
111
+ * región `aria-live`) y filtros FIJOS arriba, solo la lista se desplaza. En
112
+ * ≤640px de viewport ocupa toda la pantalla (`fullscreenOnMobile` de
113
+ * `kf-modal`). Filtros: fila 1 = buscador (`kf-input`, crece, mínimo 240px)
114
+ * + orden (`kf-select` estándar); fila 2 = `kf-filter-chips` con contador y
115
+ * un chip "Todas" (`allChipLabel`) — en varias líneas en escritorio, una
116
+ * sola línea con scroll horizontal en mobile. Elegir un ítem (o el actual)
117
+ * cierra el modal.
118
+ *
119
+ * ## Trigger (variante `inline`)
120
+ * - `triggerSize="default"` (histórico): tarjeta a ancho completo con
121
+ * miniatura y resumen.
122
+ * - `triggerSize="compact"`: control de barra de herramientas de alto FIJO
123
+ * (`--kf-quote-picker-trigger-height`, 48px), sin miniatura: línea 1 =
124
+ * título en mono + estado (columnas `status`), línea 2 = primer campo con
125
+ * elipsis, y caret. Ancho flexible (`min-width: 0`). Su nombre accesible
126
+ * es el texto visible con un prefijo: "Cambiar cotización. Actual: <id>,
127
+ * <estado>, <modelo>".
128
+ * - `inlinePanel` (default `true`): con `false`, el trigger abre DIRECTO el
129
+ * modal del picker (`aria-haspopup="dialog"`), sin panel inline — el mismo
130
+ * modal que la variante `modal`, así todas las pantallas comparten un
131
+ * único selector.
132
+ *
133
+ * Con `loading` el trigger compacto muestra un esqueleto del MISMO alto
134
+ * (región `role="status"` con `loadingLabel`), así la barra no salta.
135
+ *
136
+ * <kf-quote-picker [items]="quotes()" [columns]="columns" [idKey]="idKey"
137
+ * triggerSize="compact" [inlinePanel]="false" modalLayout="list"
138
+ * [selectedId]="id()" [chips]="chips()" [sortOptions]="sortOptions"
139
+ * [itemLabels]="{ singular: 'cotización', plural: 'cotizaciones' }"
140
+ * (selectedIdChange)="go($event)" />
141
+ *
142
+ * ## `modalLayout` (modal de la variante `modal` y el de `inlinePanel` en false)
143
+ * - `'table'` (default, comportamiento histórico): `kf-data-table` paginada
144
+ * con toggle Tabla/Tarjetas. La fila/tarjeta de `selectedId` lleva
145
+ * `aria-current="true"` y fondo `primary-soft` (`currentRowKey`).
146
+ * - `'list'`: lista de una línea por ítem con COLUMNAS ALINEADAS (CSS grid
147
+ * + `subgrid`: cada pista mide lo mismo en todas las filas), sin paginar
148
+ * ni toggle; se desplaza el cuerpo del modal. Orden de columnas: título
149
+ * (`cardRole: 'title'`, mono) | primer campo (flexible, una línea con
150
+ * elipsis y `title`) | resto de campos (`width` o su ancho de contenido;
151
+ * `align: 'end'` para montos, cifras tabulares) | `status` (hasta
152
+ * 13.75rem; el consumidor pasa `kf-badge truncate` con `title`) | check
153
+ * de la fila actual. Cabecera de columnas discreta y fija al desplazar
154
+ * (`listHeader`). Filas de 52px como mínimo; la de `selectedId` lleva
155
+ * `aria-current="true"`, fondo `primary-soft` y un check, y al abrir se
156
+ * desplaza a la vista. Con la lista de hasta 560px de ancho (container
157
+ * query, no viewport): 2 líneas (título + estado / campos), con el primer
158
+ * campo en hasta 2 líneas.
159
+ *
160
+ * Límite conocido: sin virtualización. Cada fila es un `<button>` en el
161
+ * DOM (búsqueda, foco y `aria-current` funcionan sin trucos); se probó
162
+ * fluido con 120 filas y sigue usable con unos cientos. Para miles,
163
+ * filtrar en el servidor antes de pasar `items`.
164
+ *
165
+ * En la variante modal, además: orden con `kf-select` estándar (ícono
166
+ * `sort`, "Ordenar por"), chips con `aria-pressed`, buscador con
167
+ * `searchAriaLabel` y foco inicial (`initialFocus` de `kf-modal`), conteo
168
+ * en una región `aria-live="polite"` (`itemLabels`), y estados
169
+ * `loading` (spinner con `loadingLabel`) y `error` (mensaje + botón
170
+ * "Reintentar" que emite `retry`) vía `kf-content-state`. El vacío "Sin
171
+ * resultados para esta búsqueda" solo aparece sin carga ni error. Escape
172
+ * con el panel del `kf-select` abierto cierra solo el select.
115
173
  *
116
174
  * <kf-quote-picker
117
175
  * [items]="quotes()"
@@ -170,7 +228,47 @@ class QuotePicker {
170
228
  * ícono fijo. Default `'file'` solo por no dejar el input sin valor. */
171
229
  placeholderIcon = input('file', /* @ts-ignore */
172
230
  ...(ngDevMode ? [{ debugName: "placeholderIcon" }] : /* istanbul ignore next */ []));
231
+ /** Disposición de la variante `modal`. Ver JSDoc de la clase. */
232
+ modalLayout = input('table', /* @ts-ignore */
233
+ ...(ngDevMode ? [{ debugName: "modalLayout" }] : /* istanbul ignore next */ []));
234
+ /** Nombre accesible del buscador. Default: `searchPlaceholder`. */
235
+ searchAriaLabel = input(/* @ts-ignore */
236
+ ...(ngDevMode ? [undefined, { debugName: "searchAriaLabel" }] : /* istanbul ignore next */ []));
237
+ /** Estado de carga: spinner con `loadingLabel` en el modal y, con
238
+ * `triggerSize="compact"`, un esqueleto del trigger del mismo alto. */
239
+ loading = input(false, /* @ts-ignore */
240
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
241
+ /** Texto del estado de carga. */
242
+ loadingLabel = input('Cargando…', /* @ts-ignore */
243
+ ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
244
+ /** Mensaje de error de la variante `modal`; con valor muestra el estado de error y "Reintentar". */
245
+ error = input(null, /* @ts-ignore */
246
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
247
+ /** Tamaño del trigger de la variante `inline`. Ver JSDoc de la clase. */
248
+ triggerSize = input('default', /* @ts-ignore */
249
+ ...(ngDevMode ? [{ debugName: "triggerSize" }] : /* istanbul ignore next */ []));
250
+ /** Con `false`, el trigger abre directo el modal (sin panel inline). */
251
+ inlinePanel = input(true, { ...(ngDevMode ? { debugName: "inlinePanel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
252
+ /** Ancho máximo del modal (se topa con el viewport menos 32px). */
253
+ modalWidth = input('1040px', /* @ts-ignore */
254
+ ...(ngDevMode ? [{ debugName: "modalWidth" }] : /* istanbul ignore next */ []));
255
+ /** Sustantivo del conteo del encabezado. Default "resultado/resultados". */
256
+ itemLabels = input({
257
+ singular: 'resultado',
258
+ plural: 'resultados',
259
+ }, /* @ts-ignore */
260
+ ...(ngDevMode ? [{ debugName: "itemLabels" }] : /* istanbul ignore next */ []));
261
+ /** Chip inicial "sin filtro" con el total. Vacío (`''`) lo oculta. */
262
+ allChipLabel = input('Todas', /* @ts-ignore */
263
+ ...(ngDevMode ? [{ debugName: "allChipLabel" }] : /* istanbul ignore next */ []));
264
+ /** Nombre accesible del grupo de chips del modal. */
265
+ chipsAriaLabel = input('Filtrar resultados', /* @ts-ignore */
266
+ ...(ngDevMode ? [{ debugName: "chipsAriaLabel" }] : /* istanbul ignore next */ []));
267
+ /** Cabecera de columnas en `modalLayout="list"` (solo visual). */
268
+ listHeader = input(true, { ...(ngDevMode ? { debugName: "listHeader" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
173
269
  selectedIdChange = output();
270
+ /** "Reintentar" del estado de error. */
271
+ retry = output();
174
272
  /** Disparado al superar `expandThreshold` en modo inline; el consumidor decide pasar a `modal`. */
175
273
  expandToModal = output();
176
274
  searchQuery = signal('', /* @ts-ignore */
@@ -190,9 +288,15 @@ class QuotePicker {
190
288
  * vs. cards apiladas dentro del modal.
191
289
  */
192
290
  isMobile = matchMediaSignal('(max-width: 900px)');
291
+ /** Mismo corte que el modal a pantalla completa: chips en una sola línea. */
292
+ isNarrowViewport = matchMediaSignal('(max-width: 640px)');
193
293
  destroyRef = inject(DestroyRef);
194
294
  previewScrollEl = viewChild('previewScroll', /* @ts-ignore */
195
295
  ...(ngDevMode ? [{ debugName: "previewScrollEl" }] : /* istanbul ignore next */ []));
296
+ modalListEl = viewChild('modalList', /* @ts-ignore */
297
+ ...(ngDevMode ? [{ debugName: "modalListEl" }] : /* istanbul ignore next */ []));
298
+ /** Para desplazar la fila actual a la vista UNA vez por apertura, no en cada render. */
299
+ scrolledToCurrent = false;
196
300
  /** Mismo mecanismo de fade que `kf-data-table` — ver comentario en QuotePicker.css. */
197
301
  canFadeBottom = signal(false, /* @ts-ignore */
198
302
  ...(ngDevMode ? [{ debugName: "canFadeBottom" }] : /* istanbul ignore next */ []));
@@ -262,12 +366,107 @@ class QuotePicker {
262
366
  return this.sortedItems().slice(start, start + PAGE_SIZE);
263
367
  }, /* @ts-ignore */
264
368
  ...(ngDevMode ? [{ debugName: "pagedItems" }] : /* istanbul ignore next */ []));
369
+ resolvedSearchAriaLabel = computed(() => this.searchAriaLabel() ?? this.searchPlaceholder(), /* @ts-ignore */
370
+ ...(ngDevMode ? [{ debugName: "resolvedSearchAriaLabel" }] : /* istanbul ignore next */ []));
371
+ /** Estado del cuerpo del modal para `kf-content-state`. */
372
+ modalState = computed(() => this.loading() ? 'loading' : this.error() ? 'error' : 'ready', /* @ts-ignore */
373
+ ...(ngDevMode ? [{ debugName: "modalState" }] : /* istanbul ignore next */ []));
374
+ /**
375
+ * Conteo del encabezado, en región `aria-live`: "86 cotizaciones" sin
376
+ * filtros, "12 de 86 cotizaciones" con búsqueda o chip. Vacío mientras
377
+ * carga o con error.
378
+ */
379
+ resultsLabel = computed(() => {
380
+ if (this.modalState() !== 'ready') {
381
+ return '';
382
+ }
383
+ const total = this.items().length;
384
+ const shown = this.sortedItems().length;
385
+ const { singular, plural } = this.itemLabels();
386
+ const noun = total === 1 ? singular : plural;
387
+ return shown === total ? `${total} ${noun}` : `${shown} de ${total} ${noun}`;
388
+ }, /* @ts-ignore */
389
+ ...(ngDevMode ? [{ debugName: "resultsLabel" }] : /* istanbul ignore next */ []));
390
+ /** Chips del modal en el formato de `kf-filter-chips`, con "Todas" al inicio. */
391
+ chipOptions = computed(() => {
392
+ const chips = this.chips().map((chip) => ({
393
+ value: chip.value,
394
+ label: chip.label,
395
+ count: chip.count,
396
+ }));
397
+ const allLabel = this.allChipLabel();
398
+ return allLabel
399
+ ? [{ value: '', label: allLabel, count: this.items().length }, ...chips]
400
+ : chips;
401
+ }, /* @ts-ignore */
402
+ ...(ngDevMode ? [{ debugName: "chipOptions" }] : /* istanbul ignore next */ []));
403
+ /**
404
+ * Pistas de la grilla de `modalLayout="list"`, en el orden visual: título,
405
+ * primer campo (flexible), resto, estados y el check. Todas las filas
406
+ * comparten estas pistas vía `subgrid`, por eso las columnas alinean.
407
+ */
408
+ listGridColumns = computed(() => {
409
+ const tracks = [];
410
+ const titleCol = this.titleColumn();
411
+ const primary = this.primaryFieldColumn();
412
+ if (titleCol) {
413
+ tracks.push(titleCol.width ?? 'max-content');
414
+ }
415
+ if (primary) {
416
+ tracks.push(primary.width ?? 'minmax(0, 1fr)');
417
+ }
418
+ for (const col of this.restFieldColumns()) {
419
+ tracks.push(col.width ?? (col.truncate ? `minmax(0, ${col.maxWidth ?? 'max-content'})` : 'max-content'));
420
+ }
421
+ for (const col of this.statusColumns()) {
422
+ tracks.push(col.width ?? `fit-content(${col.maxWidth ?? '13.75rem'})`);
423
+ }
424
+ tracks.push('1.25rem');
425
+ return tracks.join(' ');
426
+ }, /* @ts-ignore */
427
+ ...(ngDevMode ? [{ debugName: "listGridColumns" }] : /* istanbul ignore next */ []));
428
+ sortSelectOptions = computed(() => this.sortOptions().map((option) => ({ value: option.value, label: option.label })), /* @ts-ignore */
429
+ ...(ngDevMode ? [{ debugName: "sortSelectOptions" }] : /* istanbul ignore next */ []));
430
+ statusColumns = computed(() => this.columns().filter((c) => c.cardRole === 'status'), /* @ts-ignore */
431
+ ...(ngDevMode ? [{ debugName: "statusColumns" }] : /* istanbul ignore next */ []));
432
+ /** Primer campo (ej. modelo): ocupa el espacio libre de la fila en `modalLayout="list"`. */
433
+ primaryFieldColumn = computed(() => this.fieldColumns()[0], /* @ts-ignore */
434
+ ...(ngDevMode ? [{ debugName: "primaryFieldColumn" }] : /* istanbul ignore next */ []));
435
+ restFieldColumns = computed(() => this.fieldColumns().slice(1), /* @ts-ignore */
436
+ ...(ngDevMode ? [{ debugName: "restFieldColumns" }] : /* istanbul ignore next */ []));
437
+ /** Alineada al final: `align: 'end'` o el histórico `right: true`. */
438
+ isEndAligned(column) {
439
+ return column.align === 'end' || !!column.right;
440
+ }
441
+ /** `title` con el valor completo de una celda que puede cortarse con elipsis. */
442
+ cellTitle(item, column) {
443
+ return this.cellText(item, column) || null;
444
+ }
445
+ isSelected(item) {
446
+ return this.idKey()(item) === this.selectedId();
447
+ }
265
448
  sortLabel = computed(() => {
266
449
  const current = this.sortOptions().find((option) => option.value === this.sort());
267
450
  return current?.label ?? 'Ordenar por';
268
451
  }, /* @ts-ignore */
269
452
  ...(ngDevMode ? [{ debugName: "sortLabel" }] : /* istanbul ignore next */ []));
270
453
  constructor() {
454
+ afterRenderEffect(() => {
455
+ const list = this.modalListEl()?.nativeElement;
456
+ if (!this.open() || !list) {
457
+ this.scrolledToCurrent = false;
458
+ return;
459
+ }
460
+ untracked(() => {
461
+ if (this.scrolledToCurrent) {
462
+ return;
463
+ }
464
+ this.scrolledToCurrent = true;
465
+ list
466
+ .querySelector('.kf-quote-picker__row--current')
467
+ ?.scrollIntoView?.({ block: 'nearest' });
468
+ });
469
+ });
271
470
  afterNextRender(() => {
272
471
  const el = this.previewScrollEl()?.nativeElement;
273
472
  if (!el) {
@@ -296,14 +495,32 @@ class QuotePicker {
296
495
  toggleExpandedPanel() {
297
496
  this.expanded.set(!this.expanded());
298
497
  }
498
+ /** Click en el trigger: alterna el panel inline o, sin él, abre el modal. */
499
+ onTriggerClick() {
500
+ if (this.inlinePanel()) {
501
+ this.toggleExpandedPanel();
502
+ }
503
+ else {
504
+ this.open.set(true);
505
+ }
506
+ }
299
507
  onSearchInput(event) {
300
- this.searchQuery.set(event.target.value);
508
+ this.onSearchChange(event.target.value);
509
+ }
510
+ /** Buscador del modal (`kf-input` con `ngModel`), incluido el botón "×". */
511
+ onSearchChange(value) {
512
+ this.searchQuery.set(value ?? '');
301
513
  this.page.set(1);
302
514
  }
303
515
  selectChip(value) {
304
516
  this.activeChip.set(this.activeChip() === value ? null : value);
305
517
  this.page.set(1);
306
518
  }
519
+ /** Chips del modal: `''` es "Todas" (sin filtro). */
520
+ onChipChange(value) {
521
+ this.activeChip.set(value || null);
522
+ this.page.set(1);
523
+ }
307
524
  select(item) {
308
525
  const id = this.idKey()(item);
309
526
  // El panel se cierra siempre al elegir (haya cambiado el id o no) —
@@ -312,6 +529,9 @@ class QuotePicker {
312
529
  // consumidor sería una carga que ningún otro componente de la
313
530
  // librería le pide.
314
531
  this.expanded.set(false);
532
+ // El modal también se cierra al elegir: el consumidor ya no necesita
533
+ // cerrarlo a mano en `selectedIdChange` (hacerlo igual es inocuo).
534
+ this.open.set(false);
315
535
  if (id === this.selectedId()) {
316
536
  return;
317
537
  }
@@ -329,9 +549,14 @@ class QuotePicker {
329
549
  onTableRowClick(event) {
330
550
  this.select(event.row);
331
551
  }
552
+ /** @deprecated El orden de la variante modal ahora es un `kf-select`; este método ya no lo usa la plantilla. */
332
553
  toggleSort() {
333
554
  this.sortOpen.update((open) => !open);
334
555
  }
556
+ onSortChange(value) {
557
+ this.sort.set(value);
558
+ }
559
+ /** Alterna el orden (elegir el vigente lo quita). Se conserva por compatibilidad. */
335
560
  setSort(value) {
336
561
  this.sort.set(this.sort() === value ? null : value);
337
562
  this.sortOpen.set(false);
@@ -355,12 +580,27 @@ class QuotePicker {
355
580
  fieldColumns = computed(() => this.columns().filter((c) => !c.cardRole || c.cardRole === 'field'), /* @ts-ignore */
356
581
  ...(ngDevMode ? [{ debugName: "fieldColumns" }] : /* istanbul ignore next */ []));
357
582
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: QuotePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
358
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: QuotePicker, isStandalone: true, selector: "kf-quote-picker", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, selectedId: { classPropertyName: "selectedId", publicName: "selectedId", isSignal: true, isRequired: false, transformFunction: null }, idKey: { classPropertyName: "idKey", publicName: "idKey", isSignal: true, isRequired: true, transformFunction: null }, cellTemplates: { classPropertyName: "cellTemplates", publicName: "cellTemplates", isSignal: true, isRequired: false, transformFunction: null }, chips: { classPropertyName: "chips", publicName: "chips", isSignal: true, isRequired: false, transformFunction: null }, sortOptions: { classPropertyName: "sortOptions", publicName: "sortOptions", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, expandThreshold: { classPropertyName: "expandThreshold", publicName: "expandThreshold", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderIcon: { classPropertyName: "placeholderIcon", publicName: "placeholderIcon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sort: "sortChange", open: "openChange", selectedIdChange: "selectedIdChange", expandToModal: "expandToModal" }, viewQueries: [{ propertyName: "previewScrollEl", first: true, predicate: ["previewScroll"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (variant() === 'inline') {\n <div class=\"kf-quote-picker__inline\">\n <!-- Trigger colapsado (default): ancho completo, nunca una caja angosta. -->\n <button\n type=\"button\"\n class=\"kf-quote-picker__trigger\"\n [attr.aria-expanded]=\"expanded()\"\n (click)=\"toggleExpandedPanel()\"\n >\n <span class=\"kf-quote-picker__thumb\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedItem(), index: 0 }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"md\" />\n }\n </span>\n @if (selectedItem(); as item) {\n <span class=\"kf-quote-picker__trigger-summary\">\n @if (titleColumn(); as title) {\n <span class=\"kf-quote-picker__trigger-field kf-quote-picker__trigger-field--title\">{{\n cellValue(item, title)\n }}</span>\n }\n <!--\n En mobile solo se muestra el t\u00EDtulo (ID) \u2014 mostrar los 5 campos\n en una sola l\u00EDnea de ~90px de ancho real corta el texto sin\n elipsis a mitad del segundo campo (bug real medido:\n scrollWidth=519 vs. clientWidth=87 en 390px). El resto de los\n campos siguen disponibles en el panel expandido y en el modal,\n donde s\u00ED hay espacio horizontal.\n -->\n @if (!isMobile()) {\n @for (col of fieldColumns(); track col.key) {\n <span class=\"kf-quote-picker__trigger-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"kf-quote-picker__trigger-field\">{{ cellValue(item, col) }}</span>\n }\n }\n </span>\n } @else {\n <span class=\"kf-quote-picker__trigger-summary kf-quote-picker__trigger-summary--empty\"\n >Sin selecci\u00F3n</span\n >\n }\n <span class=\"kf-quote-picker__trigger-action\">\n {{ expanded() ? 'Cerrar' : title() }}\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"kf-quote-picker__chevron\"\n [class.kf-quote-picker__chevron--open]=\"expanded()\"\n />\n </span>\n </button>\n\n @if (expanded()) {\n <div class=\"kf-quote-picker__panel\">\n <div class=\"kf-quote-picker__toolbar\">\n <div class=\"kf-quote-picker__search\">\n <kf-icon name=\"search\" size=\"sm\" />\n <input\n type=\"text\"\n class=\"kf-quote-picker__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n />\n </div>\n @for (chip of chips(); track chip.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__chip\"\n [class.kf-quote-picker__chip--active]=\"activeChip() === chip.value\"\n (click)=\"selectChip(chip.value)\"\n >\n {{ chip.label }} ({{ chip.count }})\n </button>\n }\n </div>\n\n <div class=\"kf-quote-picker__results-wrap\">\n <div #previewScroll class=\"kf-quote-picker__results\" role=\"listbox\">\n @for (item of previewItems(); track trackItem(item, $index)) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__result\"\n [class.kf-quote-picker__result--active]=\"idKey()(item) === selectedId()\"\n role=\"option\"\n [attr.aria-selected]=\"idKey()(item) === selectedId()\"\n (click)=\"select(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <span class=\"kf-quote-picker__thumb kf-quote-picker__thumb--sm\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, index: $index }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"sm\" />\n }\n </span>\n @if (titleColumn(); as title) {\n <span\n class=\"kf-quote-picker__result-field kf-quote-picker__result-field--title\"\n >{{ cellValue(item, title) }}</span\n >\n }\n <!--\n Wrapper propio (no los spans sueltos) para los campos\n secundarios: en mobile necesita saltar de l\u00EDnea COMPLETO\n (mismo criterio que \"unidades\" no debe competir con\n \"modelo\" en la primera l\u00EDnea) \u2014 `flex-basis: 100%` en un\n solo elemento fuerza ese salto sin tener que asumir cu\u00E1l\n de los N campos gen\u00E9ricos es el m\u00E1s corto o el m\u00E1s largo.\n -->\n <span class=\"kf-quote-picker__result-fields\">\n @for (col of fieldColumns(); track col.key; let first = $first) {\n <span\n class=\"kf-quote-picker__result-field\"\n [class.kf-quote-picker__result-field--primary]=\"first\"\n >{{ cellValue(item, col) }}</span\n >\n }\n </span>\n </button>\n } @empty {\n <p class=\"kf-quote-picker__empty\">Sin resultados para esta b\u00FAsqueda.</p>\n }\n </div>\n @if (canFadeBottom()) {\n <div class=\"kf-quote-picker__fade\" aria-hidden=\"true\"></div>\n }\n </div>\n\n @if (isHighVolume()) {\n <button type=\"button\" class=\"kf-quote-picker__see-all\" (click)=\"onSeeAll()\">\n Ver las {{ sortedItems().length }} en pantalla completa\n </button>\n }\n </div>\n }\n </div>\n} @else {\n <kf-modal [(open)]=\"open\" [title]=\"title()\" [dismissible]=\"true\">\n <div class=\"kf-quote-picker__modal-toolbar\">\n <div class=\"kf-quote-picker__search\">\n <kf-icon name=\"search\" size=\"sm\" />\n <input\n type=\"text\"\n class=\"kf-quote-picker__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n />\n </div>\n <div class=\"kf-quote-picker__modal-filters\">\n <div class=\"kf-quote-picker__modal-chips\">\n @for (chip of chips(); track chip.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__chip\"\n [class.kf-quote-picker__chip--active]=\"activeChip() === chip.value\"\n (click)=\"selectChip(chip.value)\"\n >\n {{ chip.label }} ({{ chip.count }})\n </button>\n }\n </div>\n @if (sortOptions().length > 0) {\n <div class=\"kf-quote-picker__sort\">\n <button\n type=\"button\"\n class=\"kf-quote-picker__sort-trigger\"\n [attr.aria-expanded]=\"sortOpen()\"\n (click)=\"toggleSort()\"\n >\n <kf-icon name=\"sort\" size=\"sm\" />\n {{ sortLabel() }}\n <kf-icon name=\"chevron-down\" size=\"sm\" />\n </button>\n @if (sortOpen()) {\n <div class=\"kf-quote-picker__sort-panel\">\n @for (option of sortOptions(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__sort-option\"\n [class.kf-quote-picker__sort-option--active]=\"sort() === option.value\"\n (click)=\"setSort(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <kf-data-table\n [columns]=\"columns()\"\n [rows]=\"pagedItems()\"\n [cardable]=\"true\"\n [defaultView]=\"isMobile() ? 'cards' : 'table'\"\n [cellTemplates]=\"cellTemplates()\"\n [rowKey]=\"rowKeyFn\"\n (rowClick)=\"onTableRowClick($event)\"\n >\n <kf-empty-state empty icon=\"file\">Sin resultados para esta b\u00FAsqueda.</kf-empty-state>\n @if (sortedItems().length > 0) {\n <kf-pagination footer [page]=\"page()\" [pages]=\"pages()\" (pageChange)=\"page.set($event)\">\n {{ sortedItems().length }} resultados\n </kf-pagination>\n }\n </kf-data-table>\n </kf-modal>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-quote-picker__trigger{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-primary);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);cursor:pointer;font:inherit;text-align:left}.kf-quote-picker__trigger:hover{box-shadow:var(--kf-shadow-md)}.kf-quote-picker__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__thumb{display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);color:var(--kf-color-gray-400)}.kf-quote-picker__thumb--sm{width:36px;height:36px}.kf-quote-picker__result--active .kf-quote-picker__thumb{background:var(--kf-color-white);color:var(--kf-color-primary)}.kf-quote-picker__trigger-summary{flex:1;min-width:0;display:flex;align-items:baseline;gap:var(--kf-space-xs);overflow:hidden;white-space:nowrap;color:var(--kf-color-ink)}.kf-quote-picker__trigger-summary--empty{color:var(--kf-color-text-muted)}.kf-quote-picker__trigger-field--title{font-family:var(--kf-font-title);font-weight:700;font-size:1.05rem;color:var(--kf-color-ink)}.kf-quote-picker__trigger-field{font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__trigger-sep{color:var(--kf-color-border-strong)}.kf-quote-picker__trigger-action{flex:none;display:flex;align-items:center;gap:6px;color:var(--kf-color-primary);font-size:.85rem;font-weight:600}.kf-quote-picker__chevron{transition:transform .14s}.kf-quote-picker__chevron--open{transform:rotate(180deg)}.kf-quote-picker__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);margin-top:var(--kf-space-sm);padding:var(--kf-space-md) var(--kf-space-lg);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-quote-picker__toolbar{display:flex;flex-wrap:wrap;gap:var(--kf-space-sm)}.kf-quote-picker__search{flex:1;min-width:200px;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);color:var(--kf-color-text-muted)}.kf-quote-picker__search:focus-within{border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__search-input{flex:1;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-ink)}.kf-quote-picker__search-input::placeholder{color:var(--kf-color-text-muted)}.kf-quote-picker__chip{flex:none;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);font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}.kf-quote-picker__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}.kf-quote-picker__results-wrap{position:relative}.kf-quote-picker__results{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--kf-space-sm);max-height:340px;overflow-y:auto}@media(max-width:900px){.kf-quote-picker__results{grid-template-columns:1fr}}.kf-quote-picker__result{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);background:var(--kf-color-white);cursor:pointer;text-align:left;font:inherit;min-width:0;flex-wrap:wrap;row-gap:2px}.kf-quote-picker__result-fields{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;flex:1}.kf-quote-picker__result:hover{border-color:var(--kf-color-border)}.kf-quote-picker__result--active{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-quote-picker__result-field{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__result-field--title{flex:none;min-width:92px;font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__result-field--primary{flex:1;min-width:0}@media(max-width:900px){.kf-quote-picker__result-fields{flex-basis:100%;flex-wrap:wrap;row-gap:2px;padding-left:calc(36px + var(--kf-space-sm))}.kf-quote-picker__result-field--primary{flex:none;min-width:0;max-width:100%}}.kf-quote-picker__fade{position:absolute;left:0;right:0;bottom:0;height:36px;background:linear-gradient(to top,var(--kf-color-surface),transparent);pointer-events:none}.kf-quote-picker__see-all{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:var(--kf-space-sm);width:100%;margin-top:var(--kf-space-sm);padding:var(--kf-space-sm);border:1.5px dashed var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary);font:inherit;font-size:.85rem;font-weight:600;cursor:pointer}.kf-quote-picker__see-all:hover{background:var(--kf-color-primary-soft)}.kf-quote-picker__empty{grid-column:1 / -1;padding:var(--kf-space-md);text-align:center;color:var(--kf-color-text-muted);font-size:.85rem;margin:0}.kf-quote-picker__modal-toolbar{display:flex;flex-direction:column;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-md)}.kf-quote-picker__modal-filters{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);flex-wrap:wrap}.kf-quote-picker__modal-chips{display:flex;gap:var(--kf-space-sm);flex-wrap:wrap}.kf-quote-picker__sort{position:relative;flex:none}.kf-quote-picker__sort-trigger{display:flex;align-items:center;gap:var(--kf-space-sm);padding:6px 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);font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}.kf-quote-picker__sort-trigger:hover{border-color:var(--kf-color-border-strong)}.kf-quote-picker__sort-panel{position:absolute;top:calc(100% + 4px);right:0;z-index:1;min-width:180px;padding:var(--kf-space-xs) 0;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md)}.kf-quote-picker__sort-option{display:block;width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text);font:inherit;font-size:.85rem;text-align:left;cursor:pointer}.kf-quote-picker__sort-option:hover{background:var(--kf-color-primary-soft)}.kf-quote-picker__sort-option--active{color:var(--kf-color-primary);font-weight:600}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Modal, selector: "kf-modal", inputs: ["title", "open", "dismissible", "width"], outputs: ["openChange"] }, { kind: "component", type: DataTable, selector: "kf-data-table", inputs: ["columns", "rows", "loading", "rowKey", "flush", "cardable", "defaultView", "render", "cellTemplates", "density", "rowClickable", "pageSize", "pageSizeOptions", "itemLabel", "sort"], outputs: ["rowClick", "pageChange", "pageSizeChange", "sortChange"] }, { kind: "component", type: EmptyState, selector: "kf-empty-state", inputs: ["icon", "tone"] }, { kind: "component", type: Pagination, selector: "kf-pagination", inputs: ["page", "pages", "pageSizeOptions", "pageSize"], outputs: ["pageChange", "pageSizeChange"] }] });
583
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: QuotePicker, isStandalone: true, selector: "kf-quote-picker", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, selectedId: { classPropertyName: "selectedId", publicName: "selectedId", isSignal: true, isRequired: false, transformFunction: null }, idKey: { classPropertyName: "idKey", publicName: "idKey", isSignal: true, isRequired: true, transformFunction: null }, cellTemplates: { classPropertyName: "cellTemplates", publicName: "cellTemplates", isSignal: true, isRequired: false, transformFunction: null }, chips: { classPropertyName: "chips", publicName: "chips", isSignal: true, isRequired: false, transformFunction: null }, sortOptions: { classPropertyName: "sortOptions", publicName: "sortOptions", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, expandThreshold: { classPropertyName: "expandThreshold", publicName: "expandThreshold", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderIcon: { classPropertyName: "placeholderIcon", publicName: "placeholderIcon", isSignal: true, isRequired: false, transformFunction: null }, modalLayout: { classPropertyName: "modalLayout", publicName: "modalLayout", isSignal: true, isRequired: false, transformFunction: null }, searchAriaLabel: { classPropertyName: "searchAriaLabel", publicName: "searchAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, triggerSize: { classPropertyName: "triggerSize", publicName: "triggerSize", isSignal: true, isRequired: false, transformFunction: null }, inlinePanel: { classPropertyName: "inlinePanel", publicName: "inlinePanel", isSignal: true, isRequired: false, transformFunction: null }, modalWidth: { classPropertyName: "modalWidth", publicName: "modalWidth", isSignal: true, isRequired: false, transformFunction: null }, itemLabels: { classPropertyName: "itemLabels", publicName: "itemLabels", isSignal: true, isRequired: false, transformFunction: null }, allChipLabel: { classPropertyName: "allChipLabel", publicName: "allChipLabel", isSignal: true, isRequired: false, transformFunction: null }, chipsAriaLabel: { classPropertyName: "chipsAriaLabel", publicName: "chipsAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, listHeader: { classPropertyName: "listHeader", publicName: "listHeader", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sort: "sortChange", open: "openChange", selectedIdChange: "selectedIdChange", retry: "retry", expandToModal: "expandToModal" }, viewQueries: [{ propertyName: "previewScrollEl", first: true, predicate: ["previewScroll"], descendants: true, isSignal: true }, { propertyName: "modalListEl", first: true, predicate: ["modalList"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (variant() === 'inline') {\n <div class=\"kf-quote-picker__inline\">\n <!-- Trigger colapsado. `default`: tarjeta a ancho completo con miniatura\n (hist\u00F3rico). `compact`: control de barra de herramientas de alto fijo,\n sin miniatura. Con `inlinePanel` en false abre directo el modal. -->\n @if (triggerSize() === 'compact' && loading()) {\n <!-- Mismo alto que el trigger real: la barra no salta al llegar los datos. -->\n <div\n class=\"kf-quote-picker__trigger kf-quote-picker__trigger--compact kf-quote-picker__trigger--loading\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <span class=\"kf-quote-picker__sr-only\">{{ loadingLabel() }}</span>\n <span class=\"kf-quote-picker__compact\" aria-hidden=\"true\">\n <kf-skeleton width=\"35%\" height=\"12px\" />\n <kf-skeleton width=\"65%\" height=\"12px\" />\n </span>\n </div>\n } @else {\n <button\n type=\"button\"\n class=\"kf-quote-picker__trigger\"\n [class.kf-quote-picker__trigger--compact]=\"triggerSize() === 'compact'\"\n [attr.aria-expanded]=\"inlinePanel() ? expanded() : open()\"\n [attr.aria-haspopup]=\"inlinePanel() ? null : 'dialog'\"\n (click)=\"onTriggerClick()\"\n >\n @if (triggerSize() === 'compact') {\n <!-- Nombre accesible = texto visible con un prefijo y comas solo\n para lectores de pantalla: \"Cambiar cotizaci\u00F3n. Actual: <id>,\n <estado>, <modelo>\". -->\n <span class=\"kf-quote-picker__sr-only\">{{ title() }}. Actual: </span>\n @if (selectedItem(); as item) {\n <span class=\"kf-quote-picker__compact\">\n <span class=\"kf-quote-picker__compact-line\">\n @if (titleColumn(); as titleCol) {\n <span class=\"kf-quote-picker__compact-id\">{{ cellValue(item, titleCol) }}</span>\n }\n @for (col of statusColumns(); track col.key) {\n <span class=\"kf-quote-picker__sr-only\">, </span>\n <span class=\"kf-quote-picker__compact-status\">\n <ng-container\n *ngTemplateOutlet=\"cell; context: { $implicit: item, column: col, index: 0 }\"\n />\n </span>\n }\n </span>\n @if (primaryFieldColumn(); as primary) {\n <span class=\"kf-quote-picker__compact-line\">\n <span class=\"kf-quote-picker__sr-only\">, </span>\n <span class=\"kf-quote-picker__compact-primary\">{{\n cellValue(item, primary)\n }}</span>\n </span>\n }\n </span>\n } @else {\n <span class=\"kf-quote-picker__compact kf-quote-picker__trigger-summary--empty\"\n >Sin selecci\u00F3n</span\n >\n }\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"kf-quote-picker__chevron\"\n [class.kf-quote-picker__chevron--open]=\"inlinePanel() && expanded()\"\n />\n } @else {\n <span class=\"kf-quote-picker__thumb\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedItem(), index: 0 }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"md\" />\n }\n </span>\n @if (selectedItem(); as item) {\n <span class=\"kf-quote-picker__trigger-summary\">\n @if (titleColumn(); as title) {\n <span\n class=\"kf-quote-picker__trigger-field kf-quote-picker__trigger-field--title\"\n >{{ cellValue(item, title) }}</span\n >\n }\n <!--\n En mobile solo se muestra el t\u00EDtulo (ID) \u2014 mostrar los 5 campos\n en una sola l\u00EDnea de ~90px de ancho real corta el texto sin\n elipsis a mitad del segundo campo (bug real medido:\n scrollWidth=519 vs. clientWidth=87 en 390px). El resto de los\n campos siguen disponibles en el panel expandido y en el modal,\n donde s\u00ED hay espacio horizontal.\n -->\n @if (!isMobile()) {\n @for (col of fieldColumns(); track col.key) {\n <span class=\"kf-quote-picker__trigger-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"kf-quote-picker__trigger-field\">{{ cellValue(item, col) }}</span>\n }\n }\n </span>\n } @else {\n <span class=\"kf-quote-picker__trigger-summary kf-quote-picker__trigger-summary--empty\"\n >Sin selecci\u00F3n</span\n >\n }\n <span class=\"kf-quote-picker__trigger-action\">\n {{ inlinePanel() && expanded() ? 'Cerrar' : title() }}\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"kf-quote-picker__chevron\"\n [class.kf-quote-picker__chevron--open]=\"inlinePanel() && expanded()\"\n />\n </span>\n }\n </button>\n }\n\n @if (inlinePanel() && expanded()) {\n <div class=\"kf-quote-picker__panel\">\n <div class=\"kf-quote-picker__toolbar\">\n <div class=\"kf-quote-picker__search\">\n <kf-icon name=\"search\" size=\"sm\" />\n <input\n type=\"text\"\n class=\"kf-quote-picker__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"resolvedSearchAriaLabel()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n />\n </div>\n @for (chip of chips(); track chip.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__chip\"\n [class.kf-quote-picker__chip--active]=\"activeChip() === chip.value\"\n [attr.aria-pressed]=\"activeChip() === chip.value\"\n (click)=\"selectChip(chip.value)\"\n >\n {{ chip.label }} ({{ chip.count }})\n </button>\n }\n </div>\n\n <div class=\"kf-quote-picker__results-wrap\">\n <div #previewScroll class=\"kf-quote-picker__results\" role=\"listbox\">\n @for (item of previewItems(); track trackItem(item, $index)) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__result\"\n [class.kf-quote-picker__result--active]=\"idKey()(item) === selectedId()\"\n role=\"option\"\n [attr.aria-selected]=\"idKey()(item) === selectedId()\"\n (click)=\"select(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <span class=\"kf-quote-picker__thumb kf-quote-picker__thumb--sm\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, index: $index }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"sm\" />\n }\n </span>\n @if (titleColumn(); as title) {\n <span\n class=\"kf-quote-picker__result-field kf-quote-picker__result-field--title\"\n >{{ cellValue(item, title) }}</span\n >\n }\n <!--\n Wrapper propio (no los spans sueltos) para los campos\n secundarios: en mobile necesita saltar de l\u00EDnea COMPLETO\n (mismo criterio que \"unidades\" no debe competir con\n \"modelo\" en la primera l\u00EDnea) \u2014 `flex-basis: 100%` en un\n solo elemento fuerza ese salto sin tener que asumir cu\u00E1l\n de los N campos gen\u00E9ricos es el m\u00E1s corto o el m\u00E1s largo.\n -->\n <span class=\"kf-quote-picker__result-fields\">\n @for (col of fieldColumns(); track col.key; let first = $first) {\n <span\n class=\"kf-quote-picker__result-field\"\n [class.kf-quote-picker__result-field--primary]=\"first\"\n >{{ cellValue(item, col) }}</span\n >\n }\n </span>\n </button>\n } @empty {\n <p class=\"kf-quote-picker__empty\">Sin resultados para esta b\u00FAsqueda.</p>\n }\n </div>\n @if (canFadeBottom()) {\n <div class=\"kf-quote-picker__fade\" aria-hidden=\"true\"></div>\n }\n </div>\n\n @if (isHighVolume()) {\n <button type=\"button\" class=\"kf-quote-picker__see-all\" (click)=\"onSeeAll()\">\n Ver las {{ sortedItems().length }} en pantalla completa\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Modal: la variante `modal` y el que abre el trigger con `inlinePanel` en\n false. Zona de filtros FIJA (slot `kf-modal-toolbar`) y lista\n desplazable (cuerpo del modal). -->\n@if (variant() === 'modal' || !inlinePanel()) {\n <kf-modal\n class=\"kf-quote-picker__modal\"\n [(open)]=\"open\"\n [title]=\"title()\"\n [dismissible]=\"true\"\n [width]=\"modalWidth()\"\n maxHeight=\"85vh\"\n fullscreenOnMobile\n initialFocus=\".kf-quote-picker__modal-search\"\n >\n <p kf-modal-meta class=\"kf-quote-picker__count\" aria-live=\"polite\">{{ resultsLabel() }}</p>\n\n <div kf-modal-toolbar class=\"kf-quote-picker__filters\">\n <div class=\"kf-quote-picker__filters-row\">\n <kf-input\n class=\"kf-quote-picker__modal-search\"\n type=\"search\"\n icon=\"search\"\n clearable\n [placeholder]=\"searchPlaceholder()\"\n [ariaLabel]=\"resolvedSearchAriaLabel()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchChange($event)\"\n />\n @if (sortOptions().length > 0) {\n <kf-select\n class=\"kf-quote-picker__sort\"\n icon=\"sort\"\n ariaLabel=\"Ordenar por\"\n panelTitle=\"Ordenar por\"\n placeholder=\"Ordenar por\"\n [options]=\"sortSelectOptions()\"\n [value]=\"sort() ?? undefined\"\n (valueChange)=\"onSortChange($event)\"\n />\n }\n </div>\n @if (chips().length > 0) {\n <kf-filter-chips\n class=\"kf-quote-picker__chips\"\n deselectable\n [ariaLabel]=\"chipsAriaLabel()\"\n [options]=\"chipOptions()\"\n [value]=\"activeChip() ?? ''\"\n [layout]=\"isNarrowViewport() ? 'scroll' : 'wrap'\"\n (valueChange)=\"onChipChange($event)\"\n />\n }\n </div>\n\n <kf-content-state\n [state]=\"modalState()\"\n load=\"spinner\"\n [loadingLabel]=\"loadingLabel()\"\n [title]=\"error() ?? ''\"\n >\n <kf-button actions type=\"button\" variant=\"outline\" icon=\"refresh\" (click)=\"retry.emit()\">\n Reintentar\n </kf-button>\n @if (modalLayout() === 'list') {\n @if (sortedItems().length > 0) {\n <div class=\"kf-quote-picker__list-wrap\">\n <ul\n #modalList\n class=\"kf-quote-picker__list\"\n [style.grid-template-columns]=\"listGridColumns()\"\n >\n @if (listHeader()) {\n <!-- Cabecera visual de columnas: cada fila (bot\u00F3n) ya se anuncia\n completa, as\u00ED que para lectores de pantalla ser\u00EDa ruido. -->\n <li class=\"kf-quote-picker__list-head\" aria-hidden=\"true\">\n @if (titleColumn(); as titleCol) {\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--title\">\n <kf-text as=\"span\" variant=\"overline\">{{ titleCol.label }}</kf-text>\n </span>\n }\n @if (primaryFieldColumn(); as primary) {\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--primary\">\n <kf-text as=\"span\" variant=\"overline\">{{ primary.label }}</kf-text>\n </span>\n }\n @for (col of restFieldColumns(); track col.key) {\n <span\n class=\"kf-quote-picker__head-cell kf-quote-picker__cell--field\"\n [class.kf-quote-picker__cell--end]=\"isEndAligned(col)\"\n [class.kf-quote-picker__cell--center]=\"col.align === 'center'\"\n >\n <kf-text as=\"span\" variant=\"overline\">{{ col.label }}</kf-text>\n </span>\n }\n @for (col of statusColumns(); track col.key) {\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--status\">\n <kf-text as=\"span\" variant=\"overline\">{{ col.label }}</kf-text>\n </span>\n }\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--check\"></span>\n </li>\n }\n @for (item of sortedItems(); track trackItem(item, $index); let i = $index) {\n <li class=\"kf-quote-picker__list-item\">\n <button\n type=\"button\"\n class=\"kf-quote-picker__row\"\n [class.kf-quote-picker__row--current]=\"isSelected(item)\"\n [attr.aria-current]=\"isSelected(item) ? 'true' : null\"\n (click)=\"select(item)\"\n >\n <span class=\"kf-quote-picker__row-head\">\n @if (titleColumn(); as titleCol) {\n <span class=\"kf-quote-picker__row-title kf-quote-picker__cell--title\">\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: titleCol, index: i }\n \"\n />\n </span>\n }\n @for (col of statusColumns(); track col.key) {\n <span class=\"kf-quote-picker__row-status kf-quote-picker__cell--status\">\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: col, index: i }\n \"\n />\n </span>\n }\n </span>\n <span class=\"kf-quote-picker__row-body\">\n @if (primaryFieldColumn(); as primary) {\n <span\n class=\"kf-quote-picker__row-primary kf-quote-picker__cell--primary\"\n [attr.title]=\"cellTitle(item, primary)\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: primary, index: i }\n \"\n />\n </span>\n }\n @for (col of restFieldColumns(); track col.key) {\n <span\n class=\"kf-quote-picker__row-field kf-quote-picker__cell--field\"\n [class.kf-quote-picker__row-field--right]=\"isEndAligned(col)\"\n [class.kf-quote-picker__cell--end]=\"isEndAligned(col)\"\n [class.kf-quote-picker__cell--center]=\"col.align === 'center'\"\n [class.kf-quote-picker__cell--truncate]=\"col.truncate\"\n [attr.title]=\"col.truncate ? cellTitle(item, col) : null\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: col, index: i }\n \"\n />\n </span>\n }\n </span>\n <span class=\"kf-quote-picker__row-check kf-quote-picker__cell--check\">\n @if (isSelected(item)) {\n <kf-icon name=\"check\" size=\"sm\" />\n }\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n } @else {\n <kf-empty-state icon=\"file\">Sin resultados para esta b\u00FAsqueda.</kf-empty-state>\n }\n } @else {\n <kf-data-table\n [columns]=\"columns()\"\n [rows]=\"pagedItems()\"\n [cardable]=\"true\"\n [defaultView]=\"isMobile() ? 'cards' : 'table'\"\n [cellTemplates]=\"cellTemplates()\"\n [rowKey]=\"rowKeyFn\"\n [currentRowKey]=\"selectedId()\"\n (rowClick)=\"onTableRowClick($event)\"\n >\n <kf-empty-state empty icon=\"file\">Sin resultados para esta b\u00FAsqueda.</kf-empty-state>\n @if (sortedItems().length > 0) {\n <kf-pagination footer [page]=\"page()\" [pages]=\"pages()\" (pageChange)=\"page.set($event)\">\n {{ sortedItems().length }} resultados\n </kf-pagination>\n }\n </kf-data-table>\n }\n </kf-content-state>\n </kf-modal>\n}\n\n<!-- Celda de la lista compacta: el template del consumidor si existe (badge,\n monto formateado), si no el valor como texto. -->\n<ng-template #cell let-item let-column=\"column\" let-index=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, index: index }\"\n />\n } @else {\n {{ cellValue(item, column) }}\n }\n</ng-template>\n", styles: [":host{display:block;min-width:0;font-family:var(--kf-font-body)}.kf-quote-picker__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-quote-picker__trigger{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-primary);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);cursor:pointer;font:inherit;text-align:left}.kf-quote-picker__trigger:hover{box-shadow:var(--kf-shadow-md)}.kf-quote-picker__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__thumb{display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);color:var(--kf-color-gray-400)}.kf-quote-picker__thumb--sm{width:36px;height:36px}.kf-quote-picker__result--active .kf-quote-picker__thumb{background:var(--kf-color-white);color:var(--kf-color-primary)}.kf-quote-picker__trigger-summary{flex:1;min-width:0;display:flex;align-items:baseline;gap:var(--kf-space-xs);overflow:hidden;white-space:nowrap;color:var(--kf-color-ink)}.kf-quote-picker__trigger-summary--empty{color:var(--kf-color-text-muted)}.kf-quote-picker__trigger-field--title{font-family:var(--kf-font-title);font-weight:700;font-size:1.05rem;color:var(--kf-color-ink)}.kf-quote-picker__trigger-field{font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__trigger-sep{color:var(--kf-color-border-strong)}.kf-quote-picker__trigger-action{flex:none;display:flex;align-items:center;gap:6px;color:var(--kf-color-primary);font-size:.85rem;font-weight:600}.kf-quote-picker__chevron{transition:transform .14s}.kf-quote-picker__chevron--open{transform:rotate(180deg)}.kf-quote-picker__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);margin-top:var(--kf-space-sm);padding:var(--kf-space-md) var(--kf-space-lg);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-quote-picker__toolbar{display:flex;flex-wrap:wrap;gap:var(--kf-space-sm)}.kf-quote-picker__search{flex:1;min-width:200px;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);color:var(--kf-color-text-muted)}.kf-quote-picker__search:focus-within{border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__search-input{flex:1;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-ink)}.kf-quote-picker__search-input::placeholder{color:var(--kf-color-text-muted)}.kf-quote-picker__chip{flex:none;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);font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}.kf-quote-picker__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}.kf-quote-picker__results-wrap{position:relative}.kf-quote-picker__results{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--kf-space-sm);max-height:340px;overflow-y:auto}@media(max-width:900px){.kf-quote-picker__results{grid-template-columns:1fr}}.kf-quote-picker__result{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);background:var(--kf-color-white);cursor:pointer;text-align:left;font:inherit;min-width:0;flex-wrap:wrap;row-gap:2px}.kf-quote-picker__result-fields{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;flex:1}.kf-quote-picker__result:hover{border-color:var(--kf-color-border)}.kf-quote-picker__result--active{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-quote-picker__result-field{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__result-field--title{flex:none;min-width:92px;font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__result-field--primary{flex:1;min-width:0}@media(max-width:900px){.kf-quote-picker__result-fields{flex-basis:100%;flex-wrap:wrap;row-gap:2px;padding-left:calc(36px + var(--kf-space-sm))}.kf-quote-picker__result-field--primary{flex:none;min-width:0;max-width:100%}}.kf-quote-picker__fade{position:absolute;left:0;right:0;bottom:0;height:36px;background:linear-gradient(to top,var(--kf-color-surface),transparent);pointer-events:none}.kf-quote-picker__see-all{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:var(--kf-space-sm);width:100%;margin-top:var(--kf-space-sm);padding:var(--kf-space-sm);border:1.5px dashed var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary);font:inherit;font-size:.85rem;font-weight:600;cursor:pointer}.kf-quote-picker__see-all:hover{background:var(--kf-color-primary-soft)}.kf-quote-picker__empty{grid-column:1 / -1;padding:var(--kf-space-md);text-align:center;color:var(--kf-color-text-muted);font-size:.85rem;margin:0}.kf-quote-picker__trigger--compact{position:relative;height:var(--kf-quote-picker-trigger-height, 48px);min-width:0;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm) 0 var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:none;transition:border-color .14s,box-shadow .14s}.kf-quote-picker__trigger--compact:hover{border-color:var(--kf-color-border-strong);box-shadow:none}.kf-quote-picker__trigger--compact:focus-visible{border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__compact{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:2px;line-height:1.25}.kf-quote-picker__compact-line{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0}.kf-quote-picker__compact-id{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__compact-status{display:flex;min-width:0;overflow:hidden}.kf-quote-picker__compact-primary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-quote-picker__trigger--loading{display:flex;align-items:center;cursor:progress}.kf-quote-picker__trigger--compact .kf-quote-picker__chevron{flex:none;color:var(--kf-color-text)}@media(prefers-reduced-motion:reduce){.kf-quote-picker__trigger--compact{transition:none}}.kf-quote-picker__count{margin:0;font-size:var(--kf-font-size-body-sm);font-variant-numeric:tabular-nums;color:var(--kf-color-text)}.kf-quote-picker__count:empty{display:none}.kf-quote-picker__filters{container:kf-quote-picker-filters / inline-size;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-quote-picker__filters-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--kf-space-sm)}.kf-quote-picker__modal-search{flex:1 1 240px;min-width:240px}.kf-quote-picker__sort{flex:none}@container kf-quote-picker-filters (max-width: 520px){.kf-quote-picker__modal-search{flex-basis:100%;min-width:0}.kf-quote-picker__sort{width:100%}}.kf-quote-picker__list-wrap{container:kf-quote-picker-list / inline-size}.kf-quote-picker__list{display:grid;column-gap:var(--kf-space-md);row-gap:2px;margin:0;padding:0 0 2px;list-style:none}.kf-quote-picker__list-head,.kf-quote-picker__list-item{display:grid;grid-column:1 / -1;grid-template-columns:subgrid}.kf-quote-picker__list-head{position:sticky;top:0;z-index:1;align-items:end;padding:var(--kf-space-xs) var(--kf-space-md) var(--kf-space-sm);border-bottom:1px solid var(--kf-color-border);background:var(--kf-color-white)}.kf-quote-picker__row{grid-column:1 / -1;display:grid;grid-template-columns:subgrid;align-items:center;box-sizing:border-box;width:100%;min-height:52px;padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);font:inherit;font-size:var(--kf-font-size-body-sm);text-align:start;cursor:pointer;scroll-margin-top:48px;scroll-margin-bottom:var(--kf-space-sm)}.kf-quote-picker__row:hover{border-color:var(--kf-color-border);background:var(--kf-color-surface)}.kf-quote-picker__row:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:-2px}.kf-quote-picker__row--current,.kf-quote-picker__row--current:hover{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-quote-picker__row-head,.kf-quote-picker__row-body{display:contents}.kf-quote-picker__head-cell{min-width:0;white-space:nowrap}.kf-quote-picker__cell--title{order:0;white-space:nowrap}.kf-quote-picker__row-title{font-family:var(--kf-font-mono);font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__cell--primary{order:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-quote-picker__row-primary{color:var(--kf-color-ink)}.kf-quote-picker__cell--field{order:2;min-width:0;white-space:nowrap;font-variant-numeric:tabular-nums}.kf-quote-picker__cell--truncate{overflow:hidden;text-overflow:ellipsis}.kf-quote-picker__cell--end{justify-self:end;text-align:end}.kf-quote-picker__cell--center{justify-self:center;text-align:center}.kf-quote-picker__row-field--right{font-weight:600;color:var(--kf-color-ink)}.kf-quote-picker__cell--status{order:3;display:flex;min-width:0;overflow:hidden}.kf-quote-picker__cell--check{order:4;display:flex;justify-content:center;color:var(--kf-color-primary)}@container kf-quote-picker-list (max-width: 560px){.kf-quote-picker__list{display:flex;flex-direction:column}.kf-quote-picker__list-head{display:none}.kf-quote-picker__list-item{display:block}.kf-quote-picker__row{grid-template-columns:minmax(0,1fr) 1.25rem;grid-template-areas:\"head check\" \"body check\";column-gap:var(--kf-space-sm);row-gap:var(--kf-space-xs);padding:var(--kf-space-sm);scroll-margin-top:var(--kf-space-sm)}.kf-quote-picker__row-head,.kf-quote-picker__row-body{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0}.kf-quote-picker__row-head{grid-area:head;justify-content:space-between}.kf-quote-picker__row-body{grid-area:body;flex-wrap:wrap;row-gap:var(--kf-space-xs)}.kf-quote-picker__row-body .kf-quote-picker__cell--end{margin-left:auto}.kf-quote-picker__row-check{grid-area:check}.kf-quote-picker__cell--status{flex:0 1 auto;justify-content:flex-end;max-width:65%}.kf-quote-picker__cell--primary{flex:1 1 100%;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;white-space:normal;overflow-wrap:anywhere}.kf-quote-picker__cell--field{flex:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: "kf-button", inputs: ["variant", "size", "shape", "disabled", "type", "icon", "block", "loading", "ariaLabel", "ariaExpanded", "ariaPressed", "ariaHaspopup"] }, { kind: "component", type: ContentState, selector: "kf-content-state", inputs: ["state", "load", "title", "subtitle", "loadingLabel", "loadingTemplate", "loadingPreset"] }, { kind: "component", type: FilterChips, selector: "kf-filter-chips", inputs: ["options", "value", "ariaLabel", "layout", "deselectable"], outputs: ["valueChange"] }, { kind: "component", type: Icon, selector: "kf-icon", inputs: ["name", "size", "rotate", "filled"] }, { kind: "component", type: Input, selector: "kf-input", inputs: ["label", "required", "icon", "suffix", "hint", "invalid", "errorMessage", "disabled", "readonly", "type", "placeholder", "autocomplete", "name", "inputmode", "min", "max", "step", "maxlength", "toggleable", "ariaLabel", "clearable", "clearLabel"], outputs: ["cleared"] }, { kind: "component", type: Modal, selector: "kf-modal", inputs: ["title", "open", "dismissible", "width", "maxHeight", "fullscreenOnMobile", "initialFocus"], outputs: ["openChange"] }, { kind: "component", type: DataTable, selector: "kf-data-table", inputs: ["columns", "rows", "loading", "skeletonRows", "rowKey", "flush", "cardable", "defaultView", "showViewToggle", "cardMinWidth", "render", "cellTemplates", "density", "rowClickable", "cellAriaLabel", "ariaLabel", "currentRowKey", "pageSize", "pageSizeOptions", "page", "keepPageOnRowsChange", "itemLabel", "sort", "view"], outputs: ["rowClick", "cellClick", "pageChange", "pageSizeChange", "sortChange", "viewChange"] }, { kind: "component", type: EmptyState, selector: "kf-empty-state", inputs: ["icon", "tone", "variant", "title"] }, { kind: "component", type: Pagination, selector: "kf-pagination", inputs: ["page", "pages", "pageSizeOptions", "pageSize"], outputs: ["pageChange", "pageSizeChange"] }, { kind: "component", type: Select, selector: "kf-select", inputs: ["label", "options", "value", "placeholder", "disabled", "invalid", "errorMessage", "variant", "width", "searchable", "searchThreshold", "ariaLabel", "icon", "panelTitle", "panelWidth"], outputs: ["valueChange"] }, { kind: "component", type: Skeleton, selector: "kf-skeleton", inputs: ["variant", "width", "height"] }, { kind: "component", type: Text, selector: "kf-text", inputs: ["as", "variant", "tone"] }] });
359
584
  }
360
585
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: QuotePicker, decorators: [{
361
586
  type: Component,
362
- args: [{ selector: 'kf-quote-picker', imports: [NgTemplateOutlet, Icon, Modal, DataTable, EmptyState, Pagination], template: "@if (variant() === 'inline') {\n <div class=\"kf-quote-picker__inline\">\n <!-- Trigger colapsado (default): ancho completo, nunca una caja angosta. -->\n <button\n type=\"button\"\n class=\"kf-quote-picker__trigger\"\n [attr.aria-expanded]=\"expanded()\"\n (click)=\"toggleExpandedPanel()\"\n >\n <span class=\"kf-quote-picker__thumb\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedItem(), index: 0 }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"md\" />\n }\n </span>\n @if (selectedItem(); as item) {\n <span class=\"kf-quote-picker__trigger-summary\">\n @if (titleColumn(); as title) {\n <span class=\"kf-quote-picker__trigger-field kf-quote-picker__trigger-field--title\">{{\n cellValue(item, title)\n }}</span>\n }\n <!--\n En mobile solo se muestra el t\u00EDtulo (ID) \u2014 mostrar los 5 campos\n en una sola l\u00EDnea de ~90px de ancho real corta el texto sin\n elipsis a mitad del segundo campo (bug real medido:\n scrollWidth=519 vs. clientWidth=87 en 390px). El resto de los\n campos siguen disponibles en el panel expandido y en el modal,\n donde s\u00ED hay espacio horizontal.\n -->\n @if (!isMobile()) {\n @for (col of fieldColumns(); track col.key) {\n <span class=\"kf-quote-picker__trigger-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"kf-quote-picker__trigger-field\">{{ cellValue(item, col) }}</span>\n }\n }\n </span>\n } @else {\n <span class=\"kf-quote-picker__trigger-summary kf-quote-picker__trigger-summary--empty\"\n >Sin selecci\u00F3n</span\n >\n }\n <span class=\"kf-quote-picker__trigger-action\">\n {{ expanded() ? 'Cerrar' : title() }}\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"kf-quote-picker__chevron\"\n [class.kf-quote-picker__chevron--open]=\"expanded()\"\n />\n </span>\n </button>\n\n @if (expanded()) {\n <div class=\"kf-quote-picker__panel\">\n <div class=\"kf-quote-picker__toolbar\">\n <div class=\"kf-quote-picker__search\">\n <kf-icon name=\"search\" size=\"sm\" />\n <input\n type=\"text\"\n class=\"kf-quote-picker__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n />\n </div>\n @for (chip of chips(); track chip.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__chip\"\n [class.kf-quote-picker__chip--active]=\"activeChip() === chip.value\"\n (click)=\"selectChip(chip.value)\"\n >\n {{ chip.label }} ({{ chip.count }})\n </button>\n }\n </div>\n\n <div class=\"kf-quote-picker__results-wrap\">\n <div #previewScroll class=\"kf-quote-picker__results\" role=\"listbox\">\n @for (item of previewItems(); track trackItem(item, $index)) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__result\"\n [class.kf-quote-picker__result--active]=\"idKey()(item) === selectedId()\"\n role=\"option\"\n [attr.aria-selected]=\"idKey()(item) === selectedId()\"\n (click)=\"select(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <span class=\"kf-quote-picker__thumb kf-quote-picker__thumb--sm\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, index: $index }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"sm\" />\n }\n </span>\n @if (titleColumn(); as title) {\n <span\n class=\"kf-quote-picker__result-field kf-quote-picker__result-field--title\"\n >{{ cellValue(item, title) }}</span\n >\n }\n <!--\n Wrapper propio (no los spans sueltos) para los campos\n secundarios: en mobile necesita saltar de l\u00EDnea COMPLETO\n (mismo criterio que \"unidades\" no debe competir con\n \"modelo\" en la primera l\u00EDnea) \u2014 `flex-basis: 100%` en un\n solo elemento fuerza ese salto sin tener que asumir cu\u00E1l\n de los N campos gen\u00E9ricos es el m\u00E1s corto o el m\u00E1s largo.\n -->\n <span class=\"kf-quote-picker__result-fields\">\n @for (col of fieldColumns(); track col.key; let first = $first) {\n <span\n class=\"kf-quote-picker__result-field\"\n [class.kf-quote-picker__result-field--primary]=\"first\"\n >{{ cellValue(item, col) }}</span\n >\n }\n </span>\n </button>\n } @empty {\n <p class=\"kf-quote-picker__empty\">Sin resultados para esta b\u00FAsqueda.</p>\n }\n </div>\n @if (canFadeBottom()) {\n <div class=\"kf-quote-picker__fade\" aria-hidden=\"true\"></div>\n }\n </div>\n\n @if (isHighVolume()) {\n <button type=\"button\" class=\"kf-quote-picker__see-all\" (click)=\"onSeeAll()\">\n Ver las {{ sortedItems().length }} en pantalla completa\n </button>\n }\n </div>\n }\n </div>\n} @else {\n <kf-modal [(open)]=\"open\" [title]=\"title()\" [dismissible]=\"true\">\n <div class=\"kf-quote-picker__modal-toolbar\">\n <div class=\"kf-quote-picker__search\">\n <kf-icon name=\"search\" size=\"sm\" />\n <input\n type=\"text\"\n class=\"kf-quote-picker__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n />\n </div>\n <div class=\"kf-quote-picker__modal-filters\">\n <div class=\"kf-quote-picker__modal-chips\">\n @for (chip of chips(); track chip.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__chip\"\n [class.kf-quote-picker__chip--active]=\"activeChip() === chip.value\"\n (click)=\"selectChip(chip.value)\"\n >\n {{ chip.label }} ({{ chip.count }})\n </button>\n }\n </div>\n @if (sortOptions().length > 0) {\n <div class=\"kf-quote-picker__sort\">\n <button\n type=\"button\"\n class=\"kf-quote-picker__sort-trigger\"\n [attr.aria-expanded]=\"sortOpen()\"\n (click)=\"toggleSort()\"\n >\n <kf-icon name=\"sort\" size=\"sm\" />\n {{ sortLabel() }}\n <kf-icon name=\"chevron-down\" size=\"sm\" />\n </button>\n @if (sortOpen()) {\n <div class=\"kf-quote-picker__sort-panel\">\n @for (option of sortOptions(); track option.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__sort-option\"\n [class.kf-quote-picker__sort-option--active]=\"sort() === option.value\"\n (click)=\"setSort(option.value)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <kf-data-table\n [columns]=\"columns()\"\n [rows]=\"pagedItems()\"\n [cardable]=\"true\"\n [defaultView]=\"isMobile() ? 'cards' : 'table'\"\n [cellTemplates]=\"cellTemplates()\"\n [rowKey]=\"rowKeyFn\"\n (rowClick)=\"onTableRowClick($event)\"\n >\n <kf-empty-state empty icon=\"file\">Sin resultados para esta b\u00FAsqueda.</kf-empty-state>\n @if (sortedItems().length > 0) {\n <kf-pagination footer [page]=\"page()\" [pages]=\"pages()\" (pageChange)=\"page.set($event)\">\n {{ sortedItems().length }} resultados\n </kf-pagination>\n }\n </kf-data-table>\n </kf-modal>\n}\n", styles: [":host{display:block;font-family:var(--kf-font-body)}.kf-quote-picker__trigger{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-primary);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);cursor:pointer;font:inherit;text-align:left}.kf-quote-picker__trigger:hover{box-shadow:var(--kf-shadow-md)}.kf-quote-picker__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__thumb{display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);color:var(--kf-color-gray-400)}.kf-quote-picker__thumb--sm{width:36px;height:36px}.kf-quote-picker__result--active .kf-quote-picker__thumb{background:var(--kf-color-white);color:var(--kf-color-primary)}.kf-quote-picker__trigger-summary{flex:1;min-width:0;display:flex;align-items:baseline;gap:var(--kf-space-xs);overflow:hidden;white-space:nowrap;color:var(--kf-color-ink)}.kf-quote-picker__trigger-summary--empty{color:var(--kf-color-text-muted)}.kf-quote-picker__trigger-field--title{font-family:var(--kf-font-title);font-weight:700;font-size:1.05rem;color:var(--kf-color-ink)}.kf-quote-picker__trigger-field{font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__trigger-sep{color:var(--kf-color-border-strong)}.kf-quote-picker__trigger-action{flex:none;display:flex;align-items:center;gap:6px;color:var(--kf-color-primary);font-size:.85rem;font-weight:600}.kf-quote-picker__chevron{transition:transform .14s}.kf-quote-picker__chevron--open{transform:rotate(180deg)}.kf-quote-picker__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);margin-top:var(--kf-space-sm);padding:var(--kf-space-md) var(--kf-space-lg);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-quote-picker__toolbar{display:flex;flex-wrap:wrap;gap:var(--kf-space-sm)}.kf-quote-picker__search{flex:1;min-width:200px;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);color:var(--kf-color-text-muted)}.kf-quote-picker__search:focus-within{border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__search-input{flex:1;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-ink)}.kf-quote-picker__search-input::placeholder{color:var(--kf-color-text-muted)}.kf-quote-picker__chip{flex:none;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);font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}.kf-quote-picker__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}.kf-quote-picker__results-wrap{position:relative}.kf-quote-picker__results{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--kf-space-sm);max-height:340px;overflow-y:auto}@media(max-width:900px){.kf-quote-picker__results{grid-template-columns:1fr}}.kf-quote-picker__result{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);background:var(--kf-color-white);cursor:pointer;text-align:left;font:inherit;min-width:0;flex-wrap:wrap;row-gap:2px}.kf-quote-picker__result-fields{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;flex:1}.kf-quote-picker__result:hover{border-color:var(--kf-color-border)}.kf-quote-picker__result--active{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-quote-picker__result-field{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__result-field--title{flex:none;min-width:92px;font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__result-field--primary{flex:1;min-width:0}@media(max-width:900px){.kf-quote-picker__result-fields{flex-basis:100%;flex-wrap:wrap;row-gap:2px;padding-left:calc(36px + var(--kf-space-sm))}.kf-quote-picker__result-field--primary{flex:none;min-width:0;max-width:100%}}.kf-quote-picker__fade{position:absolute;left:0;right:0;bottom:0;height:36px;background:linear-gradient(to top,var(--kf-color-surface),transparent);pointer-events:none}.kf-quote-picker__see-all{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:var(--kf-space-sm);width:100%;margin-top:var(--kf-space-sm);padding:var(--kf-space-sm);border:1.5px dashed var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary);font:inherit;font-size:.85rem;font-weight:600;cursor:pointer}.kf-quote-picker__see-all:hover{background:var(--kf-color-primary-soft)}.kf-quote-picker__empty{grid-column:1 / -1;padding:var(--kf-space-md);text-align:center;color:var(--kf-color-text-muted);font-size:.85rem;margin:0}.kf-quote-picker__modal-toolbar{display:flex;flex-direction:column;gap:var(--kf-space-sm);margin-bottom:var(--kf-space-md)}.kf-quote-picker__modal-filters{display:flex;align-items:center;justify-content:space-between;gap:var(--kf-space-sm);flex-wrap:wrap}.kf-quote-picker__modal-chips{display:flex;gap:var(--kf-space-sm);flex-wrap:wrap}.kf-quote-picker__sort{position:relative;flex:none}.kf-quote-picker__sort-trigger{display:flex;align-items:center;gap:var(--kf-space-sm);padding:6px 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);font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}.kf-quote-picker__sort-trigger:hover{border-color:var(--kf-color-border-strong)}.kf-quote-picker__sort-panel{position:absolute;top:calc(100% + 4px);right:0;z-index:1;min-width:180px;padding:var(--kf-space-xs) 0;background:var(--kf-color-white);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:var(--kf-shadow-md)}.kf-quote-picker__sort-option{display:block;width:100%;padding:var(--kf-space-sm) var(--kf-space-md);border:none;background:transparent;color:var(--kf-color-text);font:inherit;font-size:.85rem;text-align:left;cursor:pointer}.kf-quote-picker__sort-option:hover{background:var(--kf-color-primary-soft)}.kf-quote-picker__sort-option--active{color:var(--kf-color-primary);font-weight:600}\n"] }]
363
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], selectedId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedId", required: false }] }], idKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "idKey", required: true }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], chips: [{ type: i0.Input, args: [{ isSignal: true, alias: "chips", required: false }] }], sortOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortOptions", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }, { type: i0.Output, args: ["sortChange"] }], expandThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandThreshold", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], placeholderIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderIcon", required: false }] }], selectedIdChange: [{ type: i0.Output, args: ["selectedIdChange"] }], expandToModal: [{ type: i0.Output, args: ["expandToModal"] }], previewScrollEl: [{ type: i0.ViewChild, args: ['previewScroll', { isSignal: true }] }] } });
587
+ args: [{ selector: 'kf-quote-picker', imports: [
588
+ NgTemplateOutlet,
589
+ FormsModule,
590
+ Button,
591
+ ContentState,
592
+ FilterChips,
593
+ Icon,
594
+ Input,
595
+ Modal,
596
+ DataTable,
597
+ EmptyState,
598
+ Pagination,
599
+ Select,
600
+ Skeleton,
601
+ Text,
602
+ ], template: "@if (variant() === 'inline') {\n <div class=\"kf-quote-picker__inline\">\n <!-- Trigger colapsado. `default`: tarjeta a ancho completo con miniatura\n (hist\u00F3rico). `compact`: control de barra de herramientas de alto fijo,\n sin miniatura. Con `inlinePanel` en false abre directo el modal. -->\n @if (triggerSize() === 'compact' && loading()) {\n <!-- Mismo alto que el trigger real: la barra no salta al llegar los datos. -->\n <div\n class=\"kf-quote-picker__trigger kf-quote-picker__trigger--compact kf-quote-picker__trigger--loading\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <span class=\"kf-quote-picker__sr-only\">{{ loadingLabel() }}</span>\n <span class=\"kf-quote-picker__compact\" aria-hidden=\"true\">\n <kf-skeleton width=\"35%\" height=\"12px\" />\n <kf-skeleton width=\"65%\" height=\"12px\" />\n </span>\n </div>\n } @else {\n <button\n type=\"button\"\n class=\"kf-quote-picker__trigger\"\n [class.kf-quote-picker__trigger--compact]=\"triggerSize() === 'compact'\"\n [attr.aria-expanded]=\"inlinePanel() ? expanded() : open()\"\n [attr.aria-haspopup]=\"inlinePanel() ? null : 'dialog'\"\n (click)=\"onTriggerClick()\"\n >\n @if (triggerSize() === 'compact') {\n <!-- Nombre accesible = texto visible con un prefijo y comas solo\n para lectores de pantalla: \"Cambiar cotizaci\u00F3n. Actual: <id>,\n <estado>, <modelo>\". -->\n <span class=\"kf-quote-picker__sr-only\">{{ title() }}. Actual: </span>\n @if (selectedItem(); as item) {\n <span class=\"kf-quote-picker__compact\">\n <span class=\"kf-quote-picker__compact-line\">\n @if (titleColumn(); as titleCol) {\n <span class=\"kf-quote-picker__compact-id\">{{ cellValue(item, titleCol) }}</span>\n }\n @for (col of statusColumns(); track col.key) {\n <span class=\"kf-quote-picker__sr-only\">, </span>\n <span class=\"kf-quote-picker__compact-status\">\n <ng-container\n *ngTemplateOutlet=\"cell; context: { $implicit: item, column: col, index: 0 }\"\n />\n </span>\n }\n </span>\n @if (primaryFieldColumn(); as primary) {\n <span class=\"kf-quote-picker__compact-line\">\n <span class=\"kf-quote-picker__sr-only\">, </span>\n <span class=\"kf-quote-picker__compact-primary\">{{\n cellValue(item, primary)\n }}</span>\n </span>\n }\n </span>\n } @else {\n <span class=\"kf-quote-picker__compact kf-quote-picker__trigger-summary--empty\"\n >Sin selecci\u00F3n</span\n >\n }\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"kf-quote-picker__chevron\"\n [class.kf-quote-picker__chevron--open]=\"inlinePanel() && expanded()\"\n />\n } @else {\n <span class=\"kf-quote-picker__thumb\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: selectedItem(), index: 0 }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"md\" />\n }\n </span>\n @if (selectedItem(); as item) {\n <span class=\"kf-quote-picker__trigger-summary\">\n @if (titleColumn(); as title) {\n <span\n class=\"kf-quote-picker__trigger-field kf-quote-picker__trigger-field--title\"\n >{{ cellValue(item, title) }}</span\n >\n }\n <!--\n En mobile solo se muestra el t\u00EDtulo (ID) \u2014 mostrar los 5 campos\n en una sola l\u00EDnea de ~90px de ancho real corta el texto sin\n elipsis a mitad del segundo campo (bug real medido:\n scrollWidth=519 vs. clientWidth=87 en 390px). El resto de los\n campos siguen disponibles en el panel expandido y en el modal,\n donde s\u00ED hay espacio horizontal.\n -->\n @if (!isMobile()) {\n @for (col of fieldColumns(); track col.key) {\n <span class=\"kf-quote-picker__trigger-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"kf-quote-picker__trigger-field\">{{ cellValue(item, col) }}</span>\n }\n }\n </span>\n } @else {\n <span class=\"kf-quote-picker__trigger-summary kf-quote-picker__trigger-summary--empty\"\n >Sin selecci\u00F3n</span\n >\n }\n <span class=\"kf-quote-picker__trigger-action\">\n {{ inlinePanel() && expanded() ? 'Cerrar' : title() }}\n <kf-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"kf-quote-picker__chevron\"\n [class.kf-quote-picker__chevron--open]=\"inlinePanel() && expanded()\"\n />\n </span>\n }\n </button>\n }\n\n @if (inlinePanel() && expanded()) {\n <div class=\"kf-quote-picker__panel\">\n <div class=\"kf-quote-picker__toolbar\">\n <div class=\"kf-quote-picker__search\">\n <kf-icon name=\"search\" size=\"sm\" />\n <input\n type=\"text\"\n class=\"kf-quote-picker__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"resolvedSearchAriaLabel()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n />\n </div>\n @for (chip of chips(); track chip.value) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__chip\"\n [class.kf-quote-picker__chip--active]=\"activeChip() === chip.value\"\n [attr.aria-pressed]=\"activeChip() === chip.value\"\n (click)=\"selectChip(chip.value)\"\n >\n {{ chip.label }} ({{ chip.count }})\n </button>\n }\n </div>\n\n <div class=\"kf-quote-picker__results-wrap\">\n <div #previewScroll class=\"kf-quote-picker__results\" role=\"listbox\">\n @for (item of previewItems(); track trackItem(item, $index)) {\n <button\n type=\"button\"\n class=\"kf-quote-picker__result\"\n [class.kf-quote-picker__result--active]=\"idKey()(item) === selectedId()\"\n role=\"option\"\n [attr.aria-selected]=\"idKey()(item) === selectedId()\"\n (click)=\"select(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <span class=\"kf-quote-picker__thumb kf-quote-picker__thumb--sm\">\n @if (mediaColumn(); as media) {\n @if (cellTemplates()?.[media.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, index: $index }\"\n />\n }\n } @else {\n <kf-icon [name]=\"placeholderIcon()\" size=\"sm\" />\n }\n </span>\n @if (titleColumn(); as title) {\n <span\n class=\"kf-quote-picker__result-field kf-quote-picker__result-field--title\"\n >{{ cellValue(item, title) }}</span\n >\n }\n <!--\n Wrapper propio (no los spans sueltos) para los campos\n secundarios: en mobile necesita saltar de l\u00EDnea COMPLETO\n (mismo criterio que \"unidades\" no debe competir con\n \"modelo\" en la primera l\u00EDnea) \u2014 `flex-basis: 100%` en un\n solo elemento fuerza ese salto sin tener que asumir cu\u00E1l\n de los N campos gen\u00E9ricos es el m\u00E1s corto o el m\u00E1s largo.\n -->\n <span class=\"kf-quote-picker__result-fields\">\n @for (col of fieldColumns(); track col.key; let first = $first) {\n <span\n class=\"kf-quote-picker__result-field\"\n [class.kf-quote-picker__result-field--primary]=\"first\"\n >{{ cellValue(item, col) }}</span\n >\n }\n </span>\n </button>\n } @empty {\n <p class=\"kf-quote-picker__empty\">Sin resultados para esta b\u00FAsqueda.</p>\n }\n </div>\n @if (canFadeBottom()) {\n <div class=\"kf-quote-picker__fade\" aria-hidden=\"true\"></div>\n }\n </div>\n\n @if (isHighVolume()) {\n <button type=\"button\" class=\"kf-quote-picker__see-all\" (click)=\"onSeeAll()\">\n Ver las {{ sortedItems().length }} en pantalla completa\n </button>\n }\n </div>\n }\n </div>\n}\n\n<!-- Modal: la variante `modal` y el que abre el trigger con `inlinePanel` en\n false. Zona de filtros FIJA (slot `kf-modal-toolbar`) y lista\n desplazable (cuerpo del modal). -->\n@if (variant() === 'modal' || !inlinePanel()) {\n <kf-modal\n class=\"kf-quote-picker__modal\"\n [(open)]=\"open\"\n [title]=\"title()\"\n [dismissible]=\"true\"\n [width]=\"modalWidth()\"\n maxHeight=\"85vh\"\n fullscreenOnMobile\n initialFocus=\".kf-quote-picker__modal-search\"\n >\n <p kf-modal-meta class=\"kf-quote-picker__count\" aria-live=\"polite\">{{ resultsLabel() }}</p>\n\n <div kf-modal-toolbar class=\"kf-quote-picker__filters\">\n <div class=\"kf-quote-picker__filters-row\">\n <kf-input\n class=\"kf-quote-picker__modal-search\"\n type=\"search\"\n icon=\"search\"\n clearable\n [placeholder]=\"searchPlaceholder()\"\n [ariaLabel]=\"resolvedSearchAriaLabel()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchChange($event)\"\n />\n @if (sortOptions().length > 0) {\n <kf-select\n class=\"kf-quote-picker__sort\"\n icon=\"sort\"\n ariaLabel=\"Ordenar por\"\n panelTitle=\"Ordenar por\"\n placeholder=\"Ordenar por\"\n [options]=\"sortSelectOptions()\"\n [value]=\"sort() ?? undefined\"\n (valueChange)=\"onSortChange($event)\"\n />\n }\n </div>\n @if (chips().length > 0) {\n <kf-filter-chips\n class=\"kf-quote-picker__chips\"\n deselectable\n [ariaLabel]=\"chipsAriaLabel()\"\n [options]=\"chipOptions()\"\n [value]=\"activeChip() ?? ''\"\n [layout]=\"isNarrowViewport() ? 'scroll' : 'wrap'\"\n (valueChange)=\"onChipChange($event)\"\n />\n }\n </div>\n\n <kf-content-state\n [state]=\"modalState()\"\n load=\"spinner\"\n [loadingLabel]=\"loadingLabel()\"\n [title]=\"error() ?? ''\"\n >\n <kf-button actions type=\"button\" variant=\"outline\" icon=\"refresh\" (click)=\"retry.emit()\">\n Reintentar\n </kf-button>\n @if (modalLayout() === 'list') {\n @if (sortedItems().length > 0) {\n <div class=\"kf-quote-picker__list-wrap\">\n <ul\n #modalList\n class=\"kf-quote-picker__list\"\n [style.grid-template-columns]=\"listGridColumns()\"\n >\n @if (listHeader()) {\n <!-- Cabecera visual de columnas: cada fila (bot\u00F3n) ya se anuncia\n completa, as\u00ED que para lectores de pantalla ser\u00EDa ruido. -->\n <li class=\"kf-quote-picker__list-head\" aria-hidden=\"true\">\n @if (titleColumn(); as titleCol) {\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--title\">\n <kf-text as=\"span\" variant=\"overline\">{{ titleCol.label }}</kf-text>\n </span>\n }\n @if (primaryFieldColumn(); as primary) {\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--primary\">\n <kf-text as=\"span\" variant=\"overline\">{{ primary.label }}</kf-text>\n </span>\n }\n @for (col of restFieldColumns(); track col.key) {\n <span\n class=\"kf-quote-picker__head-cell kf-quote-picker__cell--field\"\n [class.kf-quote-picker__cell--end]=\"isEndAligned(col)\"\n [class.kf-quote-picker__cell--center]=\"col.align === 'center'\"\n >\n <kf-text as=\"span\" variant=\"overline\">{{ col.label }}</kf-text>\n </span>\n }\n @for (col of statusColumns(); track col.key) {\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--status\">\n <kf-text as=\"span\" variant=\"overline\">{{ col.label }}</kf-text>\n </span>\n }\n <span class=\"kf-quote-picker__head-cell kf-quote-picker__cell--check\"></span>\n </li>\n }\n @for (item of sortedItems(); track trackItem(item, $index); let i = $index) {\n <li class=\"kf-quote-picker__list-item\">\n <button\n type=\"button\"\n class=\"kf-quote-picker__row\"\n [class.kf-quote-picker__row--current]=\"isSelected(item)\"\n [attr.aria-current]=\"isSelected(item) ? 'true' : null\"\n (click)=\"select(item)\"\n >\n <span class=\"kf-quote-picker__row-head\">\n @if (titleColumn(); as titleCol) {\n <span class=\"kf-quote-picker__row-title kf-quote-picker__cell--title\">\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: titleCol, index: i }\n \"\n />\n </span>\n }\n @for (col of statusColumns(); track col.key) {\n <span class=\"kf-quote-picker__row-status kf-quote-picker__cell--status\">\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: col, index: i }\n \"\n />\n </span>\n }\n </span>\n <span class=\"kf-quote-picker__row-body\">\n @if (primaryFieldColumn(); as primary) {\n <span\n class=\"kf-quote-picker__row-primary kf-quote-picker__cell--primary\"\n [attr.title]=\"cellTitle(item, primary)\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: primary, index: i }\n \"\n />\n </span>\n }\n @for (col of restFieldColumns(); track col.key) {\n <span\n class=\"kf-quote-picker__row-field kf-quote-picker__cell--field\"\n [class.kf-quote-picker__row-field--right]=\"isEndAligned(col)\"\n [class.kf-quote-picker__cell--end]=\"isEndAligned(col)\"\n [class.kf-quote-picker__cell--center]=\"col.align === 'center'\"\n [class.kf-quote-picker__cell--truncate]=\"col.truncate\"\n [attr.title]=\"col.truncate ? cellTitle(item, col) : null\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n cell;\n context: { $implicit: item, column: col, index: i }\n \"\n />\n </span>\n }\n </span>\n <span class=\"kf-quote-picker__row-check kf-quote-picker__cell--check\">\n @if (isSelected(item)) {\n <kf-icon name=\"check\" size=\"sm\" />\n }\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n } @else {\n <kf-empty-state icon=\"file\">Sin resultados para esta b\u00FAsqueda.</kf-empty-state>\n }\n } @else {\n <kf-data-table\n [columns]=\"columns()\"\n [rows]=\"pagedItems()\"\n [cardable]=\"true\"\n [defaultView]=\"isMobile() ? 'cards' : 'table'\"\n [cellTemplates]=\"cellTemplates()\"\n [rowKey]=\"rowKeyFn\"\n [currentRowKey]=\"selectedId()\"\n (rowClick)=\"onTableRowClick($event)\"\n >\n <kf-empty-state empty icon=\"file\">Sin resultados para esta b\u00FAsqueda.</kf-empty-state>\n @if (sortedItems().length > 0) {\n <kf-pagination footer [page]=\"page()\" [pages]=\"pages()\" (pageChange)=\"page.set($event)\">\n {{ sortedItems().length }} resultados\n </kf-pagination>\n }\n </kf-data-table>\n }\n </kf-content-state>\n </kf-modal>\n}\n\n<!-- Celda de la lista compacta: el template del consumidor si existe (badge,\n monto formateado), si no el valor como texto. -->\n<ng-template #cell let-item let-column=\"column\" let-index=\"index\">\n @if (cellTemplates()?.[column.key]; as template) {\n <ng-container\n [ngTemplateOutlet]=\"template\"\n [ngTemplateOutletContext]=\"{ $implicit: item, index: index }\"\n />\n } @else {\n {{ cellValue(item, column) }}\n }\n</ng-template>\n", styles: [":host{display:block;min-width:0;font-family:var(--kf-font-body)}.kf-quote-picker__sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}.kf-quote-picker__trigger{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-md);width:100%;padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-primary);border-radius:var(--kf-radius-md);box-shadow:var(--kf-shadow-sm);cursor:pointer;font:inherit;text-align:left}.kf-quote-picker__trigger:hover{box-shadow:var(--kf-shadow-md)}.kf-quote-picker__trigger:focus-visible{outline:none;box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__thumb{display:flex;align-items:center;justify-content:center;width:40px;height:40px;flex:none;border-radius:var(--kf-radius-sm);background:var(--kf-color-surface);color:var(--kf-color-gray-400)}.kf-quote-picker__thumb--sm{width:36px;height:36px}.kf-quote-picker__result--active .kf-quote-picker__thumb{background:var(--kf-color-white);color:var(--kf-color-primary)}.kf-quote-picker__trigger-summary{flex:1;min-width:0;display:flex;align-items:baseline;gap:var(--kf-space-xs);overflow:hidden;white-space:nowrap;color:var(--kf-color-ink)}.kf-quote-picker__trigger-summary--empty{color:var(--kf-color-text-muted)}.kf-quote-picker__trigger-field--title{font-family:var(--kf-font-title);font-weight:700;font-size:1.05rem;color:var(--kf-color-ink)}.kf-quote-picker__trigger-field{font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__trigger-sep{color:var(--kf-color-border-strong)}.kf-quote-picker__trigger-action{flex:none;display:flex;align-items:center;gap:6px;color:var(--kf-color-primary);font-size:.85rem;font-weight:600}.kf-quote-picker__chevron{transition:transform .14s}.kf-quote-picker__chevron--open{transform:rotate(180deg)}.kf-quote-picker__panel{display:flex;flex-direction:column;gap:var(--kf-space-md);margin-top:var(--kf-space-sm);padding:var(--kf-space-md) var(--kf-space-lg);background:var(--kf-color-surface);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-md)}.kf-quote-picker__toolbar{display:flex;flex-wrap:wrap;gap:var(--kf-space-sm)}.kf-quote-picker__search{flex:1;min-width:200px;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);background:var(--kf-color-white);border:1.5px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);color:var(--kf-color-text-muted)}.kf-quote-picker__search:focus-within{border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__search-input{flex:1;border:none;outline:none;background:transparent;font:inherit;color:var(--kf-color-ink)}.kf-quote-picker__search-input::placeholder{color:var(--kf-color-text-muted)}.kf-quote-picker__chip{flex:none;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);font:inherit;font-size:.8rem;font-weight:600;cursor:pointer}.kf-quote-picker__chip--active{background:var(--kf-color-primary);border-color:var(--kf-color-primary);color:var(--kf-color-white)}.kf-quote-picker__results-wrap{position:relative}.kf-quote-picker__results{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--kf-space-sm);max-height:340px;overflow-y:auto}@media(max-width:900px){.kf-quote-picker__results{grid-template-columns:1fr}}.kf-quote-picker__result{box-sizing:border-box;display:flex;align-items:center;gap:var(--kf-space-sm);padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);background:var(--kf-color-white);cursor:pointer;text-align:left;font:inherit;min-width:0;flex-wrap:wrap;row-gap:2px}.kf-quote-picker__result-fields{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0;flex:1}.kf-quote-picker__result:hover{border-color:var(--kf-color-border)}.kf-quote-picker__result--active{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-quote-picker__result-field{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.85rem;color:var(--kf-color-text)}.kf-quote-picker__result-field--title{flex:none;min-width:92px;font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__result-field--primary{flex:1;min-width:0}@media(max-width:900px){.kf-quote-picker__result-fields{flex-basis:100%;flex-wrap:wrap;row-gap:2px;padding-left:calc(36px + var(--kf-space-sm))}.kf-quote-picker__result-field--primary{flex:none;min-width:0;max-width:100%}}.kf-quote-picker__fade{position:absolute;left:0;right:0;bottom:0;height:36px;background:linear-gradient(to top,var(--kf-color-surface),transparent);pointer-events:none}.kf-quote-picker__see-all{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:var(--kf-space-sm);width:100%;margin-top:var(--kf-space-sm);padding:var(--kf-space-sm);border:1.5px dashed var(--kf-color-primary);border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-primary);font:inherit;font-size:.85rem;font-weight:600;cursor:pointer}.kf-quote-picker__see-all:hover{background:var(--kf-color-primary-soft)}.kf-quote-picker__empty{grid-column:1 / -1;padding:var(--kf-space-md);text-align:center;color:var(--kf-color-text-muted);font-size:.85rem;margin:0}.kf-quote-picker__trigger--compact{position:relative;height:var(--kf-quote-picker-trigger-height, 48px);min-width:0;gap:var(--kf-space-sm);padding:0 var(--kf-space-sm) 0 var(--kf-space-md);border:1px solid var(--kf-color-border);border-radius:var(--kf-radius-sm);box-shadow:none;transition:border-color .14s,box-shadow .14s}.kf-quote-picker__trigger--compact:hover{border-color:var(--kf-color-border-strong);box-shadow:none}.kf-quote-picker__trigger--compact:focus-visible{border-color:var(--kf-color-primary);box-shadow:var(--kf-shadow-focus)}.kf-quote-picker__compact{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:2px;line-height:1.25}.kf-quote-picker__compact-line{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0}.kf-quote-picker__compact-id{flex:none;font-family:var(--kf-font-mono);font-size:var(--kf-font-size-body-sm);font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__compact-status{display:flex;min-width:0;overflow:hidden}.kf-quote-picker__compact-primary{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--kf-font-size-body-sm);color:var(--kf-color-text)}.kf-quote-picker__trigger--loading{display:flex;align-items:center;cursor:progress}.kf-quote-picker__trigger--compact .kf-quote-picker__chevron{flex:none;color:var(--kf-color-text)}@media(prefers-reduced-motion:reduce){.kf-quote-picker__trigger--compact{transition:none}}.kf-quote-picker__count{margin:0;font-size:var(--kf-font-size-body-sm);font-variant-numeric:tabular-nums;color:var(--kf-color-text)}.kf-quote-picker__count:empty{display:none}.kf-quote-picker__filters{container:kf-quote-picker-filters / inline-size;display:flex;flex-direction:column;gap:var(--kf-space-sm)}.kf-quote-picker__filters-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--kf-space-sm)}.kf-quote-picker__modal-search{flex:1 1 240px;min-width:240px}.kf-quote-picker__sort{flex:none}@container kf-quote-picker-filters (max-width: 520px){.kf-quote-picker__modal-search{flex-basis:100%;min-width:0}.kf-quote-picker__sort{width:100%}}.kf-quote-picker__list-wrap{container:kf-quote-picker-list / inline-size}.kf-quote-picker__list{display:grid;column-gap:var(--kf-space-md);row-gap:2px;margin:0;padding:0 0 2px;list-style:none}.kf-quote-picker__list-head,.kf-quote-picker__list-item{display:grid;grid-column:1 / -1;grid-template-columns:subgrid}.kf-quote-picker__list-head{position:sticky;top:0;z-index:1;align-items:end;padding:var(--kf-space-xs) var(--kf-space-md) var(--kf-space-sm);border-bottom:1px solid var(--kf-color-border);background:var(--kf-color-white)}.kf-quote-picker__row{grid-column:1 / -1;display:grid;grid-template-columns:subgrid;align-items:center;box-sizing:border-box;width:100%;min-height:52px;padding:var(--kf-space-sm) var(--kf-space-md);border:1px solid transparent;border-radius:var(--kf-radius-sm);background:var(--kf-color-white);color:var(--kf-color-text);font:inherit;font-size:var(--kf-font-size-body-sm);text-align:start;cursor:pointer;scroll-margin-top:48px;scroll-margin-bottom:var(--kf-space-sm)}.kf-quote-picker__row:hover{border-color:var(--kf-color-border);background:var(--kf-color-surface)}.kf-quote-picker__row:focus-visible{outline:2px solid var(--kf-color-primary);outline-offset:-2px}.kf-quote-picker__row--current,.kf-quote-picker__row--current:hover{border-color:var(--kf-color-primary);background:var(--kf-color-primary-soft)}.kf-quote-picker__row-head,.kf-quote-picker__row-body{display:contents}.kf-quote-picker__head-cell{min-width:0;white-space:nowrap}.kf-quote-picker__cell--title{order:0;white-space:nowrap}.kf-quote-picker__row-title{font-family:var(--kf-font-mono);font-weight:700;color:var(--kf-color-ink)}.kf-quote-picker__cell--primary{order:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.kf-quote-picker__row-primary{color:var(--kf-color-ink)}.kf-quote-picker__cell--field{order:2;min-width:0;white-space:nowrap;font-variant-numeric:tabular-nums}.kf-quote-picker__cell--truncate{overflow:hidden;text-overflow:ellipsis}.kf-quote-picker__cell--end{justify-self:end;text-align:end}.kf-quote-picker__cell--center{justify-self:center;text-align:center}.kf-quote-picker__row-field--right{font-weight:600;color:var(--kf-color-ink)}.kf-quote-picker__cell--status{order:3;display:flex;min-width:0;overflow:hidden}.kf-quote-picker__cell--check{order:4;display:flex;justify-content:center;color:var(--kf-color-primary)}@container kf-quote-picker-list (max-width: 560px){.kf-quote-picker__list{display:flex;flex-direction:column}.kf-quote-picker__list-head{display:none}.kf-quote-picker__list-item{display:block}.kf-quote-picker__row{grid-template-columns:minmax(0,1fr) 1.25rem;grid-template-areas:\"head check\" \"body check\";column-gap:var(--kf-space-sm);row-gap:var(--kf-space-xs);padding:var(--kf-space-sm);scroll-margin-top:var(--kf-space-sm)}.kf-quote-picker__row-head,.kf-quote-picker__row-body{display:flex;align-items:center;gap:var(--kf-space-sm);min-width:0}.kf-quote-picker__row-head{grid-area:head;justify-content:space-between}.kf-quote-picker__row-body{grid-area:body;flex-wrap:wrap;row-gap:var(--kf-space-xs)}.kf-quote-picker__row-body .kf-quote-picker__cell--end{margin-left:auto}.kf-quote-picker__row-check{grid-area:check}.kf-quote-picker__cell--status{flex:0 1 auto;justify-content:flex-end;max-width:65%}.kf-quote-picker__cell--primary{flex:1 1 100%;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;white-space:normal;overflow-wrap:anywhere}.kf-quote-picker__cell--field{flex:none}}\n"] }]
603
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], selectedId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedId", required: false }] }], idKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "idKey", required: true }] }], cellTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellTemplates", required: false }] }], chips: [{ type: i0.Input, args: [{ isSignal: true, alias: "chips", required: false }] }], sortOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortOptions", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }, { type: i0.Output, args: ["sortChange"] }], expandThreshold: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandThreshold", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], placeholderIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderIcon", required: false }] }], modalLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalLayout", required: false }] }], searchAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchAriaLabel", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], triggerSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerSize", required: false }] }], inlinePanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlinePanel", required: false }] }], modalWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalWidth", required: false }] }], itemLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemLabels", required: false }] }], allChipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "allChipLabel", required: false }] }], chipsAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipsAriaLabel", required: false }] }], listHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "listHeader", required: false }] }], selectedIdChange: [{ type: i0.Output, args: ["selectedIdChange"] }], retry: [{ type: i0.Output, args: ["retry"] }], expandToModal: [{ type: i0.Output, args: ["expandToModal"] }], previewScrollEl: [{ type: i0.ViewChild, args: ['previewScroll', { isSignal: true }] }], modalListEl: [{ type: i0.ViewChild, args: ['modalList', { isSignal: true }] }] } });
364
604
 
365
605
  /**
366
606
  * Estado vacío de reservas en taller con llamada a agendar (página 36):