@kaufmann-lab/ui 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/kaufmann-lab-ui-business.mjs +269 -17
- package/fesm2022/kaufmann-lab-ui-business.mjs.map +1 -1
- package/fesm2022/kaufmann-lab-ui-core.mjs +1418 -101
- package/fesm2022/kaufmann-lab-ui-core.mjs.map +1 -1
- package/fesm2022/kaufmann-lab-ui-map.mjs +1 -1
- package/fesm2022/kaufmann-lab-ui-map.mjs.map +1 -1
- package/package.json +1 -1
- package/types/kaufmann-lab-ui-business.d.ts +175 -14
- package/types/kaufmann-lab-ui-core.d.ts +871 -55
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, output, Component, model, signal, inject, DestroyRef, viewChild, computed,
|
|
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
|
/**
|
|
@@ -21,6 +23,18 @@ import { matchMediaSignal, normalizeForSearch } from '@kaufmann-lab/ui/shared';
|
|
|
21
23
|
* { label: 'Sucursal', value: 'Sucursal Norte' },
|
|
22
24
|
* ]"
|
|
23
25
|
* />
|
|
26
|
+
*
|
|
27
|
+
* Con opciones en forma de objeto, `value`/`valueChange` usan el `value` de
|
|
28
|
+
* la opción, no su nombre:
|
|
29
|
+
*
|
|
30
|
+
* <kf-company-selector
|
|
31
|
+
* [companies]="[
|
|
32
|
+
* { value: '1002099', label: 'DIANA CAROLINA PINZÓN MALDONADO', tag: 'Personal', detail: 'DNI 46594489' },
|
|
33
|
+
* { value: '1004410', label: 'TRANSPORTES VIRGEN DE LA AGRICULTURA', tag: 'Empresa', detail: 'RUC 20434860808' },
|
|
34
|
+
* ]"
|
|
35
|
+
* [value]="activeAccount()"
|
|
36
|
+
* (valueChange)="activeAccount.set($event)"
|
|
37
|
+
* />
|
|
24
38
|
*/
|
|
25
39
|
class CompanySelector {
|
|
26
40
|
label = input('Selecciona empresa', /* @ts-ignore */
|
|
@@ -33,7 +47,7 @@ class CompanySelector {
|
|
|
33
47
|
...(ngDevMode ? [{ debugName: "meta" }] : /* istanbul ignore next */ []));
|
|
34
48
|
valueChange = output();
|
|
35
49
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CompanySelector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
36
|
-
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"] }] });
|
|
37
51
|
}
|
|
38
52
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CompanySelector, decorators: [{
|
|
39
53
|
type: Component,
|
|
@@ -83,10 +97,8 @@ const PAGE_SIZE = 10;
|
|
|
83
97
|
* este modo necesita. Compone también `kf-data-table` para la tabla
|
|
84
98
|
* completa (columnas configurables, fade de overflow horizontal ya
|
|
85
99
|
* resuelto ahí) y `kf-pagination` para paginar — ninguno de los tres se
|
|
86
|
-
* reimplementa
|
|
87
|
-
*
|
|
88
|
-
* fuerza un ancho fijo de 240px pensado para formularios, no para una
|
|
89
|
-
* 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`).
|
|
90
102
|
*
|
|
91
103
|
* En mobile, `kf-data-table[cardable]` con `defaultView` forzado a
|
|
92
104
|
* `'cards'` decide la vista (mismo criterio que `matchMediaSignal` ya
|
|
@@ -94,12 +106,70 @@ const PAGE_SIZE = 10;
|
|
|
94
106
|
* no tiene sentido ofrecer un toggle tabla/cards que en mobile daría una
|
|
95
107
|
* tabla angosta con scroll horizontal.
|
|
96
108
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
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.
|
|
103
173
|
*
|
|
104
174
|
* <kf-quote-picker
|
|
105
175
|
* [items]="quotes()"
|
|
@@ -158,7 +228,47 @@ class QuotePicker {
|
|
|
158
228
|
* ícono fijo. Default `'file'` solo por no dejar el input sin valor. */
|
|
159
229
|
placeholderIcon = input('file', /* @ts-ignore */
|
|
160
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 });
|
|
161
269
|
selectedIdChange = output();
|
|
270
|
+
/** "Reintentar" del estado de error. */
|
|
271
|
+
retry = output();
|
|
162
272
|
/** Disparado al superar `expandThreshold` en modo inline; el consumidor decide pasar a `modal`. */
|
|
163
273
|
expandToModal = output();
|
|
164
274
|
searchQuery = signal('', /* @ts-ignore */
|
|
@@ -178,9 +288,15 @@ class QuotePicker {
|
|
|
178
288
|
* vs. cards apiladas dentro del modal.
|
|
179
289
|
*/
|
|
180
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)');
|
|
181
293
|
destroyRef = inject(DestroyRef);
|
|
182
294
|
previewScrollEl = viewChild('previewScroll', /* @ts-ignore */
|
|
183
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;
|
|
184
300
|
/** Mismo mecanismo de fade que `kf-data-table` — ver comentario en QuotePicker.css. */
|
|
185
301
|
canFadeBottom = signal(false, /* @ts-ignore */
|
|
186
302
|
...(ngDevMode ? [{ debugName: "canFadeBottom" }] : /* istanbul ignore next */ []));
|
|
@@ -250,12 +366,107 @@ class QuotePicker {
|
|
|
250
366
|
return this.sortedItems().slice(start, start + PAGE_SIZE);
|
|
251
367
|
}, /* @ts-ignore */
|
|
252
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
|
+
}
|
|
253
448
|
sortLabel = computed(() => {
|
|
254
449
|
const current = this.sortOptions().find((option) => option.value === this.sort());
|
|
255
450
|
return current?.label ?? 'Ordenar por';
|
|
256
451
|
}, /* @ts-ignore */
|
|
257
452
|
...(ngDevMode ? [{ debugName: "sortLabel" }] : /* istanbul ignore next */ []));
|
|
258
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
|
+
});
|
|
259
470
|
afterNextRender(() => {
|
|
260
471
|
const el = this.previewScrollEl()?.nativeElement;
|
|
261
472
|
if (!el) {
|
|
@@ -284,14 +495,32 @@ class QuotePicker {
|
|
|
284
495
|
toggleExpandedPanel() {
|
|
285
496
|
this.expanded.set(!this.expanded());
|
|
286
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
|
+
}
|
|
287
507
|
onSearchInput(event) {
|
|
288
|
-
this.
|
|
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 ?? '');
|
|
289
513
|
this.page.set(1);
|
|
290
514
|
}
|
|
291
515
|
selectChip(value) {
|
|
292
516
|
this.activeChip.set(this.activeChip() === value ? null : value);
|
|
293
517
|
this.page.set(1);
|
|
294
518
|
}
|
|
519
|
+
/** Chips del modal: `''` es "Todas" (sin filtro). */
|
|
520
|
+
onChipChange(value) {
|
|
521
|
+
this.activeChip.set(value || null);
|
|
522
|
+
this.page.set(1);
|
|
523
|
+
}
|
|
295
524
|
select(item) {
|
|
296
525
|
const id = this.idKey()(item);
|
|
297
526
|
// El panel se cierra siempre al elegir (haya cambiado el id o no) —
|
|
@@ -300,6 +529,9 @@ class QuotePicker {
|
|
|
300
529
|
// consumidor sería una carga que ningún otro componente de la
|
|
301
530
|
// librería le pide.
|
|
302
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);
|
|
303
535
|
if (id === this.selectedId()) {
|
|
304
536
|
return;
|
|
305
537
|
}
|
|
@@ -317,9 +549,14 @@ class QuotePicker {
|
|
|
317
549
|
onTableRowClick(event) {
|
|
318
550
|
this.select(event.row);
|
|
319
551
|
}
|
|
552
|
+
/** @deprecated El orden de la variante modal ahora es un `kf-select`; este método ya no lo usa la plantilla. */
|
|
320
553
|
toggleSort() {
|
|
321
554
|
this.sortOpen.update((open) => !open);
|
|
322
555
|
}
|
|
556
|
+
onSortChange(value) {
|
|
557
|
+
this.sort.set(value);
|
|
558
|
+
}
|
|
559
|
+
/** Alterna el orden (elegir el vigente lo quita). Se conserva por compatibilidad. */
|
|
323
560
|
setSort(value) {
|
|
324
561
|
this.sort.set(this.sort() === value ? null : value);
|
|
325
562
|
this.sortOpen.set(false);
|
|
@@ -343,12 +580,27 @@ class QuotePicker {
|
|
|
343
580
|
fieldColumns = computed(() => this.columns().filter((c) => !c.cardRole || c.cardRole === 'field'), /* @ts-ignore */
|
|
344
581
|
...(ngDevMode ? [{ debugName: "fieldColumns" }] : /* istanbul ignore next */ []));
|
|
345
582
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: QuotePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
346
|
-
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"] }, { 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", "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", "rowKey", "flush", "cardable", "defaultView", "showViewToggle", "cardMinWidth", "render", "cellTemplates", "density", "rowClickable", "ariaLabel", "currentRowKey", "pageSize", "pageSizeOptions", "page", "keepPageOnRowsChange", "itemLabel", "sort", "view"], outputs: ["rowClick", "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"] }] });
|
|
347
584
|
}
|
|
348
585
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: QuotePicker, decorators: [{
|
|
349
586
|
type: Component,
|
|
350
|
-
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"] }]
|
|
351
|
-
|
|
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 }] }] } });
|
|
352
604
|
|
|
353
605
|
/**
|
|
354
606
|
* Estado vacío de reservas en taller con llamada a agendar (página 36):
|