@kaufmann-lab/ui 0.1.16 → 0.1.18

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.
@@ -0,0 +1,631 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, Provider } from '@angular/core';
3
+ import { IconName } from '@kaufmann-lab/ui/core';
4
+
5
+ /**
6
+ * Motor de mapas configurado a nivel app, no por uso — mismo criterio que
7
+ * `API_CONFIG` en `packages/api-client` de `route-assistance-monorepo`: el
8
+ * access token de Mapbox es un dato de despliegue (cada app/entorno tiene
9
+ * el propio), no algo que cada `<kf-map>` deba repetir.
10
+ */
11
+ interface MapConfig {
12
+ /** Access token público de Mapbox (`pk.*`) de la cuenta del proyecto que consume la librería. */
13
+ accessToken: string;
14
+ /**
15
+ * Estilo de Mapbox Studio propio del cliente, si lo tiene. Cuando se
16
+ * define, gana siempre sobre `theme()` del componente — un estilo de
17
+ * marca custom no tiene una variante "light"/"dark" que elegir, ya viene
18
+ * resuelto. Sin esto, `kf-map` usa el estilo `light-v11`/`dark-v11` de
19
+ * Mapbox según `theme()`.
20
+ */
21
+ styleUrl?: string;
22
+ }
23
+ declare const MAP_CONFIG: InjectionToken<MapConfig>;
24
+ /**
25
+ * `provide*` idiomático (mismo patrón que `provideHttpClient`/`provideRouter`)
26
+ * para inyectar una sola vez en `app.config.ts` de cada app consumidora:
27
+ *
28
+ * providers: [provideMapConfig({ accessToken: 'pk.xxx' })]
29
+ */
30
+ declare function provideMapConfig(config: MapConfig): Provider;
31
+
32
+ /**
33
+ * Vocabulario de temas de `kf-map`, separado de `MapView.ts` para que
34
+ * `MapThemePicker` (el panel flotante del control `mapStyle`) pueda
35
+ * importarlo sin crear un ciclo `MapView → MapControls → MapThemePicker →
36
+ * MapView`.
37
+ *
38
+ * `streets`/`satellite`/`outdoors` se suman a los `light`/`dark`
39
+ * originales — los 3 IDs de estilo (`streets-v12`, `satellite-streets-v12`,
40
+ * `outdoors-v12`) están confirmados contra la cuenta/token real del
41
+ * ecosistema: la API de estilos de Mapbox es por cuenta, no por versión de
42
+ * `mapbox-gl`, así que cualquier duda se resuelve probando contra el token
43
+ * real, no leyendo el número de versión instalado.
44
+ */
45
+ type MapTheme = 'light' | 'dark' | 'streets' | 'satellite' | 'outdoors';
46
+ declare const STYLE_BY_THEME: Record<MapTheme, string>;
47
+ /** Orden fijo en que `MapThemePicker` lista las 5 opciones — predecible
48
+ * (siempre el mismo orden en toda pantalla que use el picker), no importa
49
+ * cuál esté activa. */
50
+ declare const THEME_ORDER: MapTheme[];
51
+ /** Nombre en español de cada tema, para el `aria-label`/label del control
52
+ * `mapStyle` y las opciones del picker — anuncia qué tema rige HOY o qué
53
+ * tema se está por elegir, no solo "otro tema distinto". */
54
+ declare const THEME_LABEL: Record<MapTheme, string>;
55
+
56
+ /**
57
+ * Cada pieza es independiente entre sí — agregar/sacar un `MapControlKind`
58
+ * de `controls` en `kf-map` no reacomoda ni afecta a los demás. Array vacío
59
+ * (default de `kf-map`) es "ningún control": opt-in explícito del
60
+ * consumidor, nunca activado por default (ver JSDoc de `controls` en
61
+ * `MapView.ts`).
62
+ *
63
+ * zoom — botones +/- reales (`map.zoomIn()`/`zoomOut()`).
64
+ * fullscreen — expandir/contraer el mapa (Fullscreen API nativa).
65
+ * mapStyle — ciclar entre los temas de `MapTheme`.
66
+ * recenter — re-ejecuta el encuadre a markers/route bajo demanda.
67
+ * cluster — togglear agrupamiento nativo de markers cercanos.
68
+ * follow — la cámara sigue al marker `live` mientras se mueve.
69
+ */
70
+ type MapControlKind = 'zoom' | 'fullscreen' | 'mapStyle' | 'recenter' | 'cluster' | 'follow';
71
+ /**
72
+ * Barra de controles flotantes de `kf-map` — pieza puramente presentacional,
73
+ * sin estado propio: todo lo que sabe se lo pasa `MapView` por input, todo
74
+ * lo que hace lo reporta por output para que `MapView` (única dueña del
75
+ * `Map` de Mapbox) decida qué hacer. Vive en la misma carpeta que `MapView`
76
+ * a propósito (mismo criterio que `DropdownItem`/`DropdownPanel` con
77
+ * `Dropdown`): el acoplamiento es real, no de nombre — no tiene sentido
78
+ * fuera de un `kf-map` que la controle.
79
+ *
80
+ * Un solo stack vertical, siempre en la esquina superior derecha del mapa
81
+ * (fijado por `MapView.css`, no acá): es donde ya vivía el `NavigationControl`
82
+ * de Mapbox que este componente reemplaza, y la atribución obligatoria de
83
+ * Mapbox (`AttributionControl`, nunca oculta) vive abajo-derecha — un solo
84
+ * stack evita que dos grupos floten en esquinas distintas y compitan por
85
+ * espacio en un mapa `size="sm"` de 200px de alto.
86
+ *
87
+ * El stack no es una lista continua de botones idénticos: cada grupo lógico
88
+ * es su propia "card" flotante (`kf-radius-md`+`kf-shadow-md`+fondo blanco,
89
+ * ver `.kf-map-controls__group` en `MapControls.css`), separadas entre sí
90
+ * por el `gap` del `:host` — mismo lenguaje ya verificado visualmente en
91
+ * `route-assistance-monorepo/design/mockups/operations-final.html`
92
+ * (`.map__ctrl-group`). Orden fijo de arriba a abajo, independiente del
93
+ * orden en el array `controls` del consumidor (para que dos pantallas que
94
+ * piden los mismos controles en distinto orden se vean IGUAL):
95
+ *
96
+ * 1. zoom — magnificación (par +/-, unido por un divisor, sin gap entre sí).
97
+ * 2. cámara — recenter + follow (posición de la cámara).
98
+ * 3. datos — cluster (densidad de los markers, eje distinto de cámara).
99
+ * 4. chrome — mapStyle + fullscreen (nada del mapa en sí).
100
+ *
101
+ * Cada grupo usa `@if` sobre AL MENOS UNO de sus controles (ej. el grupo de
102
+ * cámara aparece con solo `recenter`, solo `follow`, o ambos) — agregar o
103
+ * sacar un control nunca reacomoda ni depende de los demás grupos.
104
+ *
105
+ * Todos los botones siguen siendo `kf-button[variant=surface][shape=circle]`
106
+ * — sin tocar `Button.ts`: el fondo/sombra propios de `surface` se anulan
107
+ * SOLO dentro de un `.kf-map-controls__group` vía una custom property CSS
108
+ * (ver el comentario junto a esa clase en `MapControls.css` sobre por qué
109
+ * eso sí atraviesa la encapsulación de Angular y un selector normal no).
110
+ */
111
+ declare class MapControls {
112
+ readonly controls: _angular_core.InputSignal<MapControlKind[]>;
113
+ readonly clusterActive: _angular_core.InputSignal<boolean>;
114
+ readonly followActive: _angular_core.InputSignal<boolean>;
115
+ /** Sin ningún marker `live`, togglear "seguir" no tendría ningún efecto
116
+ * visible — se deshabilita en vez de dejar un botón que parece no hacer nada. */
117
+ readonly canFollow: _angular_core.InputSignal<boolean>;
118
+ readonly isFullscreen: _angular_core.InputSignal<boolean>;
119
+ /** Nombre en español del tema actualmente resuelto (ej. "Calles"), para
120
+ * anunciar en el `aria-label` del botón de estilo qué se está por perder,
121
+ * no solo que "algo" va a cambiar. */
122
+ readonly styleLabel: _angular_core.InputSignal<string>;
123
+ /** Tema resuelto HOY (`light|dark|...`) — a diferencia de `styleLabel`
124
+ * (ya traducido a texto), esto es lo que `MapThemePicker` necesita para
125
+ * marcar la opción activa. */
126
+ readonly activeTheme: _angular_core.InputSignal<MapTheme>;
127
+ readonly zoomIn: _angular_core.OutputEmitterRef<void>;
128
+ readonly zoomOut: _angular_core.OutputEmitterRef<void>;
129
+ readonly recenter: _angular_core.OutputEmitterRef<void>;
130
+ readonly followToggle: _angular_core.OutputEmitterRef<void>;
131
+ readonly clusterToggle: _angular_core.OutputEmitterRef<void>;
132
+ /** Reemplaza al viejo `styleCycle` (ciclado de un click): ahora el botón
133
+ * `mapStyle` abre `MapThemePicker` y este output reporta la elección real
134
+ * hecha ahí. */
135
+ readonly themeSelect: _angular_core.OutputEmitterRef<MapTheme>;
136
+ readonly fullscreenToggle: _angular_core.OutputEmitterRef<void>;
137
+ /** Visibilidad del panel de temas — estado puramente de UI (abierto o no),
138
+ * no de negocio: `MapView` no necesita saberlo, así que vive acá y no
139
+ * rompe el criterio de "sin estado propio" del resto de la clase (ver
140
+ * JSDoc). Se cierra solo (click afuera/Escape) vía `kf-dropdown`, y acá
141
+ * además al elegir un tema. */
142
+ protected readonly mapStyleOpen: _angular_core.WritableSignal<boolean>;
143
+ protected onThemeSelect(theme: MapTheme): void;
144
+ protected readonly hasZoom: _angular_core.Signal<boolean>;
145
+ protected readonly hasRecenter: _angular_core.Signal<boolean>;
146
+ protected readonly hasFollow: _angular_core.Signal<boolean>;
147
+ protected readonly hasCluster: _angular_core.Signal<boolean>;
148
+ protected readonly hasMapStyle: _angular_core.Signal<boolean>;
149
+ protected readonly hasFullscreen: _angular_core.Signal<boolean>;
150
+ /** El grupo "cámara" (card) aparece con solo `recenter`, solo `follow`, o
151
+ * ambos — ver JSDoc de la clase sobre por qué cada `@if` de grupo cubre
152
+ * "al menos uno de sus controles" en vez de un control puntual. */
153
+ protected readonly hasCamera: _angular_core.Signal<boolean>;
154
+ protected readonly hasChrome: _angular_core.Signal<boolean>;
155
+ /**
156
+ * Texto corto que revela cada botón en hover/foco vía `[kfTooltip]` (ver
157
+ * `MapControls.html` — la librería ya resuelve este patrón con
158
+ * `TooltipDirective`, no con CSS a mano) — la misma acción que ya describe
159
+ * `ariaLabel`, condensada para caber en una píldora en vez de leerse como
160
+ * oración completa. No reemplaza `ariaLabel` (que sigue siendo la fuente
161
+ * real para lectores de pantalla): esto es SOLO visual, para quien puede
162
+ * ver el mapa pero no sabe qué hace un ícono suelto.
163
+ */
164
+ protected readonly followLabel: _angular_core.Signal<"Dejar de seguir" | "Seguir">;
165
+ protected readonly clusterLabel: _angular_core.Signal<"Desagrupar" | "Agrupar">;
166
+ protected readonly mapStyleLabel: _angular_core.Signal<string>;
167
+ protected readonly fullscreenLabel: _angular_core.Signal<"Salir de pantalla completa" | "Pantalla completa">;
168
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MapControls, never>;
169
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MapControls, "kf-map-controls", never, { "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "clusterActive": { "alias": "clusterActive"; "required": false; "isSignal": true; }; "followActive": { "alias": "followActive"; "required": false; "isSignal": true; }; "canFollow": { "alias": "canFollow"; "required": false; "isSignal": true; }; "isFullscreen": { "alias": "isFullscreen"; "required": false; "isSignal": true; }; "styleLabel": { "alias": "styleLabel"; "required": false; "isSignal": true; }; "activeTheme": { "alias": "activeTheme"; "required": false; "isSignal": true; }; }, { "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "recenter": "recenter"; "followToggle": "followToggle"; "clusterToggle": "clusterToggle"; "themeSelect": "themeSelect"; "fullscreenToggle": "fullscreenToggle"; }, never, never, true, never>;
170
+ }
171
+
172
+ type MapSize = 'sm' | 'md' | 'lg' | 'full';
173
+ type MapVariant = 'interactive' | 'preview';
174
+ /** Vocabulario semántico único de tono, compartido con `TextTone`/`StatTileTone`. */
175
+ type MapMarkerTone = 'neutral' | 'ok' | 'warn' | 'error' | 'info';
176
+ interface MapLatLng {
177
+ lat: number;
178
+ lng: number;
179
+ }
180
+ /**
181
+ * Botón de acción real dentro de `MapMarker.popup` — cada click emite
182
+ * `markerAction` con su `key` (nunca el `label`, ver JSDoc de `popup` más
183
+ * abajo). `icon` es OPCIONAL a propósito: no romper a los consumidores que
184
+ * ya usaban `actions` sin él (era el único campo hasta esta ronda).
185
+ *
186
+ * La jerarquía visual entre acciones NO es un campo aparte (`variant`) sino
187
+ * la POSICIÓN en el array: la primera acción se renderiza con estilo
188
+ * prominente (fill, mismo peso que `kf-button[variant=primary]`, el
189
+ * default del botón); el resto con estilo discreto (mismo peso que
190
+ * `kf-button[variant=ghost]` — ver el ejemplo de `Modal` con
191
+ * `variant="ghost"` para "Cancelar" junto a un primary implícito). Un
192
+ * consumidor con una sola acción no necesita pensar en esto: esa acción ya
193
+ * es "la primera".
194
+ */
195
+ interface MapPopupAction {
196
+ key: string;
197
+ label: string;
198
+ /** Ícono líder del catálogo de `kf-icon` — sin esto, la acción se ve solo
199
+ * con texto (compatibilidad con `actions` ya declarado sin `icon`). */
200
+ icon?: IconName;
201
+ }
202
+ interface MapMarker {
203
+ position: MapLatLng;
204
+ /** Sin `icon`, cae en `map-pin` — el pin genérico del catálogo. Se
205
+ * ignora cuando el control `cluster` está activo (ver JSDoc de
206
+ * `controls` — el clustering nativo resuelve por capas WebGL, no por
207
+ * DOM, y ahí no hay dónde montar un `kf-icon`). */
208
+ icon?: IconName;
209
+ tone?: MapMarkerTone;
210
+ /** Nombre accesible del marker (`aria-label`/`title`) — no abre un popup. */
211
+ label?: string;
212
+ /**
213
+ * Identificador propio del consumidor (ej. el id del ticket/vehículo que
214
+ * representa) — sin esto el marker no es clickeable: `kf-map` no tiene
215
+ * forma de reportar "se hizo click en ESTE marker" sin algo que
216
+ * identificarlo de vuelta hacia `markerSelect`. Opcional a propósito: un
217
+ * mapa puramente decorativo (ej. la vista previa de `variant="preview"`)
218
+ * no necesita que sus markers respondan a nada.
219
+ */
220
+ id?: string;
221
+ /**
222
+ * Marca este marker como el punto que sigue la cámara cuando el control
223
+ * `follow` está activo — ej. la posición en tiempo real de un vehículo o
224
+ * técnico. Genérico a propósito (no `isVehicle` ni ningún nombre atado al
225
+ * dominio de un portal): `@kaufmann-lab/ui` no sabe qué es un "vehículo",
226
+ * solo que ALGÚN marker es el que se sigue. Si más de un marker lo marca,
227
+ * se sigue el primero en `markers()`. Distinto de la geolocalización del
228
+ * propio dispositivo (`navigator.geolocation`, fuera de alcance): esto
229
+ * sigue un dato que el consumidor ya tiene, no ubica al usuario.
230
+ */
231
+ live?: boolean;
232
+ /**
233
+ * Mini-card que aparece al hover/foco de este marker — solo si el marker
234
+ * tiene `id` (ver JSDoc de arriba): sin `id` el marker no es clickeable ni
235
+ * enfocable, así que no habría forma de revelarla por teclado.
236
+ *
237
+ * `actions` son botones REALES (no el viejo `ctaLabel` de texto plano sin
238
+ * acción): cada click emite `markerAction` con su `key` — nunca el
239
+ * `label`, que es solo texto de UI y puede repetirse con significados
240
+ * distintos entre consumidores — y detiene la propagación del click para
241
+ * no disparar además `markerSelect` sobre el marker completo (ver
242
+ * `buildMarkerPopup()`).
243
+ *
244
+ * Vocabulario genérico a propósito (`title`/`description`/`details`/
245
+ * `actions`, no `folio`/`cliente`/`verTicket`): esta librería no sabe qué
246
+ * es un "ticket" — el caso de uso real (folio+cliente+estado+SLA+acciones)
247
+ * es responsabilidad de quien arma cada `MapMarker`.
248
+ */
249
+ popup?: {
250
+ title: string;
251
+ description?: string;
252
+ /** Pares clave/valor extra (ej. estado + SLA) — se listan debajo de `description`. */
253
+ details?: {
254
+ label: string;
255
+ value: string;
256
+ }[];
257
+ /** Botones de acción reales — ver JSDoc de `MapPopupAction`. */
258
+ actions?: MapPopupAction[];
259
+ };
260
+ }
261
+ /**
262
+ * Mapa con motor real de Mapbox GL JS — decisión de producto ya cerrada
263
+ * (comparación de 6 proveedores). El access token vive a nivel app, no por
264
+ * uso: ver `provideMapConfig` en `map-config.ts` (mismo criterio que
265
+ * `API_CONFIG` de `packages/api-client`), inyectado una vez en
266
+ * `app.config.ts` de cada app consumidora.
267
+ *
268
+ * `size` (`sm|md|lg` alturas fijas, `full` llena el contenedor padre) NO
269
+ * hace step-down por viewport como `kf-page-header`: ese mecanismo existe
270
+ * ahí porque un tamaño de FUENTE fijo se corta en pantallas chicas; una
271
+ * caja de mapa no tiene ese problema — el canvas de Mapbox ya es
272
+ * responsive dentro de cualquier tamaño de contenedor. Lo que sí hace
273
+ * falta (y que el header no necesita) es un `ResizeObserver` real: Mapbox
274
+ * no se entera solo de que su contenedor cambió de tamaño (un layout que
275
+ * colapsa un sidebar, un cambio de orientación) — hay que llamar
276
+ * `map.resize()` a mano cuando el contenedor cambia (mismo motivo por el
277
+ * que `fullscreenchange` también llama `resize()`, ver `onFullscreenChange`).
278
+ *
279
+ * `variant` es un eje ortogonal a `size` (mismo criterio que `variant` +
280
+ * `tone` en `kf-badge`): `interactive` trae la posibilidad de controles
281
+ * flotantes reales (ver `controls`); `preview` (`interactive: false` de
282
+ * Mapbox GL) es para una card de lista, sin chrome y sin foco por teclado —
283
+ * no hace nada al recibir foco, así que no debe formar parte del tab order,
284
+ * y por eso NUNCA renderiza `controls` aunque el consumidor los pida:
285
+ * togglear "seguir" o abrir pantalla completa no tiene sentido en un mapa
286
+ * que no acepta interacción. Ninguna de las dos variantes oculta la
287
+ * atribución: es requisito de licencia de Mapbox, no cosmético.
288
+ *
289
+ * `theme` (`light|dark|streets|satellite|outdoors`) resuelve el estilo real
290
+ * de Mapbox — eje totalmente distinto del `[data-theme]` de
291
+ * `tokens/themes/*` (paleta de marca de la librería): ninguno de los 5
292
+ * temas de `tokens/` es oscuro todavía, así que no hay ninguna señal real
293
+ * de la que derivar un default "según el tema de marca activo" — construir
294
+ * esa detección ahora sería simular una señal que no existe.
295
+ *
296
+ * `controls` (`MapControlKind[]`, default `[]`) es siempre opt-in: un mapa
297
+ * sin `controls` no dibuja ningún botón flotante, ni siquiera en
298
+ * `interactive` (antes de esta versión, `interactive` agregaba el
299
+ * `NavigationControl` default de Mapbox gratis — se sacó a propósito: era
300
+ * el look genérico de Mapbox, no el de esta librería, exactamente la queja
301
+ * que motivó este rediseño. Un consumidor que quería esos botones de zoom
302
+ * ahora los pide con `[controls]="['zoom']"`). Cada kind es una pieza
303
+ * independiente entre sí (ver `MapControls.ts` para el detalle de cada
304
+ * una) — agregar una no reacomoda ni depende de las demás.
305
+ *
306
+ * <kf-map label="Ubicación del vehículo y del destino" [markers]="markers()" />
307
+ * <kf-map size="sm" variant="preview" label="Vista previa de la ruta" [route]="route()" />
308
+ * <kf-map label="Flota completa" [markers]="fleet()" [controls]="['zoom', 'recenter', 'cluster', 'follow']" />
309
+ */
310
+ declare class MapView {
311
+ readonly size: _angular_core.InputSignal<MapSize>;
312
+ readonly variant: _angular_core.InputSignal<MapVariant>;
313
+ readonly markers: _angular_core.InputSignal<MapMarker[]>;
314
+ readonly route: _angular_core.InputSignal<MapLatLng[] | undefined>;
315
+ readonly fitToMarkers: _angular_core.InputSignalWithTransform<boolean, unknown>;
316
+ readonly center: _angular_core.InputSignal<MapLatLng | undefined>;
317
+ readonly zoom: _angular_core.InputSignal<number | undefined>;
318
+ /**
319
+ * Default `'streets'`, no `'light'` ni `'dark'`. `'dark'` se probó
320
+ * primero por coherencia con la paleta navy de marca, pero una
321
+ * verificación visual real sobre una ubicación de servicio típica (una
322
+ * faena/zona rural, no un centro urbano denso) mostró el problema real:
323
+ * `dark-v11` casi no tiene datos que pintar fuera de ciudades grandes —
324
+ * el mapa se ve prácticamente todo negro, ilegible, exactamente donde
325
+ * más importa que se lea bien (ubicaciones de terreno, no de oficina).
326
+ * `'streets'` SIEMPRE tiene cartografía base visible (calles, nombres,
327
+ * uso de suelo) sin importar la densidad del lugar — es el único de los
328
+ * 5 estilos sin ese riesgo. Sigue siendo overrideable por el consumidor. */
329
+ readonly theme: _angular_core.InputSignal<MapTheme>;
330
+ /** Controles flotantes a mostrar — ver JSDoc de la clase y `MapControls.ts`. */
331
+ readonly controls: _angular_core.InputSignal<MapControlKind[]>;
332
+ /**
333
+ * Descripción accesible real del mapa — el `<div>` donde Mapbox renderiza
334
+ * su canvas queda vacío de semántica para un lector de pantalla. Requerido
335
+ * a propósito: un default genérico ("Mapa") no dice qué está mostrando
336
+ * ESTE mapa; cada consumidor conoce su propio caso real (ej. "Ubicación
337
+ * del vehículo y del destino").
338
+ */
339
+ readonly label: _angular_core.InputSignal<string>;
340
+ /** Se emite al hacer click sobre un marker que declaró `id` (ver JSDoc de
341
+ * `MapMarker.id`) — un marker SIN `id` no es clickeable, no emite nada.
342
+ * No dispara en modo `cluster` (los markers pasan a ser capas WebGL sin
343
+ * DOM propio donde colgar un listener) ni en `variant="preview"` (el
344
+ * mapa entero no es interactivo ahí). */
345
+ readonly markerSelect: _angular_core.OutputEmitterRef<string>;
346
+ /** Se emite al clickear un botón de `MapMarker.popup.actions` — ver el
347
+ * JSDoc de `popup` para por qué reporta `actionKey` (nunca el `label`) y
348
+ * por qué el click no dispara además `markerSelect`. */
349
+ readonly markerAction: _angular_core.OutputEmitterRef<{
350
+ markerId: string;
351
+ actionKey: string;
352
+ }>;
353
+ private readonly mapConfig;
354
+ private readonly environmentInjector;
355
+ private readonly document;
356
+ private readonly destroyRef;
357
+ private readonly hostElementRef;
358
+ private readonly containerRef;
359
+ /** `preview` no es foco-navegable ni se anuncia como región interactiva. */
360
+ protected readonly isPreview: _angular_core.Signal<boolean>;
361
+ protected readonly containerRole: _angular_core.Signal<"img" | "region">;
362
+ /** Ver estado de carga/error real, más abajo (`status`). */
363
+ protected readonly status: _angular_core.WritableSignal<"error" | "loading" | "ready">;
364
+ /** Elección manual del consumidor final vía el control `mapStyle` — pisa
365
+ * `theme()` de forma sostenida (no vuelve a `theme()` sola) hasta que se
366
+ * destruya este `kf-map`; ver `resolvedTheme()`. */
367
+ private readonly userTheme;
368
+ protected readonly styleLabel: _angular_core.Signal<string>;
369
+ /** Igual que `resolvedTheme()` pero público para el template — lo necesita
370
+ * `MapThemePicker` (vía `MapControls`) para marcar la opción activa. */
371
+ protected readonly activeTheme: _angular_core.Signal<MapTheme>;
372
+ protected readonly clusterActive: _angular_core.WritableSignal<boolean>;
373
+ protected readonly followActive: _angular_core.WritableSignal<boolean>;
374
+ protected readonly hasLiveMarker: _angular_core.Signal<boolean>;
375
+ protected readonly isFullscreen: _angular_core.WritableSignal<boolean>;
376
+ /** Sin estilo de marca propio (Mapbox Studio) no hay nada que ciclar con
377
+ * `mapStyle` — se filtra en vez de dejar un botón que no cambiaría nada. */
378
+ protected readonly effectiveControls: _angular_core.Signal<MapControlKind[]>;
379
+ protected readonly showControls: _angular_core.Signal<boolean>;
380
+ private map;
381
+ /** `extraIconRefs`: íconos de las acciones del popup (ver `buildMarkerPopup()`)
382
+ * — se crean con `createComponent()` igual que `iconRef` (el pin), así que
383
+ * necesitan el mismo `.destroy()` en `clearMarkers()`. */
384
+ private markerHandles;
385
+ /** Markers DOM reales sobre cada cluster real (punto 1, a11y) — separados
386
+ * de `markerHandles` porque se reconstruyen en su propio ciclo (ver
387
+ * `syncClusterMarkers()`) y no tienen `iconRef` propio (sin `kf-icon`
388
+ * adentro, ver JSDoc de `addClusterMarkerToMap()`). */
389
+ private clusterMarkerHandles;
390
+ private resizeObserver;
391
+ private currentStyle;
392
+ private mapReady;
393
+ /** Popup resumen de un cluster real (ver `renderClusterPopup()`) — a
394
+ * diferencia del popup de marker individual (siempre en el DOM, mostrado
395
+ * por CSS `:hover`/`:focus-within`), este se crea/destruye a demanda: no
396
+ * cuelga de ningún elemento con estado hover propio, lo abre un click
397
+ * sobre una capa WebGL sin DOM. */
398
+ private clusterPopupElement;
399
+ private clusterPopupIconRefs;
400
+ /** Coordenadas del cluster abierto — necesarias para volver a proyectar
401
+ * contra pantalla en cada `'move'` (ver `repositionClusterPopup()`); el
402
+ * cluster en sí no se mueve, pero su posición EN PANTALLA sí cuando se
403
+ * hace pan. */
404
+ private clusterPopupCoordinates;
405
+ private readonly onFullscreenChange;
406
+ constructor();
407
+ private initializeMap;
408
+ private resolvedTheme;
409
+ private resolvedStyle;
410
+ private toLngLat;
411
+ /**
412
+ * `matchMedia` (no una media query CSS): esta preferencia decide la
413
+ * `duration` que se le pasa a la API de cámara de Mapbox, que corre en
414
+ * JS/WebGL — CSS no tiene nada que animar acá. `document.defaultView` (no
415
+ * el `window` global) para que quede mockeable en test igual que el resto
416
+ * de la clase usa `DOCUMENT` inyectado.
417
+ */
418
+ private prefersReducedMotion;
419
+ /** Colapsa cualquier duración a 0 (instantáneo, sin animar) si el usuario
420
+ * pidió `prefers-reduced-motion: reduce` — un único punto de verdad para
421
+ * que ningún movimiento de cámara se salga de esa preferencia. */
422
+ private cameraDuration;
423
+ private syncCamera;
424
+ /**
425
+ * Encuadra `bounds` con `fitBounds()`, salvo que sea una caja degenerada
426
+ * (ver `DEGENERATE_BOUNDS_THRESHOLD_DEG`) — un solo punto total (un único
427
+ * marker sin ruta, o vehículo+destino prácticamente en el mismo sitio) le
428
+ * da a `fitBounds()` un ancho y alto ~cero, que Mapbox resuelve con un
429
+ * `padding`/zoom que en la práctica deja el mapa mayormente en blanco
430
+ * ("se ve todo blanco", el bug real que esto corrige). Un `easeTo` con
431
+ * zoom fijo sensato muestra contexto real de calle en ese caso, animado
432
+ * igual que el resto (antes era un `jumpTo` seco).
433
+ */
434
+ private frameBounds;
435
+ private isDegenerateBounds;
436
+ /**
437
+ * Extiende sobre markers Y route: el nombre del input sigue el caso de
438
+ * uso principal (encuadrar los markers), pero un mapa con solo `route` y
439
+ * sin `markers` tampoco debería quedar en el encuadre default inútil.
440
+ */
441
+ private computeBounds;
442
+ private syncMarkers;
443
+ /**
444
+ * Crea el marker de Mapbox (DOM + `kf-icon` + wiring de click/teclado/
445
+ * popup) para UN `MapMarker` y lo agrega a `markerHandles` — compartido
446
+ * entre el modo normal (`syncMarkers()`, todos los markers) y el modo
447
+ * cluster (`syncClusterMarkers()`, solo los que quedan sueltos): un punto
448
+ * suelto en modo cluster tiene que verse y comportarse EXACTAMENTE igual
449
+ * que en modo normal, así que no puede haber dos implementaciones.
450
+ */
451
+ private addMarkerToMap;
452
+ /**
453
+ * Mini-card hija del `<div>` del marker — mismo patrón que
454
+ * `.kf-map-controls__label` (ver `MapControls.css`): un elemento SIEMPRE
455
+ * presente en el DOM, oculto por defecto vía `opacity`/`pointer-events` en
456
+ * `MapView.css` y revelado por CSS puro en `:hover`/`:focus-within` del
457
+ * marker padre — sin JS de show/hide, y sin `mapboxgl.Popup` (evita una
458
+ * segunda gestión de overlay/posicionamiento cuando el CSS ya alcanza acá,
459
+ * igual que ya decidió `.kf-map-controls__label`).
460
+ *
461
+ * Todo el subárbol (título, descripción, `details`, `actions`) se arma con
462
+ * `document.createElement` puro, nunca `kf-text`/`kf-button`: este marker
463
+ * entero vive fuera del template de Angular (ver el comentario grande de
464
+ * `ViewEncapsulation.None` más arriba) — meter un componente real acá
465
+ * significaría un `createComponent()` propio por cada línea de texto, el
466
+ * mismo costo que ya paga `kf-icon` en `syncMarkers()` pero sin ninguna
467
+ * necesidad real (no hay nada dinámico que un componente resuelva mejor
468
+ * que texto plano estilado por clase). Es la MISMA decisión que ya regía
469
+ * para `title`/`description` antes de esta ronda — se extiende acá para no
470
+ * mezclar dos convenciones distintas dentro del mismo fragmento de DOM.
471
+ *
472
+ * `actions` sí necesita wiring propio (a diferencia del resto de la card,
473
+ * que burbujea su click al listener ya cableado sobre `element` en
474
+ * `syncMarkers()`): cada botón corta esa burbuja con `stopPropagation()`
475
+ * para reportar `markerAction` en vez de (además) `markerSelect`.
476
+ *
477
+ * Jerarquía visual de `actions` (ver JSDoc de `MapPopupAction`): la
478
+ * PRIMERA acción del array es la principal (`--primary`, fill); el resto
479
+ * es secundaria (`--ghost`, transparente) — mismo peso que ya usan
480
+ * `kf-button[variant=primary]`/`[variant=ghost]` en el resto de la
481
+ * librería, sin inventar un esquema de color nuevo acá.
482
+ *
483
+ * El punto de color junto al título (`kf-map__marker-popup-tone`) reusa
484
+ * el `tone` del propio marker — no el de cada `details` (que son pares
485
+ * label/value genéricos, sin un campo "cuál de estos es el estado"): es
486
+ * la misma señal que YA pinta el círculo del marker en el mapa, mostrada
487
+ * de nuevo acá para que el popup se lea sin tener que mirar el marker.
488
+ */
489
+ private buildMarkerPopup;
490
+ private clearMarkers;
491
+ /**
492
+ * Recorte de borde para el popup de marker individual (punto 2) —
493
+ * `getBoundingClientRect()` del marker y del propio contenedor del mapa,
494
+ * recalculado en el evento que dispara la apertura (`mouseenter`/
495
+ * `focusin`, ver `addMarkerToMap()`). El mecanismo de mostrar/ocultar
496
+ * sigue siendo 100% CSS; esto solo decide 3 clases modificadoras que
497
+ * `MapView.css` traduce en alineación horizontal/vertical:
498
+ *
499
+ * `--align-start` — el popup se desborda por la izquierda del
500
+ * contenedor si se centra sobre el marker: se alinea su borde
501
+ * izquierdo con el del marker en vez de centrarlo.
502
+ * `--align-end` — mismo caso, contra el borde derecho.
503
+ * `--below` — no entra arriba del marker (cerca del borde superior del
504
+ * contenedor): se muestra debajo en vez de arriba.
505
+ *
506
+ * El popup YA está en el DOM en este punto (oculto por `opacity`, no por
507
+ * `display:none` — ver `MapView.css`), así que `getBoundingClientRect()`
508
+ * devuelve su tamaño real aunque todavía no sea visible.
509
+ */
510
+ private positionMarkerPopup;
511
+ /**
512
+ * Clustering nativo de Mapbox (`cluster: true` en la fuente GeoJSON, no un
513
+ * algoritmo armado a mano) — 2 capas, solo para clusters REALES (2+
514
+ * puntos agrupados, `has: point_count`): círculo agregado y su label de
515
+ * cantidad. Un punto que Mapbox deja suelto (sin `point_count`) YA NO
516
+ * tiene capa WebGL propia — ver `syncClusterMarkers()`, que lo renderiza
517
+ * como el mismo marker DOM de siempre (con ícono/tono/popup reales, cosa
518
+ * que una capa `circle` no puede mostrar).
519
+ */
520
+ private syncClusterSource;
521
+ /**
522
+ * Contraparte en DOM de la capa WebGL: mientras esa capa solo dibuja
523
+ * clusters reales, ESTE método decide qué puntos quedan sueltos al zoom
524
+ * ACTUAL (`querySourceFeatures` con el mismo filtro `!has(point_count)`
525
+ * que ya usa Mapbox para separar cluster de leaf) y les arma el mismo
526
+ * marker DOM que tendrían sin clustering — vía `addMarkerToMap()`, la
527
+ * MISMA función que usa el modo normal, no una copia.
528
+ *
529
+ * `querySourceFeatures` puede devolver una feature repetida si cae cerca
530
+ * de un borde interno de tile — de ahí el `Set` sobre `index` (la
531
+ * property que identifica al `MapMarker` de origen, ver
532
+ * `markersToFeatureCollection()`), no una lista con posibles duplicados.
533
+ * Mismo motivo (y mismo `Set`, esta vez sobre `cluster_id`) para los
534
+ * clusters reales que se resuelven acá abajo.
535
+ *
536
+ * Se llama solo cuando `clusterActive()` — ver los listeners `'data'`/
537
+ * `'moveend'` en `initializeMap()` sobre CUÁNDO se llama esto.
538
+ */
539
+ private syncClusterMarkers;
540
+ /**
541
+ * Marker DOM real sobre un cluster (punto 1, a11y) — MISMO patrón de
542
+ * wiring que `addMarkerToMap()` (`role="button"`, `tabindex="0"`,
543
+ * `click`+`keydown` de Enter/Space), aplicado acá porque el círculo que
544
+ * pinta `CLUSTER_CIRCLE_LAYER_ID` es una capa WebGL sin nodo DOM propio
545
+ * —inalcanzable por teclado (WCAG SC 2.1.1 Keyboard). `mapboxgl.Marker`
546
+ * posiciona y reproyecta este `<div>` solo en cada pan/zoom, igual que ya
547
+ * hace con cualquier marker individual — no hace falta reposicionarlo a
548
+ * mano como sí necesita el popup resumen (ver `repositionClusterPopup()`).
549
+ *
550
+ * Sin `kf-icon`/texto propio adentro a propósito: el círculo+conteo que ya
551
+ * pinta la capa WebGL sigue siendo la representación visual (mismo
552
+ * `'circle-radius'`, ver `clusterMarkerRadiusPx()`); este `<div>` es
553
+ * transparente y se superpone exacto encima, aportando SOLO la
554
+ * interactividad real que la capa WebGL no puede tener.
555
+ */
556
+ private addClusterMarkerToMap;
557
+ private clearClusterMarkers;
558
+ /**
559
+ * Activación de un cluster real (click O Enter/Space sobre su marker DOM,
560
+ * ver `addClusterMarkerToMap()`): ANTES hacía zoom directo
561
+ * (`getClusterExpansionZoom()` + `easeTo`) — ahora abre el popup resumen
562
+ * (`renderClusterPopup()`) con la lista de tickets agrupados, y ESE zoom
563
+ * pasa a ser la acción explícita "Acercar para separar" dentro del popup
564
+ * (ver `expandCluster()`).
565
+ */
566
+ private activateCluster;
567
+ /** Dispara el zoom que antes hacía el click directo sobre el círculo —
568
+ * ahora vive detrás del botón "Acercar para separar" del popup resumen. */
569
+ private expandCluster;
570
+ /**
571
+ * `index` identifica de vuelta al `MapMarker` de origen (ver
572
+ * `syncClusterMarkers()`); `title`/`description`/`markerId` son las
573
+ * mismas properties que ya arma `buildMarkerPopup()` (folio/cliente/id
574
+ * del caso real) — se serializan acá TAMBIÉN para que
575
+ * `getClusterLeaves()` pueda listarlas en el popup resumen sin tener que
576
+ * ir a buscar cada `MapMarker` original por separado. Los campos son
577
+ * `string` (no `undefined`): las properties de una `Feature` de GeoJSON
578
+ * no viajan bien como `undefined` — `''` significa "sin dato" en los 3.
579
+ */
580
+ private markersToFeatureCollection;
581
+ private clearClusterLayers;
582
+ /**
583
+ * Popup resumen de un cluster real (punto 4) — a diferencia del popup de
584
+ * marker individual (siempre en el DOM, `:hover`/`:focus-within` puro),
585
+ * este no cuelga de ningún elemento con estado hover propio: lo abre un
586
+ * click sobre una capa WebGL sin DOM, así que se crea/destruye a demanda
587
+ * y su visibilidad la maneja JS, no CSS. Se agrega como hijo del HOST
588
+ * (`kf-map`, no `.kf-map__container`): ambos comparten exactamente la
589
+ * misma caja (`.kf-map__container` es `width/height:100%` sin padding/
590
+ * borde dentro de un host `position:relative`), así que las coordenadas
591
+ * de `map.project()` (relativas al contenedor) valen también acá — mismo
592
+ * ancestro posicionado que ya usa `.kf-map__overlay`.
593
+ *
594
+ * `title`/`description` de cada leaf son `popup.title`/`popup.description`
595
+ * del `MapMarker` original (folio/cliente en el caso real) — ver
596
+ * `markersToFeatureCollection()`. El punto de color reusa `tone`, MISMO
597
+ * criterio que el punto de color del popup individual (ver JSDoc de
598
+ * `buildMarkerPopup()`): no se inventa un texto de "estado" que la
599
+ * librería no tiene, se reusa la señal de color que sí existe.
600
+ */
601
+ private renderClusterPopup;
602
+ /** Reproyecta el popup de cluster contra pantalla — se llama en cada
603
+ * `'move'` (ver `initializeMap()`): el punto geográfico no se mueve, pero
604
+ * su posición EN PANTALLA sí cuando se hace pan. Mismo criterio de
605
+ * recorte de borde que `positionMarkerPopup()` (punto 2), pero en
606
+ * píxeles calculados a mano en vez de clases CSS: este popup no cuelga
607
+ * de un ancestro posicionado por CSS relativo al marker, su posición
608
+ * ENTERA viene de `map.project()`. */
609
+ private repositionClusterPopup;
610
+ private closeClusterPopup;
611
+ private syncRoute;
612
+ private resolveColorToken;
613
+ protected handleZoomIn(): void;
614
+ protected handleZoomOut(): void;
615
+ /** Reencuadra a demanda, ignorando `fitToMarkers()` (tiene sentido pedirlo
616
+ * justo cuando `fitToMarkers` está en `false`) y sin apagar `follow`: si
617
+ * está activo, la próxima posición nueva vuelve a pegar la cámara al
618
+ * marker `live` — este reencuadre es una corrección puntual, no un modo. */
619
+ protected handleRecenter(): void;
620
+ protected handleFollowToggle(): void;
621
+ protected handleClusterToggle(): void;
622
+ /** Reemplaza al viejo ciclado de un click (`handleStyleCycle`): la
623
+ * elección real llega desde `MapThemePicker`, vía `MapControls`. */
624
+ protected handleThemeSelect(theme: MapTheme): void;
625
+ protected handleFullscreenToggle(): void;
626
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MapView, never>;
627
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MapView, "kf-map", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "markers": { "alias": "markers"; "required": false; "isSignal": true; }; "route": { "alias": "route"; "required": false; "isSignal": true; }; "fitToMarkers": { "alias": "fitToMarkers"; "required": false; "isSignal": true; }; "center": { "alias": "center"; "required": false; "isSignal": true; }; "zoom": { "alias": "zoom"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "controls": { "alias": "controls"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; }, { "markerSelect": "markerSelect"; "markerAction": "markerAction"; }, never, never, true, never>;
628
+ }
629
+
630
+ export { MAP_CONFIG, MapControls, MapView, STYLE_BY_THEME, THEME_LABEL, THEME_ORDER, provideMapConfig };
631
+ export type { MapConfig, MapControlKind, MapLatLng, MapMarker, MapMarkerTone, MapPopupAction, MapSize, MapTheme, MapVariant };