@konce-pt/angular 0.7.12 → 0.8.1

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.
@@ -2,614 +2,11 @@ import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, model, input, booleanAttribute, output, inject, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, Injector, effect, untracked, runInInjectionContext, numberAttribute, DestroyRef, TemplateRef, Directive, viewChild, contentChildren, contentChild, afterNextRender } from '@angular/core';
4
4
  import { KptI18n, KptCheckbox, KptDatepicker, KptIcon, KptIconButton, KptFileUpload, KptInput, KptFormField, KptTextarea, KptInputNumber, KptSelect, KptButton, KptIconRegistry, KptDialog } from '@konce-pt/angular';
5
+ import { isFittableBounds, emptyWeek, weekdayName, parseTime, contrastVarOf, colorVarOf, createLeafletEngine, createGoogleEngine, countByCategory, clusterLocations, isCluster, renderClusterHtml, renderMarkerHtml, haversine, resolveCountryView, boundsOf, KPT_MAP_DEFAULT_TILE_URL, KPT_MAP_DEFAULT_ATTRIBUTION, renderUserMarkerHtml, isSplittable, foregroundVarOf, formatDistance, isAlwaysOpen, isOpenAt, groupWeek, nextChange, normalizeUrl, prettyUrl, formatLatLng } from '@konce-pt/map';
6
+ export { KPT_CLUSTER_DEFAULTS, KPT_EARTH_RADIUS, KPT_MAP_COUNTRIES, KPT_MAP_DEFAULT_ATTRIBUTION, KPT_MAP_DEFAULT_COUNTRY, KPT_MAP_DEFAULT_TILE_URL, KPT_MAX_LATITUDE, KPT_MIN_FIT_SPAN, KPT_TILE_SIZE, boundsCenter, boundsContain, boundsOf, centerOfBounds, clampLat, clusterLocations, clusterTier, colorVarOf, contrastVarOf, countByCategory, countryCodes, countryPreset, createGoogleEngine, createLeafletEngine, dominantCategory, emptyWeek, foregroundVarOf, formatDistance, formatLatLng, formatTime, groupWeek, haversine, isAlwaysOpen, isCluster, isFittableBounds, isOpenAt, isSplittable, isoWeekday, loadGoogleMapsApi, metersPerPixel, nextChange, normalizeLng, padBounds, parseTime, project, renderClusterHtml, renderMarkerHtml, renderUserMarkerHtml, resolveCountryView, unproject, weekdayName, worldSize, zoomForBounds } from '@konce-pt/map';
5
7
  import { form, required, min, max, email, pattern, validate, submit, FormRoot, FormField } from '@angular/forms/signals';
6
8
  import { of, Observable } from 'rxjs';
7
9
 
8
- /** Bok kafla w pikselach — stała obu silników. */
9
- const KPT_TILE_SIZE = 256;
10
- /**
11
- * Granica szerokości w Mercatorze. Powyżej niej projekcja ucieka do nieskończoności,
12
- * więc obie biblioteki przycinają świat dokładnie w tym miejscu.
13
- */
14
- const KPT_MAX_LATITUDE = 85.051_128_779_806_59;
15
- /** Promień Ziemi w metrach (sfera, jak w Leaflet `L.CRS.Earth`). */
16
- const KPT_EARTH_RADIUS = 6_378_137;
17
- /** Przycina szerokość do zakresu obsługiwanego przez Mercator. */
18
- function clampLat(lat) {
19
- return Math.max(-KPT_MAX_LATITUDE, Math.min(KPT_MAX_LATITUDE, lat));
20
- }
21
- /**
22
- * Zawija długość geograficzną do zakresu [-180, 180].
23
- *
24
- * Wartości już w zakresie wracają nietknięte — także -180, bo w projekcji jest to
25
- * lewa krawędź świata (x = 0), a nie ta sama kolumna pikseli co +180. Zawijanie
26
- * dotyczy wyłącznie tego, co wychodzi poza zakres: 540° to ten sam południk co 180°.
27
- */
28
- function normalizeLng(lng) {
29
- if (lng >= -180 && lng <= 180)
30
- return lng;
31
- const wrapped = ((lng + 180) % 360 + 360) % 360 - 180;
32
- return wrapped === -180 ? 180 : wrapped;
33
- }
34
- /** Liczba pikseli świata na danym zoomie. */
35
- function worldSize(zoom) {
36
- return KPT_TILE_SIZE * Math.pow(2, zoom);
37
- }
38
- /** Współrzędne → piksele świata na danym zoomie (origin w lewym górnym rogu). */
39
- function project(at, zoom) {
40
- const size = worldSize(zoom);
41
- const lat = clampLat(at.lat);
42
- const sin = Math.sin((lat * Math.PI) / 180);
43
- return {
44
- x: size * (normalizeLng(at.lng) / 360 + 0.5),
45
- y: size * (0.5 - Math.log((1 + sin) / (1 - sin)) / (4 * Math.PI)),
46
- };
47
- }
48
- /** Piksele świata → współrzędne. Odwrotność `project`. */
49
- function unproject(point, zoom) {
50
- const size = worldSize(zoom);
51
- const lng = (point.x / size - 0.5) * 360;
52
- const n = Math.PI * (1 - 2 * (point.y / size));
53
- const lat = (180 / Math.PI) * Math.atan(Math.sinh(n));
54
- return { lat, lng };
55
- }
56
- /** Najmniejszy prostokąt obejmujący wszystkie punkty. Pusta lista → `null`. */
57
- function boundsOf(points) {
58
- if (points.length === 0)
59
- return null;
60
- let south = Infinity;
61
- let north = -Infinity;
62
- let west = Infinity;
63
- let east = -Infinity;
64
- for (const p of points) {
65
- if (p.lat < south)
66
- south = p.lat;
67
- if (p.lat > north)
68
- north = p.lat;
69
- if (p.lng < west)
70
- west = p.lng;
71
- if (p.lng > east)
72
- east = p.lng;
73
- }
74
- return { south, west, north, east };
75
- }
76
- /**
77
- * Najmniejszy zasięg w stopniach, przy którym `fitBounds` ma jeszcze sens (~100 m).
78
- *
79
- * Geokodery obrysowują pojedynczy adres prostokątem rzędu 0,0001°. Wpasowanie takiego
80
- * w kadr wjeżdża na górny limit zoomu, a wynik zależy od tego, jak dostawca obrysował
81
- * budynek — dwa sąsiednie adresy lądują w różnych przybliżeniach.
82
- */
83
- const KPT_MIN_FIT_SPAN = 0.001;
84
- /**
85
- * Czy prostokąt warto wpasowywać w kadr, zamiast potraktować go jak punkt.
86
- *
87
- * `false` dla zasięgu zdegenerowanego albo ciaśniejszego niż `minSpan` w OBU osiach —
88
- * długa, wąska ulica ma jeden wymiar mikroskopijny i dalej zasługuje na `fitBounds`.
89
- */
90
- function isFittableBounds(bounds, minSpan = KPT_MIN_FIT_SPAN) {
91
- const lat = bounds.north - bounds.south;
92
- const lng = bounds.east - bounds.west;
93
- if (!Number.isFinite(lat) || !Number.isFinite(lng) || lat < 0 || lng < 0)
94
- return false;
95
- return lat >= minSpan || lng >= minSpan;
96
- }
97
- /** Środek prostokąta. */
98
- function boundsCenter(bounds) {
99
- return { lat: (bounds.south + bounds.north) / 2, lng: (bounds.west + bounds.east) / 2 };
100
- }
101
- /** Czy punkt leży w prostokącie (granice włącznie). */
102
- function boundsContain(bounds, at) {
103
- return at.lat >= bounds.south && at.lat <= bounds.north && at.lng >= bounds.west && at.lng <= bounds.east;
104
- }
105
- /** Rozszerza prostokąt o zadaną liczbę stopni w każdą stronę. */
106
- function padBounds(bounds, degrees) {
107
- return {
108
- south: clampLat(bounds.south - degrees),
109
- north: clampLat(bounds.north + degrees),
110
- west: bounds.west - degrees,
111
- east: bounds.east + degrees,
112
- };
113
- }
114
- /** Odległość po wielkim kole w metrach. */
115
- function haversine(a, b) {
116
- const toRad = Math.PI / 180;
117
- const dLat = (b.lat - a.lat) * toRad;
118
- const dLng = (b.lng - a.lng) * toRad;
119
- const s = Math.sin(dLat / 2) ** 2 +
120
- Math.cos(a.lat * toRad) * Math.cos(b.lat * toRad) * Math.sin(dLng / 2) ** 2;
121
- return 2 * KPT_EARTH_RADIUS * Math.asin(Math.min(1, Math.sqrt(s)));
122
- }
123
- /**
124
- * Ile metrów przypada na piksel na danej szerokości i zoomie.
125
- * Potrzebne, żeby krąg dokładności geolokalizacji miał realny promień.
126
- */
127
- function metersPerPixel(lat, zoom) {
128
- return (Math.cos((clampLat(lat) * Math.PI) / 180) * 2 * Math.PI * KPT_EARTH_RADIUS) / worldSize(zoom);
129
- }
130
- /**
131
- * Największy zoom, przy którym `bounds` mieści się w kontenerze o zadanym rozmiarze.
132
- * Zwraca liczbę zmiennoprzecinkową — zaokrąglenie w dół zostawiamy wołającemu.
133
- */
134
- function zoomForBounds(bounds, size, paddingPx = 0) {
135
- const w = Math.max(1, size.width - paddingPx * 2);
136
- const h = Math.max(1, size.height - paddingPx * 2);
137
- // Rozpiętość liczona na zoomie 0, potem skalowana logarytmicznie.
138
- const sw = project({ lat: bounds.south, lng: bounds.west }, 0);
139
- const ne = project({ lat: bounds.north, lng: bounds.east }, 0);
140
- const dx = Math.max(1e-9, Math.abs(ne.x - sw.x));
141
- const dy = Math.max(1e-9, Math.abs(sw.y - ne.y));
142
- return Math.min(Math.log2(w / dx), Math.log2(h / dy));
143
- }
144
-
145
- /*
146
- * Grupowanie znaczników — siatka w przestrzeni pikseli, O(n), deterministyczna.
147
- * Pure TS: ten sam kod obsługuje silnik Leaflet i Google Maps, i przenosi się do Reacta.
148
- */
149
- const KPT_CLUSTER_DEFAULTS = {
150
- gridSize: 60,
151
- maxZoom: 16,
152
- minPoints: 2,
153
- };
154
- /**
155
- * Zwija bliskie sobie lokalizacje w grupy. Zwraca listę mieszaną: grupy i pojedyncze punkty.
156
- *
157
- * Środek grupy uśredniamy w przestrzeni pikseli, nie w stopniach. Mercator rozciąga
158
- * stopnie tym mocniej, im dalej od równika, więc średnia w stopniach wypada obok
159
- * wizualnego środka kropek na ekranie — a to jego ma pilnować znacznik grupy.
160
- */
161
- function clusterLocations(locations, options) {
162
- const gridSize = options.gridSize ?? KPT_CLUSTER_DEFAULTS.gridSize;
163
- const maxZoom = options.maxZoom ?? KPT_CLUSTER_DEFAULTS.maxZoom;
164
- const minPoints = Math.max(2, options.minPoints ?? KPT_CLUSTER_DEFAULTS.minPoints);
165
- const zoom = options.zoom;
166
- if (locations.length === 0)
167
- return [];
168
- // Powyżej progu pokazujemy wszystko osobno — użytkownik przybliżył się celowo.
169
- if (zoom > maxZoom || gridSize <= 0)
170
- return [...locations];
171
- const cells = new Map();
172
- for (const loc of locations) {
173
- const p = project(loc.at, zoom);
174
- const cellX = Math.floor(p.x / gridSize);
175
- const cellY = Math.floor(p.y / gridSize);
176
- const key = `${cellX}:${cellY}`;
177
- const cell = cells.get(key);
178
- if (cell) {
179
- cell.items.push(loc);
180
- cell.sumX += p.x;
181
- cell.sumY += p.y;
182
- }
183
- else {
184
- cells.set(key, { cellX, cellY, items: [loc], sumX: p.x, sumY: p.y });
185
- }
186
- }
187
- const out = [];
188
- for (const cell of cells.values()) {
189
- if (cell.items.length < minPoints) {
190
- out.push(...cell.items);
191
- continue;
192
- }
193
- const center = unproject({ x: cell.sumX / cell.items.length, y: cell.sumY / cell.items.length }, zoom);
194
- out.push({
195
- id: `c:${zoom}:${cell.cellX}:${cell.cellY}`,
196
- at: center,
197
- count: cell.items.length,
198
- dominantCategoryId: dominantCategory(cell.items, options.categoryOrder),
199
- items: cell.items,
200
- // Punkty w komórce zawsze istnieją, więc `boundsOf` nie zwróci tu null.
201
- bounds: boundsOf(cell.items.map((i) => i.at)),
202
- });
203
- }
204
- return out;
205
- }
206
- /**
207
- * Najliczniejsza kategoria w grupie. Remis rozstrzyga `categoryOrder`, a bez niej
208
- * kolejność pierwszego wystąpienia — w obu wypadkach wynik jest deterministyczny.
209
- */
210
- function dominantCategory(items, categoryOrder) {
211
- const counts = new Map();
212
- const firstSeen = new Map();
213
- items.forEach((item, index) => {
214
- counts.set(item.categoryId, (counts.get(item.categoryId) ?? 0) + 1);
215
- if (!firstSeen.has(item.categoryId))
216
- firstSeen.set(item.categoryId, index);
217
- });
218
- let best = items[0].categoryId;
219
- let bestCount = -1;
220
- for (const [id, count] of counts) {
221
- if (count > bestCount) {
222
- best = id;
223
- bestCount = count;
224
- continue;
225
- }
226
- if (count < bestCount)
227
- continue;
228
- // Remis — niższy indeks w `categoryOrder` wygrywa, a poza nią wcześniejsze wystąpienie.
229
- const rank = (c) => {
230
- const i = categoryOrder?.indexOf(c) ?? -1;
231
- return i >= 0 ? i : Number.MAX_SAFE_INTEGER;
232
- };
233
- const byOrder = rank(id) - rank(best);
234
- if (byOrder < 0 || (byOrder === 0 && firstSeen.get(id) < firstSeen.get(best)))
235
- best = id;
236
- }
237
- return best;
238
- }
239
- /** Zlicza lokalizacje w rozbiciu na kategorie — licznik obok pozycji legendy. */
240
- function countByCategory(locations) {
241
- const out = {};
242
- for (const loc of locations)
243
- out[loc.categoryId] = (out[loc.categoryId] ?? 0) + 1;
244
- return out;
245
- }
246
- /**
247
- * Czy grupa da się rozbić przybliżeniem. Punkty o tej samej współrzędnej zostaną razem
248
- * na każdym zoomie — wtedy kliknięcie ma otworzyć listę zamiast zoomować w nieskończoność.
249
- */
250
- function isSplittable(cluster) {
251
- const { south, west, north, east } = cluster.bounds;
252
- return north - south > 1e-7 || east - west > 1e-7;
253
- }
254
-
255
- /*
256
- * Presety widoku bazowego — pure TS. Domyślny kraj to Polska.
257
- * Kto potrzebuje kraju spoza listy, podaje `[view]` albo `[bounds]` wprost.
258
- */
259
- /** Kod kraju użyty, gdy aplikacja nie poda żadnego. */
260
- const KPT_MAP_DEFAULT_COUNTRY = 'PL';
261
- /**
262
- * Zasięgi lądowe (bez terytoriów zamorskich) zaokrąglone do dwóch miejsc —
263
- * to widok startowy, nie granica administracyjna.
264
- */
265
- const KPT_MAP_COUNTRIES = {
266
- PL: { code: 'PL', center: { lat: 52.07, lng: 19.48 }, zoom: 6, bounds: { south: 49.0, west: 14.07, north: 54.84, east: 24.15 } },
267
- DE: { code: 'DE', center: { lat: 51.17, lng: 10.45 }, zoom: 6, bounds: { south: 47.27, west: 5.87, north: 55.06, east: 15.04 } },
268
- CZ: { code: 'CZ', center: { lat: 49.82, lng: 15.47 }, zoom: 7, bounds: { south: 48.55, west: 12.09, north: 51.06, east: 18.86 } },
269
- SK: { code: 'SK', center: { lat: 48.67, lng: 19.7 }, zoom: 7, bounds: { south: 47.73, west: 16.83, north: 49.61, east: 22.57 } },
270
- UA: { code: 'UA', center: { lat: 48.38, lng: 31.17 }, zoom: 6, bounds: { south: 44.39, west: 22.14, north: 52.38, east: 40.23 } },
271
- LT: { code: 'LT', center: { lat: 55.17, lng: 23.88 }, zoom: 7, bounds: { south: 53.9, west: 20.94, north: 56.45, east: 26.84 } },
272
- LV: { code: 'LV', center: { lat: 56.88, lng: 24.6 }, zoom: 7, bounds: { south: 55.67, west: 20.97, north: 58.09, east: 28.24 } },
273
- GB: { code: 'GB', center: { lat: 54.0, lng: -2.9 }, zoom: 5, bounds: { south: 49.9, west: -8.65, north: 60.86, east: 1.77 } },
274
- FR: { code: 'FR', center: { lat: 46.6, lng: 2.45 }, zoom: 6, bounds: { south: 41.33, west: -5.14, north: 51.09, east: 9.56 } },
275
- ES: { code: 'ES', center: { lat: 40.2, lng: -3.3 }, zoom: 6, bounds: { south: 35.95, west: -9.3, north: 43.79, east: 3.32 } },
276
- IT: { code: 'IT', center: { lat: 42.5, lng: 12.5 }, zoom: 6, bounds: { south: 36.65, west: 6.63, north: 47.09, east: 18.52 } },
277
- NL: { code: 'NL', center: { lat: 52.2, lng: 5.5 }, zoom: 7, bounds: { south: 50.75, west: 3.36, north: 53.56, east: 7.23 } },
278
- BE: { code: 'BE', center: { lat: 50.64, lng: 4.66 }, zoom: 8, bounds: { south: 49.5, west: 2.54, north: 51.5, east: 6.41 } },
279
- AT: { code: 'AT', center: { lat: 47.7, lng: 13.35 }, zoom: 7, bounds: { south: 46.37, west: 9.53, north: 49.02, east: 17.16 } },
280
- SE: { code: 'SE', center: { lat: 62.2, lng: 16.3 }, zoom: 4, bounds: { south: 55.34, west: 11.11, north: 69.06, east: 24.16 } },
281
- NO: { code: 'NO', center: { lat: 64.5, lng: 12.0 }, zoom: 4, bounds: { south: 57.98, west: 4.65, north: 71.19, east: 31.08 } },
282
- US: { code: 'US', center: { lat: 39.5, lng: -98.35 }, zoom: 4, bounds: { south: 24.4, west: -125.0, north: 49.38, east: -66.93 } },
283
- EU: { code: 'EU', center: { lat: 54.0, lng: 15.0 }, zoom: 4, bounds: { south: 34.8, west: -11.0, north: 71.2, east: 40.2 } },
284
- };
285
- /** Preset kraju po kodzie (wielkość liter bez znaczenia); `undefined`, gdy nieznany. */
286
- function countryPreset(code) {
287
- return KPT_MAP_COUNTRIES[code?.toUpperCase()];
288
- }
289
- /** Kody wszystkich presetów — do listy wyboru w aplikacji. */
290
- function countryCodes() {
291
- return Object.keys(KPT_MAP_COUNTRIES);
292
- }
293
- /**
294
- * Widok startowy: preset kraju, a przy nieznanym kodzie preset domyślny.
295
- * Zawsze zwraca coś sensownego — mapa nigdy nie startuje na `[0, 0]`.
296
- */
297
- function resolveCountryView(code) {
298
- return (code ? countryPreset(code) : undefined) ?? KPT_MAP_COUNTRIES[KPT_MAP_DEFAULT_COUNTRY];
299
- }
300
- /** Środek prostokąta kraju — pomocnicze przy własnych `bounds`. */
301
- function centerOfBounds(bounds) {
302
- return boundsCenter(bounds);
303
- }
304
-
305
- /** Domyślne źródło kafli. Do produkcji podstaw własny serwer — patrz llms.txt. */
306
- const KPT_MAP_DEFAULT_TILE_URL = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
307
- const KPT_MAP_DEFAULT_ATTRIBUTION = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
308
-
309
- const ROLE_VARS = {
310
- primary: 'var(--kpt-color-primary)',
311
- success: 'var(--kpt-color-success)',
312
- warning: 'var(--kpt-color-warning)',
313
- danger: 'var(--kpt-color-danger)',
314
- info: 'var(--kpt-color-info)',
315
- muted: 'var(--kpt-color-muted)',
316
- };
317
- const ROLE_CONTRAST_VARS = {
318
- primary: 'var(--kpt-color-primary-contrast)',
319
- success: 'var(--kpt-color-success-contrast)',
320
- warning: 'var(--kpt-color-warning-contrast)',
321
- danger: 'var(--kpt-color-danger-contrast)',
322
- info: 'var(--kpt-color-info-contrast)',
323
- muted: 'var(--kpt-color-on-muted)',
324
- };
325
- /**
326
- * Kształt jedynego wejścia spoza znanej roli, jakie kontrakt `KptMapColor` dopuszcza —
327
- * gotowy token `var(--kpt-*)`. Dane runtime (JSON/CMS) nie są tym kontraktem związane, więc
328
- * zanim trafią do atrybutu `style="..."` budowanego konkatenacją stringów w `renderMarkerHtml`/
329
- * `renderClusterHtml`, muszą zostać zweryfikowane — inaczej cudzysłów/`>` wyrywa się z atrybutu
330
- * (HTML injection), a średnik wstrzykuje dodatkową deklarację CSS.
331
- */
332
- const SAFE_VAR_TOKEN = /^var\(--kpt-[\w-]+\)$/;
333
- /** Rola koloru → zmienna CSS. Gotowe `var(--kpt-*)` przechodzi bez zmian; cokolwiek spoza tego
334
- * kontraktu dostaje bezpieczny fallback zamiast trafić surowo do wyjścia. */
335
- function colorVarOf(color) {
336
- const known = ROLE_VARS[color];
337
- if (known)
338
- return known;
339
- return SAFE_VAR_TOKEN.test(color) ? color : ROLE_VARS.muted;
340
- }
341
- /** Kolor treści na tle `color` — dla ról znany, dla własnych zmiennych fallback na biel tokenową. */
342
- function contrastVarOf(color) {
343
- return ROLE_CONTRAST_VARS[color] ?? 'var(--kpt-color-text-inverse)';
344
- }
345
- /**
346
- * Kolor **glifu stojącego wprost na powierzchni**, bez własnego tła.
347
- *
348
- * Różni się od `colorVarOf()` tylko dla roli `muted`: `--kpt-color-muted` to neutral-100,
349
- * czyli token tła, a nie treści — goły znak w tym kolorze na jasnej powierzchni jest
350
- * niewidoczny. Bierzemy więc czytelny szary tekstowy. Pozostałe role są nasycone
351
- * i jako treść czytają się bez zmian.
352
- */
353
- const ROLE_FOREGROUND_VARS = {
354
- ...ROLE_VARS,
355
- muted: 'var(--kpt-color-on-surface-muted)',
356
- };
357
- function foregroundVarOf(color) {
358
- const known = ROLE_FOREGROUND_VARS[color];
359
- if (known)
360
- return known;
361
- return SAFE_VAR_TOKEN.test(color) ? color : ROLE_FOREGROUND_VARS.muted;
362
- }
363
- /** Ucieka znaki, które rozbiłyby atrybut albo wstrzyknęły znacznik. */
364
- function escapeHtml(value) {
365
- return value
366
- .replaceAll('&', '&amp;')
367
- .replaceAll('<', '&lt;')
368
- .replaceAll('>', '&gt;')
369
- .replaceAll('"', '&quot;')
370
- .replaceAll("'", '&#39;');
371
- }
372
- /**
373
- * Pinezka o wysokości `--kpt-map-marker-size`, zakotwiczona czubkiem w punkcie.
374
- * `viewBox` 24×32: 24 szerokości koła + ogon do 32.
375
- */
376
- const PIN_PATH = 'M12 0C5.8 0 .8 5 .8 11.2c0 8 9.9 19.4 10.3 19.9a1.2 1.2 0 0 0 1.8 0c.4-.5 10.3-11.9 10.3-19.9C23.2 5 18.2 0 12 0z';
377
- function renderMarkerHtml(input) {
378
- // `shape` trafia do atrybutu `class="..."` konkatenacją stringów — `KptMapMarkerShape` ogranicza
379
- // to tylko compile-time, dane runtime (JSON/CMS) nie są tym związane (ten sam powód, dla którego
380
- // `color`/`contrast` dostały escapowanie/whitelistę wyżej w pliku).
381
- const shape = escapeHtml(input.shape ?? 'pin');
382
- const color = colorVarOf(input.color);
383
- const contrast = contrastVarOf(input.color);
384
- const label = escapeHtml(input.label);
385
- const cls = `kpt-map__marker kpt-map__marker--${shape}${input.selected ? ' kpt-map__marker--selected' : ''}`;
386
- const glyph = input.iconSvgInner
387
- ? `<svg class="kpt-map__marker-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"` +
388
- ` stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${input.iconSvgInner}</svg>`
389
- : '';
390
- const body = shape === 'pin'
391
- ? `<svg class="kpt-map__marker-shape" viewBox="0 0 24 32" aria-hidden="true">` +
392
- `<path d="${PIN_PATH}" /></svg><span class="kpt-map__marker-glyph">${glyph}</span>`
393
- : `<span class="kpt-map__marker-glyph">${glyph}</span>`;
394
- return (`<div class="${cls}" role="img" aria-label="${label}" title="${label}"` +
395
- ` style="--_kpt-map-marker-bg:${color};--_kpt-map-marker-fg:${contrast}">${body}</div>`);
396
- }
397
- /** Koło z licznikiem; rozmiar rośnie schodkowo z liczebnością grupy. */
398
- function renderClusterHtml(input) {
399
- const color = colorVarOf(input.color);
400
- const contrast = contrastVarOf(input.color);
401
- const label = escapeHtml(input.label);
402
- const tier = clusterTier(input.count);
403
- return (`<div class="kpt-map__cluster kpt-map__cluster--${tier}" role="img" aria-label="${label}" title="${label}"` +
404
- ` style="--_kpt-map-cluster-bg:${color};--_kpt-map-cluster-fg:${contrast}">` +
405
- `<span class="kpt-map__cluster-count">${formatCount(input.count)}</span></div>`);
406
- }
407
- /** Próg wielkości grupy — trzy stopnie wystarczą, żeby zobaczyć różnicę bez skalowania ciągłego. */
408
- function clusterTier(count) {
409
- if (count < 10)
410
- return 'sm';
411
- if (count < 100)
412
- return 'md';
413
- return 'lg';
414
- }
415
- /** Powyżej 999 licznik przestaje się mieścić — skracamy. */
416
- function formatCount(count) {
417
- return count > 999 ? '999+' : String(count);
418
- }
419
- /** Znacznik pozycji użytkownika — ikona podawana z zewnątrz, jak przy zwykłym znaczniku. */
420
- function renderUserMarkerHtml(input) {
421
- const label = escapeHtml(input.label);
422
- const glyph = input.iconSvgInner
423
- ? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"` +
424
- ` stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${input.iconSvgInner}</svg>`
425
- : '';
426
- return `<div class="kpt-map__user-marker" role="img" aria-label="${label}" title="${label}">${glyph}</div>`;
427
- }
428
-
429
- /*
430
- * Typy rdzenia mapy — pure TS, zero Angulara i zero silnika mapowego.
431
- * Ten plik przenosi się do portu React bez zmian.
432
- */
433
- /** Zawęża wynik `clusterLocations` do grupy. */
434
- function isCluster(entry) {
435
- return entry.count !== undefined;
436
- }
437
-
438
- /*
439
- * Godziny otwarcia — pure TS, bez Angulara i bez silnika mapowego.
440
- * Ten plik przenosi się do portu React bez zmian.
441
- *
442
- * Umowa o czasie: `HH:MM` w czasie lokalnym miejsca, a porównania robimy
443
- * na lokalnym czasie przeglądarki. Miejsce w innej strefie niż użytkownik
444
- * pokaże więc status wg zegara użytkownika — to świadome uproszczenie,
445
- * bo model nie niesie strefy. Kto potrzebuje inaczej, liczy status u siebie.
446
- */
447
- /** Minuta doby dla `HH:MM`; `24:00` to 1440. `null`, gdy zapis jest nieprawidłowy. */
448
- function parseTime(value) {
449
- const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
450
- if (!match)
451
- return null;
452
- const hours = Number(match[1]);
453
- const minutes = Number(match[2]);
454
- if (minutes > 59)
455
- return null;
456
- const total = hours * 60 + minutes;
457
- return total > 1440 ? null : total;
458
- }
459
- /** `HH:MM` z minuty doby. 1440 wraca jako `24:00`, nie `00:00`. */
460
- function formatTime(minutes) {
461
- const m = Math.max(0, Math.min(1440, Math.round(minutes)));
462
- return `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`;
463
- }
464
- /** Dzień tygodnia wg ISO (1 = poniedziałek) z natywnej daty. */
465
- function isoWeekday(date) {
466
- const day = date.getDay();
467
- return (day === 0 ? 7 : day);
468
- }
469
- function previousDay(day) {
470
- return (day === 1 ? 7 : day - 1);
471
- }
472
- function dayOf(hours, day) {
473
- return hours.find((entry) => entry.day === day);
474
- }
475
- /** Przedział przechodzi przez północ, gdy kończy się nie później niż zaczyna. */
476
- function crossesMidnight(range) {
477
- const from = parseTime(range.from);
478
- const to = parseTime(range.to);
479
- return from !== null && to !== null && to <= from;
480
- }
481
- function usableRanges(entry) {
482
- if (!entry || entry.closed)
483
- return [];
484
- return entry.ranges.filter((r) => parseTime(r.from) !== null && parseTime(r.to) !== null);
485
- }
486
- /**
487
- * Czy miejsce jest otwarte w danym momencie.
488
- *
489
- * Sprawdza dwa dni: bieżący oraz poprzedni, bo przedział przez północ
490
- * (`22:00 – 01:00`) obejmuje wczesne godziny dnia następnego.
491
- */
492
- function isOpenAt(hours, date) {
493
- if (!hours || hours.length === 0)
494
- return false;
495
- const minute = date.getHours() * 60 + date.getMinutes();
496
- const today = isoWeekday(date);
497
- for (const range of usableRanges(dayOf(hours, today))) {
498
- const from = parseTime(range.from);
499
- const to = parseTime(range.to);
500
- if (to > from ? minute >= from && minute < to : minute >= from)
501
- return true;
502
- }
503
- for (const range of usableRanges(dayOf(hours, previousDay(today)))) {
504
- if (!crossesMidnight(range))
505
- continue;
506
- if (minute < parseTime(range.to))
507
- return true;
508
- }
509
- return false;
510
- }
511
- /** Czy rozkład to pełna doba przez siedem dni — wtedy karta pisze po prostu „24/7". */
512
- function isAlwaysOpen(hours) {
513
- if (!hours)
514
- return false;
515
- for (let day = 1; day <= 7; day++) {
516
- const ranges = usableRanges(dayOf(hours, day));
517
- const full = ranges.some((r) => parseTime(r.from) === 0 && parseTime(r.to) === 1440);
518
- if (!full)
519
- return false;
520
- }
521
- return true;
522
- }
523
- /** Najbliższa zmiana stanu otwarte/zamknięte albo `null`, gdy w ciągu tygodnia jej nie ma. */
524
- function nextChange(hours, date) {
525
- if (!hours || hours.length === 0)
526
- return null;
527
- const now = isOpenAt(hours, date);
528
- // Granice przedziałów na najbliższe osiem dób; pierwsza różnica wygrywa.
529
- for (let offset = 0; offset <= 8; offset++) {
530
- const base = new Date(date);
531
- base.setHours(0, 0, 0, 0);
532
- base.setDate(base.getDate() + offset);
533
- const day = isoWeekday(base);
534
- const minutes = new Set();
535
- for (const range of usableRanges(dayOf(hours, day))) {
536
- minutes.add(parseTime(range.from));
537
- const to = parseTime(range.to);
538
- if (to > parseTime(range.from))
539
- minutes.add(to);
540
- }
541
- for (const range of usableRanges(dayOf(hours, previousDay(day)))) {
542
- if (crossesMidnight(range))
543
- minutes.add(parseTime(range.to));
544
- }
545
- for (const minute of [...minutes].sort((a, b) => a - b)) {
546
- const at = new Date(base);
547
- at.setMinutes(minute);
548
- if (at.getTime() <= date.getTime())
549
- continue;
550
- const open = isOpenAt(hours, at);
551
- if (open !== now)
552
- return { open, at };
553
- }
554
- }
555
- return null;
556
- }
557
- /** Skrócona nazwa dnia z `Intl` — bez słownika, więc działa dla dowolnego locale. */
558
- function weekdayName(day, locale) {
559
- // 2024-01-01 był poniedziałkiem, więc dzień ISO wypada na tę samą liczbę w miesiącu.
560
- const date = new Date(Date.UTC(2024, 0, day));
561
- return new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }).format(date);
562
- }
563
- function signatureOf(entry) {
564
- const ranges = usableRanges(entry);
565
- if (ranges.length === 0)
566
- return 'closed';
567
- return ranges.map((r) => `${parseTime(r.from)}-${parseTime(r.to)}`).join(',');
568
- }
569
- /**
570
- * Rozkład tygodnia z sąsiadującymi dniami sklejonymi w jeden wiersz.
571
- *
572
- * Niedziela i poniedziałek się nie sklejają, choć w tablicy sąsiadują — tydzień
573
- * czyta się liniowo od poniedziałku i zawijanie byłoby mylące.
574
- */
575
- function groupWeek(hours, locale, separator = ' – ') {
576
- if (!hours || hours.length === 0)
577
- return [];
578
- const groups = [];
579
- let signature = null;
580
- for (let day = 1; day <= 7; day++) {
581
- const entry = dayOf(hours, day);
582
- const current = signatureOf(entry);
583
- const previous = groups[groups.length - 1];
584
- if (current === signature && previous) {
585
- previous.days.push(day);
586
- continue;
587
- }
588
- signature = current;
589
- const ranges = usableRanges(entry);
590
- groups.push({
591
- label: '',
592
- days: [day],
593
- closed: ranges.length === 0,
594
- ranges: ranges.map((r) => `${r.from}${separator}${r.to}`),
595
- });
596
- }
597
- for (const group of groups) {
598
- const first = weekdayName(group.days[0], locale);
599
- const last = weekdayName(group.days[group.days.length - 1], locale);
600
- group.label = group.days.length === 1 ? first : `${first}–${last}`;
601
- }
602
- return groups;
603
- }
604
- /** Siedem dni oznaczonych jako zamknięte — wartość startowa pola `hours` w formularzu. */
605
- function emptyWeek() {
606
- return Array.from({ length: 7 }, (_, i) => ({
607
- day: (i + 1),
608
- closed: true,
609
- ranges: [],
610
- }));
611
- }
612
-
613
10
  const KPT_MAP_GEOCODER = new InjectionToken('KPT_MAP_GEOCODER');
614
11
  /** Publiczna instancja OSM. Ma twardą politykę użycia — patrz JSDoc providera. */
615
12
  const NOMINATIM_URL = 'https://nominatim.openstreetmap.org';
@@ -1704,7 +1101,7 @@ class KptMapCreateDialog {
1704
1101
  KptMapImageField,
1705
1102
  ], host: { class: 'kpt-map-create' }, template: "@if (formRoot(); as root) {\n<form class=\"kpt-map-create__form\" [formRoot]=\"root\" (submit)=\"onSubmit()\">\n <p class=\"kpt-map-create__coords\">\n <span>{{ i18n.t('map.latitude') }}: <strong>{{ coords().lat }}</strong></span>\n <span>{{ i18n.t('map.longitude') }}: <strong>{{ coords().lng }}</strong></span>\n </p>\n\n @if (geocoding()) {\n <p class=\"kpt-map-create__geo\" aria-live=\"polite\">{{ i18n.t('map.lookingUpAddress') }}</p>\n } @else if (address(); as adres) {\n @if (adres.label) {\n <p class=\"kpt-map-create__geo\" aria-live=\"polite\">{{ adres.label }}</p>\n }\n } @else if (hasAutofill()) {\n <p class=\"kpt-map-create__geo\" aria-live=\"polite\">{{ i18n.t('map.addressNotFound') }}</p>\n }\n\n @for (def of fields(); track def.key) {\n <kpt-form-field\n [label]=\"def.label\"\n [hint]=\"def.hint ?? ''\"\n [error]=\"errorOf(def.key)\"\n [required]=\"!!def.required\"\n [controlId]=\"hasOwnInput(def) ? controlId(def) : ''\"\n >\n @switch (def.type) {\n @case ('textarea') {\n <kpt-textarea [formField]=\"$any(fieldOf(def.key))\" [placeholder]=\"def.placeholder ?? ''\" />\n }\n @case ('number') {\n <!-- Bez [min]/[max]: FormField wyprowadza je z walidator\u00F3w `min()`/`max()`\n zadeklarowanych w schemie i zabrania jawnego wi\u0105zania (NG8022). -->\n <kpt-input-number [formField]=\"$any(fieldOf(def.key))\" [placeholder]=\"def.placeholder ?? ''\" />\n }\n @case ('date') {\n <kpt-datepicker\n [formField]=\"$any(fieldOf(def.key))\"\n [placeholder]=\"def.placeholder ?? ''\"\n [minDate]=\"$any(def.min ?? null)\"\n [maxDate]=\"$any(def.max ?? null)\"\n />\n }\n @case ('select') {\n <kpt-select\n [formField]=\"$any(fieldOf(def.key))\"\n [options]=\"optionsOf(def)\"\n [placeholder]=\"def.placeholder ?? ''\"\n />\n }\n @case ('multiselect') {\n <kpt-select\n [formField]=\"$any(fieldOf(def.key))\"\n [options]=\"optionsOf(def)\"\n [placeholder]=\"def.placeholder ?? ''\"\n multiple\n />\n }\n @case ('checkbox') {\n <kpt-checkbox [formField]=\"$any(fieldOf(def.key))\" [label]=\"def.placeholder ?? ''\" />\n }\n @case ('tel') {\n <kpt-input\n [formField]=\"$any(fieldOf(def.key))\"\n [id]=\"controlId(def)\"\n type=\"tel\"\n [placeholder]=\"def.placeholder ?? ''\"\n />\n }\n @case ('email') {\n <kpt-input\n [formField]=\"$any(fieldOf(def.key))\"\n [id]=\"controlId(def)\"\n type=\"email\"\n [placeholder]=\"def.placeholder ?? ''\"\n />\n }\n @case ('url') {\n <kpt-input\n [formField]=\"$any(fieldOf(def.key))\"\n [id]=\"controlId(def)\"\n type=\"url\"\n [placeholder]=\"def.placeholder ?? 'https://'\"\n />\n }\n @case ('image') {\n <kpt-map-image-field [formField]=\"$any(fieldOf(def.key))\" />\n }\n @case ('hours') {\n <kpt-map-hours-field [formField]=\"$any(fieldOf(def.key))\" />\n }\n @default {\n <kpt-input\n [formField]=\"$any(fieldOf(def.key))\"\n [id]=\"controlId(def)\"\n [placeholder]=\"def.placeholder ?? ''\"\n />\n }\n }\n </kpt-form-field>\n }\n\n <div class=\"kpt-map-create__actions\">\n <kpt-button type=\"button\" variant=\"text\" (click)=\"cancelled.emit()\">\n {{ i18n.t('map.cancel') }}\n </kpt-button>\n <kpt-button type=\"submit\" [disabled]=\"formInvalid()\">{{ i18n.t('map.save') }}</kpt-button>\n </div>\n</form>\n}\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-create__form{display:flex;flex-direction:column;gap:var(--kpt-space-4);min-width:min(28rem,100%)}.kpt-map-create__coords{display:flex;gap:var(--kpt-space-4);margin:0;font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface-muted)}.kpt-map-create__coords strong{font-variant-numeric:tabular-nums;color:var(--kpt-color-on-surface)}.kpt-map-create__geo{margin:calc(var(--kpt-space-3) * -1) 0 0;font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface-muted)}.kpt-map-create__actions{position:sticky;bottom:calc(var(--kpt-space-6) * -1);display:flex;gap:var(--kpt-space-2);justify-content:flex-end;margin:var(--kpt-space-2) calc(var(--kpt-space-6) * -1) calc(var(--kpt-space-6) * -1);padding:var(--kpt-space-4) var(--kpt-space-6);background:var(--kpt-color-surface-raised);border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border)}}\n"] }]
1706
1103
  }], () => [], { category: [{ type: i0.Input, args: [{ isSignal: true, alias: "category", required: true }] }], at: [{ type: i0.Input, args: [{ isSignal: true, alias: "at", required: true }] }], geocoder: [{ type: i0.Input, args: [{ isSignal: true, alias: "geocoder", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] }); })();
1707
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapCreateDialog, { className: "KptMapCreateDialog", filePath: "lib/map-create-dialog.component.ts", lineNumber: 110 }); })();
1104
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapCreateDialog, { className: "KptMapCreateDialog", filePath: "lib/map-create-dialog.component.ts", lineNumber: 103 }); })();
1708
1105
 
1709
1106
  const _forTrack0$3 = ($index, $item) => $item.category.id;
1710
1107
  function KptMapLegend_For_1_Conditional_2_Template(rf, ctx) { if (rf & 1) {
@@ -2351,635 +1748,6 @@ function provideKptMapGeolocationStub(at, accuracy = 50) {
2351
1748
  return { provide: KPT_MAP_GEOLOCATION, useValue: new KptMapGeolocationStub(at, accuracy) };
2352
1749
  }
2353
1750
 
2354
- const LOAD_ERROR = 'kpt-map: nie udało się załadować Google Maps JavaScript API. ' +
2355
- 'Sprawdź klucz (`apiKey`), czy Maps JavaScript API jest włączone w projekcie ' +
2356
- 'i czy restrykcja domenowa klucza obejmuje ten adres.';
2357
- /** Nazwa globalnego callbacku, którego wymaga `loading=async`. */
2358
- const CALLBACK = '__kptGoogleMapsReady';
2359
- let modulePromise$1 = null;
2360
- /**
2361
- * Ładuje Google Maps JavaScript API raz na aplikację. Gdy `google.maps` już istnieje — bo
2362
- * aplikacja wstawiła skrypt sama albo stoi tu druga mapa — korzystamy z tego, co jest: drugie
2363
- * wczytanie Google i tak odrzuca, wypisując ostrzeżenie do konsoli.
2364
- *
2365
- * Publiczne, bo silnik nie jest jedynym, kto tego API potrzebuje. Geokoder Google **musi**
2366
- * iść przez SDK, a nie przez REST: web service `/maps/api/geocode/json` odrzuca klucze
2367
- * z restrykcją `Referer` („API keys with referer restrictions cannot be used with this API"),
2368
- * a to jedyna restrykcja, jaką da się sensownie nałożyć na klucz leżący w bundlu przeglądarki.
2369
- * Wywołania z załadowanego SDK autoryzują się tak samo jak sama mapa.
2370
- *
2371
- * Adaptera geokodera biblioteka nadal nie dostarcza — tylko drogę do API, którą i tak
2372
- * otwiera silnikowi. Powody stoją przy `provideKptGoogleMap` i w `llms.txt`.
2373
- */
2374
- async function loadGoogleMapsApi(options) {
2375
- if (typeof window === 'undefined' || typeof document === 'undefined') {
2376
- throw new Error('kpt-map: silnik Google Maps wymaga przeglądarki.');
2377
- }
2378
- const existing = window['google']?.maps;
2379
- if (existing?.importLibrary)
2380
- return existing;
2381
- modulePromise$1 ??= new Promise((resolve, reject) => {
2382
- const params = new URLSearchParams({
2383
- key: options.apiKey,
2384
- v: options.version ?? 'weekly',
2385
- loading: 'async',
2386
- callback: CALLBACK,
2387
- });
2388
- if (options.language)
2389
- params.set('language', options.language);
2390
- if (options.region)
2391
- params.set('region', options.region);
2392
- window[CALLBACK] = () => {
2393
- delete window[CALLBACK];
2394
- resolve(window['google'].maps);
2395
- };
2396
- const script = document.createElement('script');
2397
- script.src = `https://maps.googleapis.com/maps/api/js?${params}`;
2398
- script.async = true;
2399
- script.onerror = () => {
2400
- modulePromise$1 = null; // pozwól spróbować ponownie po poprawieniu klucza
2401
- reject(new Error(LOAD_ERROR));
2402
- };
2403
- document.head.append(script);
2404
- });
2405
- return modulePromise$1;
2406
- }
2407
- /** Fabryka silnika — każda mapa dostaje własną instancję. */
2408
- function createGoogleEngine(options) {
2409
- return new GoogleEngine(options);
2410
- }
2411
- class GoogleEngine {
2412
- constructor(options) {
2413
- this.options = options;
2414
- this.kind = 'google';
2415
- this.maps = null;
2416
- this.markerLib = null;
2417
- this.map = null;
2418
- this.host = null;
2419
- this.userMarker = null;
2420
- /** Krąg dokładności jest własnym `<div>`, nie `google.maps.Circle` — patrz `setUserPosition`. */
2421
- this.accuracyEl = null;
2422
- this.accuracyMeters = 0;
2423
- this.accuracyAt = null;
2424
- /** Znaczniki po `id` wraz z ich sprzątaniem — bez tego każdy przelicz przerysowywałby mapę. */
2425
- this.markers = new Map();
2426
- this.listeners = [];
2427
- this.handlers = {};
2428
- }
2429
- async init(host, opts) {
2430
- const maps = (this.maps = await loadGoogleMapsApi(this.options));
2431
- // Pod nazwą `Map` kryje się konstruktor Google, nie wbudowana kolekcja — stąd alias.
2432
- const [{ Map: GoogleMap }, markerLib] = await Promise.all([
2433
- maps.importLibrary('maps'),
2434
- maps.importLibrary('marker'),
2435
- ]);
2436
- this.markerLib = markerLib;
2437
- this.host = host;
2438
- this.map = new GoogleMap(host, {
2439
- center: { lat: opts.center.lat, lng: opts.center.lng },
2440
- zoom: opts.zoom,
2441
- minZoom: opts.minZoom ?? this.options.minZoom ?? 2,
2442
- maxZoom: opts.maxZoom ?? this.options.maxZoom ?? 21,
2443
- mapId: this.options.mapId,
2444
- // Kontrolki rysuje warstwa Angulara — te same przyciski w obu silnikach.
2445
- disableDefaultUI: true,
2446
- // POI Google przechwytywałyby kliknięcia przeznaczone dla mapy (menu „dodaj lokalizację").
2447
- clickableIcons: false,
2448
- scrollwheel: this.options.scrollWheelZoom ?? true,
2449
- restriction: opts.maxBounds
2450
- ? { latLngBounds: this.toLatLngBounds(opts.maxBounds), strictBounds: true }
2451
- : undefined,
2452
- });
2453
- // `opts.tileUrl` i `opts.attribution` są tu bez znaczenia: kafle i atrybucję daje Google,
2454
- // a jej zasłonięcie albo usunięcie łamie warunki korzystania z usługi.
2455
- this.on_(this.map, 'bounds_changed', () => this.emit('move'));
2456
- this.on_(this.map, 'idle', () => this.emit('moveend'));
2457
- this.on_(this.map, 'zoom_changed', () => {
2458
- // Krąg dokładności ma stały promień w metrach, więc przy każdym zoomie zmienia piksele.
2459
- this.resizeAccuracy();
2460
- this.emit('zoomend');
2461
- });
2462
- this.on_(this.map, 'click', (e) => this.emit('click', this.toPointerEvent(e)));
2463
- this.on_(this.map, 'contextmenu', (e) => {
2464
- // Menu przeglądarki zasłoniłoby własne „Dodaj lokalizację".
2465
- e.domEvent?.preventDefault();
2466
- this.emit('contextmenu', this.toPointerEvent(e));
2467
- });
2468
- // Pierwszy `idle` znaczy, że mapa zna już swój rozmiar i zasięg.
2469
- await new Promise((resolve) => {
2470
- this.maps.event.addListenerOnce(this.map, 'idle', () => resolve());
2471
- });
2472
- }
2473
- destroy() {
2474
- for (const { marker, dispose } of this.markers.values()) {
2475
- dispose();
2476
- marker.map = null;
2477
- }
2478
- this.markers.clear();
2479
- if (this.userMarker)
2480
- this.userMarker.map = null;
2481
- this.userMarker = null;
2482
- this.accuracyEl = null;
2483
- this.accuracyAt = null;
2484
- for (const listener of this.listeners)
2485
- listener.remove?.();
2486
- this.listeners.length = 0;
2487
- // Google nie ma odpowiednika `map.remove()` — kontener czyścimy sami.
2488
- if (this.map)
2489
- this.maps?.event.clearInstanceListeners(this.map);
2490
- this.host?.replaceChildren();
2491
- this.map = null;
2492
- this.host = null;
2493
- }
2494
- setView(center, zoom, animate = true) {
2495
- if (!this.map)
2496
- return;
2497
- const at = { lat: center.lat, lng: center.lng };
2498
- if (animate) {
2499
- this.map.panTo(at);
2500
- this.map.setZoom(zoom);
2501
- }
2502
- else {
2503
- this.map.moveCamera({ center: at, zoom });
2504
- }
2505
- }
2506
- fitBounds(bounds, paddingPx = 0) {
2507
- this.map?.fitBounds(this.toLatLngBounds(bounds), paddingPx);
2508
- }
2509
- panBy(offset) {
2510
- this.map?.panBy(offset.x, offset.y);
2511
- }
2512
- getCenter() {
2513
- const c = this.map.getCenter();
2514
- return { lat: c.lat(), lng: c.lng() };
2515
- }
2516
- getZoom() {
2517
- return this.map.getZoom();
2518
- }
2519
- /**
2520
- * Zasięg widoku. `getBounds()` bywa `undefined`, zanim mapa policzy pierwszy układ —
2521
- * liczymy go wtedy z rdzenia, zamiast oddawać `null` przez kontrakt, który go nie przewiduje.
2522
- */
2523
- getBounds() {
2524
- const b = this.map?.getBounds();
2525
- if (b) {
2526
- const ne = b.getNorthEast();
2527
- const sw = b.getSouthWest();
2528
- return { south: sw.lat(), west: sw.lng(), north: ne.lat(), east: ne.lng() };
2529
- }
2530
- const size = this.getSize();
2531
- const nw = this.unproject({ x: 0, y: 0 });
2532
- const se = this.unproject({ x: size.width, y: size.height });
2533
- return { south: se.lat, west: nw.lng, north: nw.lat, east: se.lng };
2534
- }
2535
- getSize() {
2536
- const rect = this.host?.getBoundingClientRect();
2537
- return { width: rect?.width ?? 0, height: rect?.height ?? 0 };
2538
- }
2539
- /**
2540
- * Współrzędne → piksele kontenera.
2541
- *
2542
- * Świadomie **nie** przez `map.getProjection()`: ten zwraca `null` do pierwszego
2543
- * `projection_changed` i tak czy owak wymaga ręcznego przeliczenia na piksele kontenera.
2544
- * Rdzeń (`core/geo.ts`) ma dokładnie tę matematykę — tę samą, na której stoi grupowanie
2545
- * i adapter Leafleta — więc popup ląduje piksel w piksel tak samo w obu silnikach.
2546
- * Działa też dla zoomu ułamkowego, którego Google używa przy gestach.
2547
- */
2548
- project(at) {
2549
- const zoom = this.getZoom();
2550
- const size = this.getSize();
2551
- const origin = project(this.getCenter(), zoom);
2552
- const point = project(at, zoom);
2553
- return {
2554
- x: point.x - origin.x + size.width / 2,
2555
- y: point.y - origin.y + size.height / 2,
2556
- };
2557
- }
2558
- unproject(point) {
2559
- const zoom = this.getZoom();
2560
- const size = this.getSize();
2561
- const origin = project(this.getCenter(), zoom);
2562
- return unproject({ x: origin.x + point.x - size.width / 2, y: origin.y + point.y - size.height / 2 }, zoom);
2563
- }
2564
- /**
2565
- * Wymienia zestaw znaczników różnicowo — znaczniki o niezmienionym `id` zostają na miejscu,
2566
- * inaczej każdy przelicz gasiłby animacje i migotał.
2567
- */
2568
- setMarkers(next) {
2569
- if (!this.map || !this.markerLib)
2570
- return;
2571
- const { AdvancedMarkerElement } = this.markerLib;
2572
- const seen = new Set();
2573
- for (const m of next) {
2574
- seen.add(m.id);
2575
- const existing = this.markers.get(m.id);
2576
- if (existing) {
2577
- existing.marker.position = { lat: m.at.lat, lng: m.at.lng };
2578
- if (m.zIndex !== undefined)
2579
- existing.marker.zIndex = m.zIndex;
2580
- this.fillMarker(existing.inner, m);
2581
- continue;
2582
- }
2583
- const { anchor, inner } = this.buildMarkerContent(m);
2584
- const marker = new AdvancedMarkerElement({
2585
- map: this.map,
2586
- position: { lat: m.at.lat, lng: m.at.lng },
2587
- content: anchor,
2588
- zIndex: m.zIndex ?? 0,
2589
- });
2590
- /*
2591
- * Kliknięcie łapiemy listenerem DOM na własnej treści, a nie zdarzeniem `gmp-click`
2592
- * z `gmpClickable: true`. Powód jest ten sam, dla którego adapter Leafleta woła
2593
- * `L.DomEvent.stopPropagation`: bez zatrzymania propagacji to samo kliknięcie doszłoby
2594
- * do mapy i `onMapClick()` zamknąłby popup w tej samej klatce, w której go otwieramy.
2595
- * Przy okazji odpada zależność od tego, jak `gmp-click` zachowuje się w danej wersji API.
2596
- * Dostępność bez zmian — etykietę niesie `aria-label` w HTML-u ikony.
2597
- */
2598
- const onClick = (event) => {
2599
- event.stopPropagation();
2600
- this.emit('markerclick', m.id, this.fromDomEvent(event, m.at));
2601
- };
2602
- inner.addEventListener('click', onClick);
2603
- this.markers.set(m.id, {
2604
- marker,
2605
- inner,
2606
- dispose: () => inner.removeEventListener('click', onClick),
2607
- });
2608
- }
2609
- for (const [id, entry] of this.markers) {
2610
- if (seen.has(id))
2611
- continue;
2612
- entry.dispose();
2613
- entry.marker.map = null;
2614
- this.markers.delete(id);
2615
- }
2616
- }
2617
- setUserPosition(position, html) {
2618
- if (!this.map || !this.markerLib)
2619
- return;
2620
- const { AdvancedMarkerElement } = this.markerLib;
2621
- if (!position) {
2622
- if (this.userMarker)
2623
- this.userMarker.map = null;
2624
- this.userMarker = null;
2625
- this.accuracyEl = null;
2626
- this.accuracyAt = null;
2627
- return;
2628
- }
2629
- const at = { lat: position.at.lat, lng: position.at.lng };
2630
- this.accuracyMeters = position.accuracy;
2631
- this.accuracyAt = position.at;
2632
- if (this.userMarker) {
2633
- this.userMarker.position = at;
2634
- this.userMarker.content = this.buildUserContent(html);
2635
- }
2636
- else {
2637
- this.userMarker = new AdvancedMarkerElement({
2638
- map: this.map,
2639
- position: at,
2640
- content: this.buildUserContent(html),
2641
- // Nad znacznikami lokalizacji — własne położenie ma być zawsze widoczne.
2642
- zIndex: 1000,
2643
- });
2644
- }
2645
- this.resizeAccuracy();
2646
- }
2647
- /** Google dostarcza własne kafle i własną atrybucję — nie ma czego podmieniać. */
2648
- setTileSource() {
2649
- // celowo puste
2650
- }
2651
- setMaxBounds(bounds) {
2652
- this.map?.setOptions({
2653
- restriction: bounds ? { latLngBounds: this.toLatLngBounds(bounds), strictBounds: true } : null,
2654
- });
2655
- }
2656
- /** Google sam reaguje na zmianę rozmiaru kontenera — nie ma odpowiednika `invalidateSize`. */
2657
- invalidateSize() {
2658
- // celowo puste
2659
- }
2660
- on(event, handler) {
2661
- (this.handlers[event] ??= []).push(handler);
2662
- }
2663
- // ── prywatne ────────────────────────────────────────────────────────────────
2664
- emit(event, ...args) {
2665
- for (const handler of this.handlers[event] ?? []) {
2666
- handler(...args);
2667
- }
2668
- }
2669
- /** Rejestruje listener Google i zapamiętuje go do sprzątania w `destroy()`. */
2670
- on_(target, event, handler) {
2671
- this.listeners.push(target.addListener(event, handler));
2672
- }
2673
- /**
2674
- * Opakowanie znacznika. `AdvancedMarkerElement` kotwiczy treść **środkiem dolnej krawędzi**,
2675
- * a kontrakt daje kotwicę w pikselach od lewego górnego rogu ikony. Element zewnętrzny ma
2676
- * więc zerowe wymiary — jego „środek dolnej krawędzi" wypada dokładnie we współrzędnych —
2677
- * a wewnętrzny przesuwa ikonę o żądaną kotwicę.
2678
- */
2679
- buildMarkerContent(m) {
2680
- const anchor = document.createElement('div');
2681
- anchor.style.cssText = 'position:relative;width:0;height:0';
2682
- const inner = document.createElement('div');
2683
- inner.className = 'kpt-map__icon';
2684
- inner.style.position = 'absolute';
2685
- anchor.append(inner);
2686
- this.fillMarker(inner, m);
2687
- return { anchor, inner };
2688
- }
2689
- fillMarker(inner, m) {
2690
- inner.style.left = `${-m.anchor.x}px`;
2691
- inner.style.top = `${-m.anchor.y}px`;
2692
- inner.style.width = `${m.size.width}px`;
2693
- inner.style.height = `${m.size.height}px`;
2694
- // HTML pochodzi z `marker-html.ts` — z rdzenia biblioteki, nie od użytkownika.
2695
- inner.innerHTML = m.html;
2696
- }
2697
- /**
2698
- * Znacznik użytkownika i krąg dokładności w jednym elemencie.
2699
- *
2700
- * Krąg jest `<div>`-em z klasą `kpt-map__accuracy`, a nie `google.maps.Circle`: Circle
2701
- * przyjmuje kolory w opcjach, a kontrakt repo wymaga stylowania tokenami `var(--kpt-*)`.
2702
- * Średnicę liczy `metersPerPixel()` z rdzenia, więc jest wspólna dla obu silników.
2703
- */
2704
- buildUserContent(html) {
2705
- const anchor = document.createElement('div');
2706
- anchor.style.cssText = 'position:relative;width:0;height:0';
2707
- const circle = document.createElement('div');
2708
- circle.className = 'kpt-map__accuracy';
2709
- circle.style.cssText = 'position:absolute;border-radius:50%;pointer-events:none';
2710
- anchor.append(circle);
2711
- this.accuracyEl = circle;
2712
- const inner = document.createElement('div');
2713
- inner.className = 'kpt-map__icon';
2714
- inner.style.cssText = 'position:absolute;left:-12px;top:-12px;width:24px;height:24px';
2715
- inner.innerHTML = html;
2716
- anchor.append(inner);
2717
- this.resizeAccuracy();
2718
- return anchor;
2719
- }
2720
- resizeAccuracy() {
2721
- const el = this.accuracyEl;
2722
- const at = this.accuracyAt;
2723
- if (!el || !at || !this.map)
2724
- return;
2725
- const diameter = (2 * this.accuracyMeters) / metersPerPixel(at.lat, this.getZoom());
2726
- el.style.width = `${diameter}px`;
2727
- el.style.height = `${diameter}px`;
2728
- el.style.left = `${-diameter / 2}px`;
2729
- el.style.top = `${-diameter / 2}px`;
2730
- }
2731
- toPointerEvent(e) {
2732
- const at = { lat: e.latLng.lat(), lng: e.latLng.lng() };
2733
- return { at, point: this.toContainerPoint(e.domEvent), originalEvent: e.domEvent };
2734
- }
2735
- fromDomEvent(event, at) {
2736
- return { at, point: this.toContainerPoint(event), originalEvent: event };
2737
- }
2738
- /** Google podaje zdarzenie DOM bez pozycji w kontenerze — liczymy ją z prostokąta hosta. */
2739
- toContainerPoint(event) {
2740
- const rect = this.host?.getBoundingClientRect();
2741
- if (!event || !rect)
2742
- return { x: 0, y: 0 };
2743
- return { x: event.clientX - rect.left, y: event.clientY - rect.top };
2744
- }
2745
- toLatLngBounds(b) {
2746
- return new this.maps.LatLngBounds({ lat: b.south, lng: b.west }, { lat: b.north, lng: b.east });
2747
- }
2748
- }
2749
-
2750
- const MODULE_LOAD_ERROR = 'kpt-map: nie udało się załadować biblioteki `leaflet`. ' +
2751
- 'To zależność opcjonalna — zainstaluj ją w aplikacji: `pnpm add leaflet` ' +
2752
- '(oraz dołącz `leaflet/dist/leaflet.css`).';
2753
- let modulePromise = null;
2754
- /**
2755
- * Ładuje Leaflet raz na aplikację. Leaflet 1.9 jest paczką UMD/CJS bez pola `exports`,
2756
- * więc pod bundlerem trafia raz jako `default`, a raz jako sam namespace — stąd interop.
2757
- */
2758
- async function loadLeaflet() {
2759
- modulePromise ??= import('leaflet')
2760
- .then((mod) => mod?.default ?? mod)
2761
- .catch((cause) => {
2762
- modulePromise = null; // pozwól spróbować ponownie po doinstalowaniu paczki
2763
- throw new Error(MODULE_LOAD_ERROR, { cause });
2764
- });
2765
- return modulePromise;
2766
- }
2767
- /** Fabryka silnika — każda mapa dostaje własną instancję. */
2768
- function createLeafletEngine(options = {}) {
2769
- return new LeafletEngine(options);
2770
- }
2771
- class LeafletEngine {
2772
- constructor(options) {
2773
- this.options = options;
2774
- this.kind = 'leaflet';
2775
- this.L = null;
2776
- this.map = null;
2777
- this.tiles = null;
2778
- this.userMarker = null;
2779
- this.userCircle = null;
2780
- /** Znaczniki po `id` — bez tego każdy przelicz przerysowywałby całą mapę. */
2781
- this.markers = new Map();
2782
- this.handlers = {};
2783
- }
2784
- async init(host, opts) {
2785
- const L = (this.L = await loadLeaflet());
2786
- this.map = new L.Map(host, {
2787
- center: [opts.center.lat, opts.center.lng],
2788
- zoom: opts.zoom,
2789
- minZoom: opts.minZoom ?? this.options.minZoom ?? 2,
2790
- maxZoom: opts.maxZoom ?? this.options.maxZoom ?? 19,
2791
- // Kontrolki rysuje warstwa Angulara — domyślnych nie da się otokenizować
2792
- // bez walki z niewarstwowanym `leaflet.css`.
2793
- zoomControl: false,
2794
- attributionControl: true,
2795
- inertia: this.options.inertia ?? true,
2796
- scrollWheelZoom: this.options.scrollWheelZoom ?? true,
2797
- maxBounds: opts.maxBounds ? this.toLatLngBounds(opts.maxBounds) : undefined,
2798
- // Bez tego mapa przy `maxBounds` daje się odciągnąć i sprężynuje.
2799
- maxBoundsViscosity: opts.maxBounds ? 1 : 0,
2800
- });
2801
- this.tiles = new L.TileLayer(opts.tileUrl, {
2802
- attribution: opts.attribution,
2803
- maxZoom: opts.maxZoom ?? this.options.maxZoom ?? 19,
2804
- }).addTo(this.map);
2805
- this.map.on('move', () => this.emit('move'));
2806
- this.map.on('moveend', () => this.emit('moveend'));
2807
- this.map.on('zoomend', () => this.emit('zoomend'));
2808
- this.map.on('click', (e) => this.emit('click', this.toPointerEvent(e)));
2809
- this.map.on('contextmenu', (e) => {
2810
- // Menu przeglądarki zasłoniłoby własne „Dodaj lokalizację".
2811
- L.DomEvent.preventDefault(e.originalEvent);
2812
- this.emit('contextmenu', this.toPointerEvent(e));
2813
- });
2814
- }
2815
- destroy() {
2816
- this.markers.clear();
2817
- this.userMarker = null;
2818
- this.userCircle = null;
2819
- this.tiles = null;
2820
- this.map?.remove();
2821
- this.map = null;
2822
- }
2823
- setView(center, zoom, animate = true) {
2824
- this.map?.setView([center.lat, center.lng], zoom, { animate });
2825
- }
2826
- fitBounds(bounds, paddingPx = 0) {
2827
- this.map?.fitBounds(this.toLatLngBounds(bounds), {
2828
- padding: [paddingPx, paddingPx],
2829
- maxZoom: this.options.maxZoom ?? 19,
2830
- });
2831
- }
2832
- panBy(offset) {
2833
- this.map?.panBy([offset.x, offset.y], { animate: true });
2834
- }
2835
- getCenter() {
2836
- const c = this.map.getCenter();
2837
- return { lat: c.lat, lng: c.lng };
2838
- }
2839
- getZoom() {
2840
- return this.map.getZoom();
2841
- }
2842
- getBounds() {
2843
- const b = this.map.getBounds();
2844
- return { south: b.getSouth(), west: b.getWest(), north: b.getNorth(), east: b.getEast() };
2845
- }
2846
- getSize() {
2847
- const s = this.map.getSize();
2848
- return { width: s.x, height: s.y };
2849
- }
2850
- project(at) {
2851
- const p = this.map.latLngToContainerPoint([at.lat, at.lng]);
2852
- return { x: p.x, y: p.y };
2853
- }
2854
- unproject(point) {
2855
- const ll = this.map.containerPointToLatLng([point.x, point.y]);
2856
- return { lat: ll.lat, lng: ll.lng };
2857
- }
2858
- /**
2859
- * Wymienia zestaw znaczników różnicowo. Znaczniki, których `id` się nie zmieniło,
2860
- * zostają na miejscu — inaczej każdy przelicz gasiłby animacje i migotał.
2861
- */
2862
- setMarkers(next) {
2863
- if (!this.map || !this.L)
2864
- return;
2865
- const L = this.L;
2866
- const seen = new Set();
2867
- for (const m of next) {
2868
- seen.add(m.id);
2869
- const existing = this.markers.get(m.id);
2870
- const icon = L.divIcon({
2871
- html: m.html,
2872
- className: 'kpt-map__icon',
2873
- iconSize: [m.size.width, m.size.height],
2874
- iconAnchor: [m.anchor.x, m.anchor.y],
2875
- });
2876
- if (existing) {
2877
- existing.setLatLng([m.at.lat, m.at.lng]);
2878
- existing.setIcon(icon);
2879
- if (m.zIndex !== undefined)
2880
- existing.setZIndexOffset(m.zIndex);
2881
- continue;
2882
- }
2883
- const marker = new L.Marker([m.at.lat, m.at.lng], {
2884
- icon,
2885
- // Znacznik niesie własny `aria-label` w HTML-u ikony; pusty `alt`
2886
- // powstrzymuje Leaflet przed dorzuceniem drugiej etykiety.
2887
- alt: '',
2888
- keyboard: false,
2889
- zIndexOffset: m.zIndex ?? 0,
2890
- });
2891
- marker.on('click', (e) => {
2892
- L.DomEvent.stopPropagation(e);
2893
- this.emit('markerclick', m.id, this.toPointerEvent(e));
2894
- });
2895
- marker.addTo(this.map);
2896
- this.markers.set(m.id, marker);
2897
- }
2898
- for (const [id, marker] of this.markers) {
2899
- if (seen.has(id))
2900
- continue;
2901
- marker.remove();
2902
- this.markers.delete(id);
2903
- }
2904
- }
2905
- setUserPosition(position, html) {
2906
- if (!this.map || !this.L)
2907
- return;
2908
- const L = this.L;
2909
- if (!position) {
2910
- this.userMarker?.remove();
2911
- this.userCircle?.remove();
2912
- this.userMarker = null;
2913
- this.userCircle = null;
2914
- return;
2915
- }
2916
- const at = [position.at.lat, position.at.lng];
2917
- const icon = L.divIcon({ html, className: 'kpt-map__icon', iconSize: [24, 24], iconAnchor: [12, 12] });
2918
- if (this.userMarker) {
2919
- this.userMarker.setLatLng(at);
2920
- this.userMarker.setIcon(icon);
2921
- }
2922
- else {
2923
- // Nad znacznikami lokalizacji — własne położenie ma być zawsze widoczne.
2924
- this.userMarker = new L.Marker(at, { icon, alt: '', keyboard: false, zIndexOffset: 1000 }).addTo(this.map);
2925
- }
2926
- // Promień w metrach; L.Circle sam przelicza go na piksele przy każdym zoomie.
2927
- if (this.userCircle) {
2928
- this.userCircle.setLatLng(at);
2929
- this.userCircle.setRadius(position.accuracy);
2930
- }
2931
- else {
2932
- this.userCircle = new L.Circle(at, {
2933
- radius: position.accuracy,
2934
- className: 'kpt-map__accuracy',
2935
- interactive: false,
2936
- // Kolory idą z klasy CSS (tokeny), nie z opcji Leafleta.
2937
- stroke: true,
2938
- weight: 1,
2939
- }).addTo(this.map);
2940
- }
2941
- }
2942
- setTileSource(tileUrl, attribution) {
2943
- if (!this.map || !this.L)
2944
- return;
2945
- this.tiles?.remove();
2946
- this.tiles = new this.L.TileLayer(tileUrl, {
2947
- attribution,
2948
- maxZoom: this.options.maxZoom ?? 19,
2949
- }).addTo(this.map);
2950
- // Kafle wchodzą pod znaczniki — bez tego nowa warstwa przykryłaby je.
2951
- this.tiles.bringToBack();
2952
- }
2953
- setMaxBounds(bounds) {
2954
- if (!this.map)
2955
- return;
2956
- this.map.setMaxBounds(bounds ? this.toLatLngBounds(bounds) : null);
2957
- this.map.options.maxBoundsViscosity = bounds ? 1 : 0;
2958
- }
2959
- invalidateSize() {
2960
- // `animate: false` — to reakcja na zmianę layoutu, nie gest użytkownika.
2961
- this.map?.invalidateSize({ animate: false });
2962
- }
2963
- on(event, handler) {
2964
- (this.handlers[event] ??= []).push(handler);
2965
- }
2966
- emit(event, ...args) {
2967
- for (const handler of this.handlers[event] ?? []) {
2968
- handler(...args);
2969
- }
2970
- }
2971
- toPointerEvent(e) {
2972
- return {
2973
- at: { lat: e.latlng.lat, lng: e.latlng.lng },
2974
- point: { x: e.containerPoint.x, y: e.containerPoint.y },
2975
- originalEvent: e.originalEvent,
2976
- };
2977
- }
2978
- toLatLngBounds(b) {
2979
- return new this.L.LatLngBounds([b.south, b.west], [b.north, b.east]);
2980
- }
2981
- }
2982
-
2983
1751
  /**
2984
1752
  * Fabryka silnika mapy. Fabryka, a nie gotowa instancja — każda mapa na stronie
2985
1753
  * potrzebuje własnego obiektu, a provider jest jeden.
@@ -3957,61 +2725,7 @@ class KptMap {
3957
2725
  '[attr.data-engine]': 'engineKind()',
3958
2726
  }, template: "<div class=\"kpt-map__viewport\">\n <!-- Kontener silnika. Wszystko poni\u017Cej to warstwa Angulara rozpi\u0119ta nad map\u0105. -->\n <div #canvas class=\"kpt-map__canvas\"></div>\n\n <div class=\"kpt-map__controls\" role=\"group\" [attr.aria-label]=\"i18n.t('map.legend')\">\n <button\n class=\"kpt-map__control\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('map.zoomIn')\"\n [title]=\"i18n.t('map.zoomIn')\"\n [disabled]=\"!ready()\"\n (click)=\"zoomBy(1)\"\n >\n <span aria-hidden=\"true\">+</span>\n </button>\n <button\n class=\"kpt-map__control\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('map.zoomOut')\"\n [title]=\"i18n.t('map.zoomOut')\"\n [disabled]=\"!ready()\"\n (click)=\"zoomBy(-1)\"\n >\n <span aria-hidden=\"true\">\u2212</span>\n </button>\n @if (showUserLocation()) {\n <button\n class=\"kpt-map__control kpt-map__control--locate\"\n type=\"button\"\n [attr.aria-label]=\"locationBlocked() ? i18n.t('map.locationDenied') : i18n.t('map.locateMe')\"\n [title]=\"locationBlocked() ? i18n.t('map.locationDenied') : i18n.t('map.locateMe')\"\n [disabled]=\"!userPosition()\"\n (click)=\"centreOnUser()\"\n >\n <span class=\"kpt-map__crosshair\" aria-hidden=\"true\"></span>\n </button>\n }\n </div>\n\n <!--\n Wyszukiwarka adres\u00F3w. Renderowana tylko wtedy, gdy port ma czym szuka\u0107.\n\n Pozycjonuje j\u0105 OPAKOWANIE, a nie klasa postawiona wprost na <kpt-map-search> \u2014\n pow\u00F3d stoi przy regule .kpt-map__search w map.component.scss.\n -->\n @if (searchEnabled()) {\n <div class=\"kpt-map__search\">\n <kpt-map-search\n [geocoder]=\"activeGeocoder()\"\n [placeholder]=\"searchPlaceholder()\"\n [minLength]=\"searchOptions().minLength ?? 3\"\n [debounce]=\"searchOptions().debounce ?? 300\"\n [limit]=\"searchOptions().limit ?? 5\"\n [viewbox]=\"searchViewbox()\"\n [countryCodes]=\"searchCountryCodes()\"\n (selected)=\"onSearchSelect($event)\"\n (cleared)=\"clearSearchPin()\"\n />\n </div>\n }\n\n <!-- Menu pod prawym przyciskiem: wyb\u00F3r kategorii nowej lokalizacji. -->\n @if (allowCreate() && contextTarget()) {\n @if (contextPosition(); as position) {\n <div\n class=\"kpt-map__context\"\n role=\"menu\"\n [attr.aria-label]=\"i18n.t('map.addLocation')\"\n [style.--_kpt-map-x.px]=\"position.x\"\n [style.--_kpt-map-y.px]=\"position.y\"\n >\n <p class=\"kpt-map__context-title\">{{ i18n.t('map.chooseCategory') }}</p>\n @for (category of creatableCategories(); track category.id) {\n <button class=\"kpt-map__context-item\" type=\"button\" role=\"menuitem\" (click)=\"startCreate(category)\">\n <span class=\"kpt-map__context-dot\" aria-hidden=\"true\"></span>\n {{ category.label }}\n </button>\n } @empty {\n <p class=\"kpt-map__context-empty\">{{ i18n.t('map.empty') }}</p>\n }\n </div>\n }\n }\n\n <!-- Popup szczeg\u00F3\u0142\u00F3w. Renderowany przez Angulara, pozycjonowany z engine.project(). -->\n @if (popup(); as state) {\n @if (popupPosition(); as position) {\n <div\n #popupEl\n class=\"kpt-map__popup\"\n role=\"dialog\"\n [attr.data-layout]=\"popupLayout()\"\n [attr.aria-label]=\"state.location.title\"\n [style.--_kpt-map-x.px]=\"position.x\"\n [style.--_kpt-map-y.px]=\"position.y\"\n >\n <!-- W wariancie `card` krzy\u017Cyk daje karta, wi\u0119c wbudowany by\u0142by drugim. -->\n @if (popupLayout() === 'plain') {\n <button\n class=\"kpt-map__popup-close\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('common.close')\"\n (click)=\"closePopup()\"\n >\n <span aria-hidden=\"true\">\u00D7</span>\n </button>\n }\n\n @if (state.group) {\n <!-- Grupa punkt\u00F3w o tej samej wsp\u00F3\u0142rz\u0119dnej \u2014 zoom jej nie rozdzieli. -->\n <p class=\"kpt-map__popup-group\">{{ i18n.t('map.clusterItems') }}</p>\n <div class=\"kpt-map__popup-tabs\" role=\"tablist\">\n @for (item of state.group.items; track item.id) {\n <button\n class=\"kpt-map__popup-tab\"\n type=\"button\"\n role=\"tab\"\n [attr.aria-selected]=\"item.id === state.location.id\"\n (click)=\"selectFromGroup(item)\"\n >\n {{ item.title }}\n </button>\n }\n </div>\n }\n\n <div class=\"kpt-map__popup-body\">\n @if (popupTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl.templateRef\"\n [ngTemplateOutletContext]=\"{\n $implicit: state.location,\n data: state.location.data,\n category: popupCategory(),\n distance: popupDistance(),\n close: closePopupFn\n }\"\n />\n } @else {\n <strong class=\"kpt-map__popup-title\">{{ state.location.title }}</strong>\n @if (popupCategory(); as category) {\n <p class=\"kpt-map__popup-category\">{{ category.label }}</p>\n }\n }\n </div>\n </div>\n }\n }\n</div>\n\n@if (legend()) {\n <kpt-map-legend\n class=\"kpt-map__legend\"\n [categories]=\"categories()\"\n [(hidden)]=\"hiddenCategories\"\n [counts]=\"counts()\"\n />\n}\n\n<!-- Dialog dodawania: schema z kategorii, a w\u0142asny szablon ma pierwsze\u0144stwo. -->\n<kpt-dialog [(open)]=\"createOpen\" [header]=\"i18n.t('map.addLocation')\">\n @if (createFor(); as target) {\n @if (createFormTemplate(); as tpl) {\n @if (createFormApi(); as api) {\n <ng-container\n [ngTemplateOutlet]=\"tpl.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: api, at: target.at, category: target.category }\"\n />\n }\n } @else {\n <kpt-map-create-dialog\n [geocoder]=\"activeGeocoder()\"\n [category]=\"target.category\"\n [at]=\"target.at\"\n (save)=\"emitCreate($event)\"\n (cancelled)=\"closeCreate()\"\n />\n }\n }\n</kpt-dialog>\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map{--kpt-map-height: 480px;display:flex;flex-direction:column;gap:var(--kpt-space-3);width:100%;isolation:isolate}.kpt-map__viewport{position:relative;height:var(--kpt-map-height);overflow:hidden;background:var(--kpt-map-bg, var(--kpt-color-surface-sunken));border:var(--kpt-border-width-thin) solid var(--kpt-map-border, var(--kpt-color-border));border-radius:var(--kpt-map-radius, var(--kpt-radius-lg))}.kpt-map__canvas{width:100%;height:100%}.kpt-map__controls{position:absolute;top:var(--kpt-space-3);left:var(--kpt-space-3);z-index:2;display:flex;flex-direction:column;gap:var(--kpt-space-1)}.kpt-map__control{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;font-size:var(--kpt-font-size-lg);line-height:1;color:var(--kpt-color-on-surface);cursor:pointer;background:var(--kpt-color-surface-raised);border:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-radius:var(--kpt-radius-md);box-shadow:var(--kpt-elevation-1)}.kpt-map__control:hover:not(:disabled){background:var(--kpt-color-surface-hover)}.kpt-map__control:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:2px}.kpt-map__control:disabled{color:var(--kpt-color-on-surface-muted);cursor:not-allowed;opacity:.6}.kpt-map__search{position:absolute;top:var(--kpt-space-3);right:var(--kpt-space-3);z-index:5;max-width:calc(100% - 2rem - var(--kpt-space-3) * 3)}.kpt-map__crosshair{position:relative;width:.875rem;height:.875rem;border:var(--kpt-border-width-medium) solid currentcolor;border-radius:var(--kpt-radius-full)}.kpt-map__crosshair:after{position:absolute;inset:50% auto auto 50%;width:.25rem;height:.25rem;content:\"\";background:currentcolor;border-radius:var(--kpt-radius-full);translate:-50% -50%}.kpt-map__marker{position:relative;display:block;width:100%;height:100%;color:var(--_kpt-map-marker-fg, var(--kpt-color-text-inverse))}.kpt-map__marker-shape{display:block;width:100%;height:100%;fill:var(--_kpt-map-marker-bg, var(--kpt-color-muted));stroke:var(--kpt-color-surface-raised);stroke-width:1.5;filter:drop-shadow(0 1px 2px rgba(0,0,0,.25))}.kpt-map__marker-glyph{position:absolute;display:grid;place-items:center;font-size:.875rem}.kpt-map__marker-glyph svg{width:1em;height:1em}.kpt-map__marker--pin .kpt-map__marker-glyph{inset:0 0 auto;height:76%}.kpt-map__marker--dot,.kpt-map__marker--square{display:grid;place-items:center;background:var(--_kpt-map-marker-bg, var(--kpt-color-muted));border:var(--kpt-border-width-medium) solid var(--kpt-color-surface-raised);box-shadow:var(--kpt-elevation-1)}.kpt-map__marker--dot{border-radius:var(--kpt-radius-full)}.kpt-map__marker--square{border-radius:var(--kpt-radius-sm)}.kpt-map__marker--dot .kpt-map__marker-glyph,.kpt-map__marker--square .kpt-map__marker-glyph{position:static}.kpt-map__marker--selected{z-index:1}.kpt-map__marker--selected .kpt-map__marker-shape{stroke:var(--kpt-color-focus-ring);stroke-width:2.5}.kpt-map__cluster{display:grid;place-items:center;width:100%;height:100%;font-weight:var(--kpt-font-weight-semibold);font-variant-numeric:tabular-nums;color:var(--_kpt-map-cluster-fg, var(--kpt-color-primary-contrast));background:var(--_kpt-map-cluster-bg, var(--kpt-map-cluster-bg, var(--kpt-color-primary)));border:var(--kpt-border-width-medium) solid var(--kpt-color-surface-raised);border-radius:var(--kpt-radius-full);box-shadow:var(--kpt-elevation-2)}.kpt-map__cluster--sm{font-size:var(--kpt-font-size-xs);scale:.78}.kpt-map__cluster--md{font-size:var(--kpt-font-size-sm);scale:.9}.kpt-map__cluster--lg{font-size:var(--kpt-font-size-sm);scale:1}.kpt-map__user-marker{display:grid;place-items:center;width:100%;height:100%;font-size:.875rem;color:var(--kpt-color-info-contrast);background:var(--kpt-color-info);border:var(--kpt-border-width-medium) solid var(--kpt-color-surface-raised);border-radius:var(--kpt-radius-full);box-shadow:var(--kpt-elevation-2)}.kpt-map__user-marker svg{width:1em;height:1em}.kpt-map__popup{position:absolute;top:var(--_kpt-map-y, 0);left:var(--_kpt-map-x, 0);z-index:3;max-width:min(var(--kpt-map-popup-width, 20rem),100% - var(--kpt-space-6));padding:var(--kpt-space-4);color:var(--kpt-color-on-surface);background:var(--kpt-map-popup-bg, var(--kpt-color-surface-raised));border:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-radius:var(--kpt-radius-lg);box-shadow:var(--kpt-map-popup-shadow, var(--kpt-elevation-3));translate:-50% calc(-100% - var(--kpt-space-3))}.kpt-map__popup:after{position:absolute;top:100%;left:50%;width:.75rem;height:.75rem;content:\"\";background:inherit;border-right:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-bottom:var(--kpt-border-width-thin) solid var(--kpt-color-border);rotate:45deg;translate:-50% -50%}.kpt-map__popup[data-layout=card]{max-width:min(var(--kpt-map-popup-width-card, 26rem),100% - var(--kpt-space-6));width:min(var(--kpt-map-popup-width-card, 26rem),100% - var(--kpt-space-6));max-height:min(var(--kpt-map-popup-max-height, 32rem),100% - var(--kpt-space-8));padding:0;display:flex;flex-direction:column}.kpt-map__popup[data-layout=card] .kpt-map__popup-body{display:flex;min-height:0;padding:0;overflow:hidden}.kpt-map__popup[data-layout=card] .kpt-map__popup-body>*{flex:1 1 auto;min-height:0}.kpt-map__popup[data-layout=card] .kpt-map__popup-group,.kpt-map__popup[data-layout=card] .kpt-map__popup-tabs{margin-inline:var(--kpt-space-4)}.kpt-map__popup[data-layout=card] .kpt-map__popup-group{margin-top:var(--kpt-space-3)}.kpt-map__popup-close{position:absolute;top:var(--kpt-space-2);right:var(--kpt-space-2);display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;font-size:var(--kpt-font-size-lg);line-height:1;color:var(--kpt-color-on-surface-muted);cursor:pointer;background:transparent;border:none;border-radius:var(--kpt-radius-full)}.kpt-map__popup-close:hover{background:var(--kpt-color-surface-hover)}.kpt-map__popup-close:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}.kpt-map__popup-body{padding-right:var(--kpt-space-5);font-size:var(--kpt-font-size-sm)}.kpt-map__popup-body p{margin:0 0 var(--kpt-space-1)}.kpt-map__popup-title{display:block;margin-bottom:var(--kpt-space-1);font-size:var(--kpt-font-size-base)}.kpt-map__popup-category{color:var(--kpt-color-on-surface-muted)}.kpt-map__popup-group{margin:0 var(--kpt-space-5) var(--kpt-space-2) 0;font-size:var(--kpt-font-size-xs);color:var(--kpt-color-on-surface-muted);text-transform:uppercase}.kpt-map__popup-tabs{display:flex;flex-wrap:wrap;gap:var(--kpt-space-1);margin-bottom:var(--kpt-space-2)}.kpt-map__popup-tab{padding:var(--kpt-space-1) var(--kpt-space-2);font:inherit;font-size:var(--kpt-font-size-xs);color:var(--kpt-color-on-surface);cursor:pointer;background:var(--kpt-color-surface-variant);border:var(--kpt-border-width-thin) solid transparent;border-radius:var(--kpt-radius-sm)}.kpt-map__popup-tab[aria-selected=true]{background:var(--kpt-color-surface-selected);border-color:var(--kpt-color-border-strong)}.kpt-map__popup-tab:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}.kpt-map__context{position:absolute;top:var(--_kpt-map-y, 0);left:var(--_kpt-map-x, 0);z-index:4;min-width:12rem;padding:var(--kpt-space-2);background:var(--kpt-color-surface-raised);border:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-radius:var(--kpt-radius-md);box-shadow:var(--kpt-elevation-3)}.kpt-map__context-title{margin:0 0 var(--kpt-space-1);padding:0 var(--kpt-space-2);font-size:var(--kpt-font-size-xs);color:var(--kpt-color-on-surface-muted)}.kpt-map__context-item{display:flex;gap:var(--kpt-space-2);align-items:center;width:100%;padding:var(--kpt-space-2);font:inherit;font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface);text-align:start;cursor:pointer;background:transparent;border:none;border-radius:var(--kpt-radius-sm)}.kpt-map__context-item:hover{background:var(--kpt-color-surface-hover)}.kpt-map__context-item:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:-2px}.kpt-map__context-empty{margin:0;padding:var(--kpt-space-2);font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface-muted)}.kpt-map__context-dot{width:.5rem;height:.5rem;background:var(--kpt-color-border-strong);border-radius:var(--kpt-radius-full)}.kpt-map[data-engine=google] .kpt-map__accuracy{background:color-mix(in oklch,var(--kpt-color-info) 15%,transparent);border:var(--kpt-border-width-thin) solid color-mix(in oklch,var(--kpt-color-info) 50%,transparent)}}.kpt-map .leaflet-container{font-family:var(--kpt-font-family-sans);font-size:var(--kpt-font-size-sm);background:var(--kpt-color-surface-sunken);z-index:0}.kpt-map .leaflet-div-icon,.kpt-map .kpt-map__icon{background:transparent;border:none}.kpt-map .leaflet-control-attribution{padding:0 var(--kpt-space-1);font-size:var(--kpt-font-size-xs);color:var(--kpt-color-on-surface-muted);background:color-mix(in oklch,var(--kpt-color-surface-raised) 85%,transparent)}.kpt-map .leaflet-control-attribution a{color:var(--kpt-color-primary)}.kpt-map .kpt-map__accuracy{fill:var(--kpt-color-info);fill-opacity:.15;stroke:var(--kpt-color-info);stroke-opacity:.5}.kpt-map .leaflet-popup-content-wrapper,.kpt-map .leaflet-popup-tip{display:none}\n"] }]
3959
2727
  }], () => [], { locations: [{ type: i0.Input, args: [{ isSignal: true, alias: "locations", required: true }] }], categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: true }] }], country: [{ type: i0.Input, args: [{ isSignal: true, alias: "country", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }], restrictToCountry: [{ type: i0.Input, args: [{ isSignal: true, alias: "restrictToCountry", required: false }] }], bounds: [{ type: i0.Input, args: [{ isSignal: true, alias: "bounds", required: false }] }], cluster: [{ type: i0.Input, args: [{ isSignal: true, alias: "cluster", required: false }] }], clusterOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "clusterOptions", required: false }] }], clusterColorBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "clusterColorBy", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], hiddenCategories: [{ type: i0.Input, args: [{ isSignal: true, alias: "hiddenCategories", required: false }] }, { type: i0.Output, args: ["hiddenCategoriesChange"] }], legendCounts: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendCounts", required: false }] }], showUserLocation: [{ type: i0.Input, args: [{ isSignal: true, alias: "showUserLocation", required: false }] }], followUser: [{ type: i0.Input, args: [{ isSignal: true, alias: "followUser", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], search: [{ type: i0.Input, args: [{ isSignal: true, alias: "search", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], searchOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchOptions", required: false }] }], searchZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchZoom", required: false }] }], searchMarker: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchMarker", required: false }] }], popupLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "popupLayout", required: false }] }], distanceFrom: [{ type: i0.Input, args: [{ isSignal: true, alias: "distanceFrom", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], tileUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "tileUrl", required: false }] }], attribution: [{ type: i0.Input, args: [{ isSignal: true, alias: "attribution", required: false }] }], engine: [{ type: i0.Input, args: [{ isSignal: true, alias: "engine", required: false }] }], geocoder: [{ type: i0.Input, args: [{ isSignal: true, alias: "geocoder", required: false }] }], locationClick: [{ type: i0.Output, args: ["locationClick"] }], clusterClick: [{ type: i0.Output, args: ["clusterClick"] }], locationCreate: [{ type: i0.Output, args: ["locationCreate"] }], searchSelect: [{ type: i0.Output, args: ["searchSelect"] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }], userPositionChange: [{ type: i0.Output, args: ["userPositionChange"] }], userLocationError: [{ type: i0.Output, args: ["userLocationError"] }], canvas: [{ type: i0.ViewChild, args: ['canvas', { isSignal: true }] }], popupEl: [{ type: i0.ViewChild, args: ['popupEl', { isSignal: true }] }], popupTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => KptMapPopupTemplate), { isSignal: true }] }], createFormTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KptMapCreateFormTemplate), { isSignal: true }] }] }); })();
3960
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMap, { className: "KptMap", filePath: "lib/map.component.ts", lineNumber: 119 }); })();
3961
-
3962
- /*
3963
- * Formatowanie liczb dla warstwy prezentacji mapy — pure TS.
3964
- * Wszystko przez `Intl`, więc separator dziesiętny idzie z locale,
3965
- * a nie z zaszytej na sztywno kropki.
3966
- */
3967
- /**
3968
- * Dystans po ludzku: poniżej kilometra w metrach zaokrąglonych do dziesiątek,
3969
- * wyżej w kilometrach z jednym miejscem po przecinku (`6,2 km` w `pl-PL`).
3970
- *
3971
- * Wartość ujemna albo nieskończona daje `null` — wołający chowa wtedy wiersz,
3972
- * zamiast malować „NaN km".
3973
- */
3974
- function formatDistance(metres, locale) {
3975
- if (metres == null || !Number.isFinite(metres) || metres < 0)
3976
- return null;
3977
- if (metres < 1000) {
3978
- const rounded = metres < 100 ? Math.round(metres) : Math.round(metres / 10) * 10;
3979
- return `${new Intl.NumberFormat(locale).format(rounded)} m`;
3980
- }
3981
- const km = metres / 1000;
3982
- // Powyżej 100 km ułamek nie niesie już informacji.
3983
- const digits = km >= 100 ? 0 : 1;
3984
- const value = new Intl.NumberFormat(locale, {
3985
- minimumFractionDigits: digits,
3986
- maximumFractionDigits: digits,
3987
- }).format(km);
3988
- return `${value} km`;
3989
- }
3990
- /** Współrzędne do wiersza karty — pięć miejsc po przecinku to ok. metr. */
3991
- function formatLatLng(at, digits = 5) {
3992
- return `${at.lat.toFixed(digits)}, ${at.lng.toFixed(digits)}`;
3993
- }
3994
-
3995
- /*
3996
- * Normalizacja adresów dla karty POI — pure TS. Wydzielone z `map-poi-card.component.ts`,
3997
- * żeby dało się to testować `node --test` bez ciągnięcia Angulara.
3998
- */
3999
- const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
4000
- const SAFE_SCHEME = /^(https?:|mailto:|tel:)/i;
4001
- /**
4002
- * Adres bez schematu dostaje `https://` — inaczej przeglądarka potraktuje go jak ścieżkę względną.
4003
- * Schemat spoza allowlisty (np. `javascript:`) traktujemy jak brak schematu — bezpieczny
4004
- * fallback zamiast przepuszczenia surowej wartości do `[href]`/`[src]`/`navigator.share()`.
4005
- */
4006
- function normalizeUrl(value) {
4007
- if (!HAS_SCHEME.test(value))
4008
- return `https://${value}`;
4009
- return SAFE_SCHEME.test(value) ? value : `https://${value.replace(HAS_SCHEME, '')}`;
4010
- }
4011
- /** Wersja adresu do pokazania: bez schematu i bez końcowego ukośnika. */
4012
- function prettyUrl(value) {
4013
- return value.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '').replace(/\/$/, '');
4014
- }
2728
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMap, { className: "KptMap", filePath: "lib/map.component.ts", lineNumber: 95 }); })();
4015
2729
 
4016
2730
  const _c0 = () => [];
4017
2731
  const _forTrack0 = ($index, $item) => $item.label;
@@ -4085,8 +2799,8 @@ function KptMapPoiCard_Conditional_14_Template(rf, ctx) { if (rf & 1) {
4085
2799
  i0.ɵɵelementEnd();
4086
2800
  } if (rf & 2) {
4087
2801
  const ctx_r1 = i0.ɵɵnextContext();
4088
- i0.ɵɵproperty("ariaLabel", ctx_r1.favouriteLabel());
4089
- i0.ɵɵattribute("aria-pressed", ctx_r1.favourite())("data-on", ctx_r1.favourite() ? "" : null);
2802
+ i0.ɵɵproperty("ariaPressed", ctx_r1.favourite())("ariaLabel", ctx_r1.favouriteLabel());
2803
+ i0.ɵɵattribute("data-on", ctx_r1.favourite() ? "" : null);
4090
2804
  } }
4091
2805
  function KptMapPoiCard_Conditional_15_Conditional_1_Template(rf, ctx) { if (rf & 1) {
4092
2806
  i0.ɵɵelement(0, "kpt-icon", 27);
@@ -4522,7 +3236,7 @@ class KptMapPoiCard {
4522
3236
  return this.i18n.t(this.favourite() ? 'map.poi.removeFavourite' : 'map.poi.addFavourite');
4523
3237
  }
4524
3238
  static { this.ɵfac = function KptMapPoiCard_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapPoiCard)(); }; }
4525
- static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapPoiCard, selectors: [["kpt-map-poi-card"]], hostAttrs: [1, "kpt-map-poi"], inputs: { poi: [1, "poi"], distance: [1, "distance"], category: [1, "category"], primaryLabel: [1, "primaryLabel"], showShare: [1, "showShare"], showFavourite: [1, "showFavourite"], favourite: [1, "favourite"], closable: [1, "closable"], hoursExpanded: [1, "hoursExpanded"] }, outputs: { favourite: "favouriteChange", hoursExpanded: "hoursExpandedChange", primaryAction: "primaryAction", closed: "closed", shared: "shared" }, decls: 20, vars: 12, consts: [[1, "kpt-map-poi__head"], ["role", "heading", "aria-level", "3", 1, "kpt-map-poi__title"], ["variant", "text", "size", "sm", 1, "kpt-map-poi__close", 3, "ariaLabel"], [1, "kpt-map-poi__meta"], [1, "kpt-map-poi__distance"], [1, "kpt-map-poi__address"], [1, "kpt-map-poi__scroll"], [1, "kpt-map-poi__media"], ["loading", "lazy", "decoding", "async", 1, "kpt-map-poi__photo", 3, "src", "alt"], ["role", "img", 1, "kpt-map-poi__photo-empty"], [1, "kpt-map-poi__actions"], ["size", "sm", "fullWidth", "", 1, "kpt-map-poi__cta"], ["variant", "outline", "size", "sm", 3, "ariaLabel"], ["variant", "outline", "size", "sm", 1, "kpt-map-poi__fav", 3, "ariaLabel"], [1, "kpt-map-poi__category", 3, "--%NS%_kpt-poi-color"], [1, "kpt-map-poi__hours"], [1, "kpt-map-poi__rows"], ["aria-live", "polite", 1, "kpt-map-poi__live"], ["variant", "text", "size", "sm", 1, "kpt-map-poi__close", 3, "click", "ariaLabel"], ["name", "x"], ["name", "photo", "size", "1.75rem"], ["size", "sm", "fullWidth", "", 1, "kpt-map-poi__cta", 3, "click"], ["variant", "outline", "size", "sm", 3, "click", "ariaLabel"], ["name", "share"], ["variant", "outline", "size", "sm", 1, "kpt-map-poi__fav", 3, "click", "ariaLabel"], ["name", "star"], [1, "kpt-map-poi__category"], [1, "kpt-map-poi__category-icon", 3, "name"], ["type", "button", 1, "kpt-map-poi__hours-toggle", 3, "click"], ["name", "clock", 1, "kpt-map-poi__row-icon"], [1, "kpt-map-poi__status"], [1, "kpt-map-poi__status-hint"], ["name", "chevron-down", 1, "kpt-map-poi__chevron"], [1, "kpt-map-poi__week", 3, "id"], [1, "kpt-map-poi__week-day"], [1, "kpt-map-poi__week-hours"], [1, "kpt-map-poi__week-closed"], [1, "kpt-map-poi__week-range"], [1, "kpt-map-poi__row"], [1, "kpt-map-poi__row", "kpt-map-poi__row--fact"], [1, "kpt-map-poi__row-icon", 3, "name"], [1, "kpt-map-poi__row-value", "kpt-map-poi__row-link", 3, "href"], [1, "kpt-map-poi__row-value"], [1, "kpt-map-poi__row-actions"], [1, "kpt-map-poi__row-action", 3, "href", "title"], ["type", "button", 1, "kpt-map-poi__row-action", 3, "title"], [3, "name"], ["type", "button", 1, "kpt-map-poi__row-action", 3, "click", "title"], ["aria-hidden", "true", 1, "kpt-map-poi__row-icon"], [1, "kpt-map-poi__fact-label"]], template: function KptMapPoiCard_Template(rf, ctx) { if (rf & 1) {
3239
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapPoiCard, selectors: [["kpt-map-poi-card"]], hostAttrs: [1, "kpt-map-poi"], inputs: { poi: [1, "poi"], distance: [1, "distance"], category: [1, "category"], primaryLabel: [1, "primaryLabel"], showShare: [1, "showShare"], showFavourite: [1, "showFavourite"], favourite: [1, "favourite"], closable: [1, "closable"], hoursExpanded: [1, "hoursExpanded"] }, outputs: { favourite: "favouriteChange", hoursExpanded: "hoursExpandedChange", primaryAction: "primaryAction", closed: "closed", shared: "shared" }, decls: 20, vars: 12, consts: [[1, "kpt-map-poi__head"], ["role", "heading", "aria-level", "3", 1, "kpt-map-poi__title"], ["variant", "text", "size", "sm", 1, "kpt-map-poi__close", 3, "ariaLabel"], [1, "kpt-map-poi__meta"], [1, "kpt-map-poi__distance"], [1, "kpt-map-poi__address"], [1, "kpt-map-poi__scroll"], [1, "kpt-map-poi__media"], ["loading", "lazy", "decoding", "async", 1, "kpt-map-poi__photo", 3, "src", "alt"], ["role", "img", 1, "kpt-map-poi__photo-empty"], [1, "kpt-map-poi__actions"], ["size", "sm", "fullWidth", "", 1, "kpt-map-poi__cta"], ["variant", "outline", "size", "sm", 3, "ariaLabel"], ["variant", "outline", "size", "sm", 1, "kpt-map-poi__fav", 3, "ariaPressed", "ariaLabel"], [1, "kpt-map-poi__category", 3, "--%NS%_kpt-poi-color"], [1, "kpt-map-poi__hours"], [1, "kpt-map-poi__rows"], ["aria-live", "polite", 1, "kpt-map-poi__live"], ["variant", "text", "size", "sm", 1, "kpt-map-poi__close", 3, "click", "ariaLabel"], ["name", "x"], ["name", "photo", "size", "1.75rem"], ["size", "sm", "fullWidth", "", 1, "kpt-map-poi__cta", 3, "click"], ["variant", "outline", "size", "sm", 3, "click", "ariaLabel"], ["name", "share"], ["variant", "outline", "size", "sm", 1, "kpt-map-poi__fav", 3, "click", "ariaPressed", "ariaLabel"], ["name", "star"], [1, "kpt-map-poi__category"], [1, "kpt-map-poi__category-icon", 3, "name"], ["type", "button", 1, "kpt-map-poi__hours-toggle", 3, "click"], ["name", "clock", 1, "kpt-map-poi__row-icon"], [1, "kpt-map-poi__status"], [1, "kpt-map-poi__status-hint"], ["name", "chevron-down", 1, "kpt-map-poi__chevron"], [1, "kpt-map-poi__week", 3, "id"], [1, "kpt-map-poi__week-day"], [1, "kpt-map-poi__week-hours"], [1, "kpt-map-poi__week-closed"], [1, "kpt-map-poi__week-range"], [1, "kpt-map-poi__row"], [1, "kpt-map-poi__row", "kpt-map-poi__row--fact"], [1, "kpt-map-poi__row-icon", 3, "name"], [1, "kpt-map-poi__row-value", "kpt-map-poi__row-link", 3, "href"], [1, "kpt-map-poi__row-value"], [1, "kpt-map-poi__row-actions"], [1, "kpt-map-poi__row-action", 3, "href", "title"], ["type", "button", 1, "kpt-map-poi__row-action", 3, "title"], [3, "name"], ["type", "button", 1, "kpt-map-poi__row-action", 3, "click", "title"], ["aria-hidden", "true", 1, "kpt-map-poi__row-icon"], [1, "kpt-map-poi__fact-label"]], template: function KptMapPoiCard_Template(rf, ctx) { if (rf & 1) {
4526
3240
  i0.ɵɵelementStart(0, "header", 0)(1, "p", 1);
4527
3241
  i0.ɵɵtext(2);
4528
3242
  i0.ɵɵelementEnd();
@@ -4575,17 +3289,19 @@ class KptMapPoiCard {
4575
3289
  i0.ɵɵconditional(ctx.rows().length || ctx.poi().facts?.length ? 17 : -1);
4576
3290
  i0.ɵɵadvance(2);
4577
3291
  i0.ɵɵconditional(ctx.justCopied() ? 19 : -1);
4578
- } }, dependencies: [KptButton, KptIcon, KptIconButton], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-poi{display:flex;flex-direction:column;min-height:0;color:var(--%NS%kpt-color-on-surface);font-size:var(--%NS%kpt-font-size-sm);text-align:start}.kpt-map-poi__scroll{flex:0 1 auto;min-height:0;padding:0 var(--%NS%kpt-space-4) var(--%NS%kpt-space-4);overflow-y:auto;overscroll-behavior:contain}.kpt-map-poi__head{flex:none;padding:var(--%NS%kpt-space-4) var(--%NS%kpt-space-4) 0;display:flex;gap:var(--%NS%kpt-space-2);align-items:flex-start;justify-content:space-between}.kpt-map-poi__title{margin:0;font-size:var(--%NS%kpt-font-size-lg);font-weight:var(--%NS%kpt-font-weight-bold);line-height:1.25;letter-spacing:normal;text-transform:none}.kpt-map-poi__close{flex:none;margin:calc(var(--%NS%kpt-space-1) * -1) calc(var(--%NS%kpt-space-2) * -1) 0 0}.kpt-map-poi__meta{flex:none;display:flex;flex-wrap:wrap;gap:0 var(--%NS%kpt-space-2);margin:var(--%NS%kpt-space-1) 0 0;padding:0 var(--%NS%kpt-space-4) var(--%NS%kpt-space-3);border-bottom:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}.kpt-map-poi__distance{font-weight:var(--%NS%kpt-font-weight-bold)}.kpt-map-poi__address{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__media{margin-top:var(--%NS%kpt-space-4);overflow:hidden;border-radius:var(--%NS%kpt-map-poi-media-radius, var(--%NS%kpt-radius-md))}.kpt-map-poi__photo,.kpt-map-poi__photo-empty{display:block;width:100%;aspect-ratio:16/9}.kpt-map-poi__photo{object-fit:cover}.kpt-map-poi__photo-empty{display:grid;place-items:center;color:var(--%NS%kpt-color-text-subtle);background:var(--%NS%kpt-color-surface-sunken)}.kpt-map-poi__actions{display:flex;gap:var(--%NS%kpt-space-2);align-items:center;margin-top:var(--%NS%kpt-space-3);margin-bottom:var(--%NS%kpt-space-3)}.kpt-map-poi__cta{flex:1 1 auto;min-width:0}.kpt-map-poi__fav[data-on]{color:var(--%NS%kpt-color-warning);border-color:var(--%NS%kpt-color-warning-border)}.kpt-map-poi__category{display:flex;gap:var(--%NS%kpt-space-3);align-items:center;margin:0;padding:var(--%NS%kpt-space-3) 0;border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}.kpt-map-poi__category-icon{color:var(--%NS%_kpt-poi-color, var(--%NS%kpt-color-primary))}.kpt-map-poi__hours{border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}.kpt-map-poi__hours-toggle{display:flex;gap:var(--%NS%kpt-space-3);align-items:center;width:100%;padding:var(--%NS%kpt-space-3) 0;font:inherit;color:inherit;text-align:start;background:none;border:0;cursor:pointer}.kpt-map-poi__hours-toggle:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:calc(var(--%NS%kpt-space-1) * -1)}.kpt-map-poi__status{font-weight:var(--%NS%kpt-font-weight-medium);color:var(--%NS%kpt-color-danger)}.kpt-map-poi__status[data-open=true]{color:var(--%NS%kpt-color-success)}.kpt-map-poi__status-hint{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__chevron{margin-inline-start:auto;color:var(--%NS%kpt-color-on-surface-muted);transition:rotate var(--%NS%kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__hours-toggle[aria-expanded=true] .kpt-map-poi__chevron{rotate:180deg}.kpt-map-poi__week{display:grid;grid-template-columns:auto 1fr;gap:var(--%NS%kpt-space-1) var(--%NS%kpt-space-4);margin:0;padding:0 0 var(--%NS%kpt-space-3) calc(1em + var(--%NS%kpt-space-3))}.kpt-map-poi__week-day{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__week-hours{display:flex;flex-direction:column;margin:0}.kpt-map-poi__week-closed{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__rows{margin:0;padding:0;list-style:none}.kpt-map-poi__row{display:flex;gap:var(--%NS%kpt-space-3);align-items:center;padding:var(--%NS%kpt-space-2) var(--%NS%kpt-space-2) var(--%NS%kpt-space-2) 0;margin-inline-start:calc(var(--%NS%kpt-space-2) * -1);padding-inline-start:var(--%NS%kpt-space-2);border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-sm)}.kpt-map-poi__row:hover,.kpt-map-poi__row:focus-within{background:var(--%NS%kpt-map-poi-row-hover, var(--%NS%kpt-color-surface-hover))}.kpt-map-poi__row-icon{flex:none;color:var(--%NS%kpt-color-primary)}.kpt-map-poi__row-value{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}.kpt-map-poi__row-link{color:inherit;text-decoration:none}.kpt-map-poi__row-link:hover{text-decoration:underline}.kpt-map-poi__fact-label{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__fact-label:after{content:\" \\b7 \"}.kpt-map-poi__row-actions{display:flex;flex:none;gap:var(--%NS%kpt-space-1)}.kpt-map-poi__row-action{display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;color:var(--%NS%kpt-color-on-surface-muted);background:none;border:0;border-radius:var(--%NS%kpt-radius-sm);cursor:pointer}.kpt-map-poi__row-action:hover{color:var(--%NS%kpt-color-primary);background:var(--%NS%kpt-color-surface-selected)}.kpt-map-poi__row-action:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}@media(hover:hover)and (pointer:fine){.kpt-map-poi__row-actions{opacity:0;transition:opacity var(--%NS%kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__row:hover .kpt-map-poi__row-actions,.kpt-map-poi__row:focus-within .kpt-map-poi__row-actions{opacity:1}}.kpt-map-poi__live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"], encapsulation: 2 }); }
3292
+ } }, dependencies: [KptButton, KptIcon, KptIconButton], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-poi{display:flex;flex-direction:column;min-height:0;color:var(--%NS%kpt-color-on-surface);font-size:var(--%NS%kpt-font-size-sm);text-align:start}.kpt-map-poi__scroll{flex:0 1 auto;min-height:0;padding:0 var(--%NS%kpt-space-4) var(--%NS%kpt-space-4);overflow-y:auto;overscroll-behavior:contain}.kpt-map-poi__head{flex:none;padding:var(--%NS%kpt-space-4) var(--%NS%kpt-space-4) 0;display:flex;gap:var(--%NS%kpt-space-2);align-items:flex-start;justify-content:space-between}.kpt-map-poi__title{margin:0;font-size:var(--%NS%kpt-font-size-lg);font-weight:var(--%NS%kpt-font-weight-bold);line-height:1.25;letter-spacing:normal;text-transform:none}.kpt-map-poi__close{flex:none;margin:calc(var(--%NS%kpt-space-1) * -1) calc(var(--%NS%kpt-space-2) * -1) 0 0}.kpt-map-poi__meta{flex:none;display:flex;flex-wrap:wrap;gap:0 var(--%NS%kpt-space-2);margin:var(--%NS%kpt-space-1) 0 0;padding:0 var(--%NS%kpt-space-4) var(--%NS%kpt-space-3);border-bottom:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}.kpt-map-poi__distance{font-weight:var(--%NS%kpt-font-weight-bold)}.kpt-map-poi__address{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__media{margin-top:var(--%NS%kpt-space-4);overflow:hidden;border-radius:var(--%NS%kpt-map-poi-media-radius, var(--%NS%kpt-radius-md))}.kpt-map-poi__photo,.kpt-map-poi__photo-empty{display:block;width:100%;aspect-ratio:16/9}.kpt-map-poi__photo{object-fit:cover}.kpt-map-poi__photo-empty{display:grid;place-items:center;color:var(--%NS%kpt-color-text-subtle);background:var(--%NS%kpt-color-surface-sunken)}.kpt-map-poi__actions{display:flex;gap:var(--%NS%kpt-space-2);align-items:center;margin-top:var(--%NS%kpt-space-3);margin-bottom:var(--%NS%kpt-space-3)}.kpt-map-poi__cta{flex:1 1 auto;min-width:0}.kpt-map-poi__fav[data-on] .kpt-icon-button{color:var(--%NS%kpt-color-warning);border-color:var(--%NS%kpt-color-warning-border)}.kpt-map-poi__category{display:flex;gap:var(--%NS%kpt-space-3);align-items:center;margin:0;padding:var(--%NS%kpt-space-3) 0;border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}.kpt-map-poi__category-icon{color:var(--%NS%_kpt-poi-color, var(--%NS%kpt-color-primary))}.kpt-map-poi__hours{border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}.kpt-map-poi__hours-toggle{display:flex;gap:var(--%NS%kpt-space-3);align-items:center;width:100%;padding:var(--%NS%kpt-space-3) 0;font:inherit;color:inherit;text-align:start;background:none;border:0;cursor:pointer}.kpt-map-poi__hours-toggle:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:calc(var(--%NS%kpt-space-1) * -1)}.kpt-map-poi__status{font-weight:var(--%NS%kpt-font-weight-medium);color:var(--%NS%kpt-color-danger)}.kpt-map-poi__status[data-open=true]{color:var(--%NS%kpt-color-success)}.kpt-map-poi__status-hint{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__chevron{margin-inline-start:auto;color:var(--%NS%kpt-color-on-surface-muted);transition:rotate var(--%NS%kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__hours-toggle[aria-expanded=true] .kpt-map-poi__chevron{rotate:180deg}.kpt-map-poi__week{display:grid;grid-template-columns:auto 1fr;gap:var(--%NS%kpt-space-1) var(--%NS%kpt-space-4);margin:0;padding:0 0 var(--%NS%kpt-space-3) calc(1em + var(--%NS%kpt-space-3))}.kpt-map-poi__week-day{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__week-hours{display:flex;flex-direction:column;margin:0}.kpt-map-poi__week-closed{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__rows{margin:0;padding:0;list-style:none}.kpt-map-poi__row{display:flex;gap:var(--%NS%kpt-space-3);align-items:center;padding:var(--%NS%kpt-space-2) var(--%NS%kpt-space-2) var(--%NS%kpt-space-2) 0;margin-inline-start:calc(var(--%NS%kpt-space-2) * -1);padding-inline-start:var(--%NS%kpt-space-2);border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-sm)}.kpt-map-poi__row:hover,.kpt-map-poi__row:focus-within{background:var(--%NS%kpt-map-poi-row-hover, var(--%NS%kpt-color-surface-hover))}.kpt-map-poi__row-icon{flex:none;color:var(--%NS%kpt-color-primary)}.kpt-map-poi__row-value{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}.kpt-map-poi__row-link{color:inherit;text-decoration:none}.kpt-map-poi__row-link:hover{text-decoration:underline}.kpt-map-poi__fact-label{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-poi__fact-label:after{content:\" \\b7 \"}.kpt-map-poi__row-actions{display:flex;flex:none;gap:var(--%NS%kpt-space-1)}.kpt-map-poi__row-action{display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;color:var(--%NS%kpt-color-on-surface-muted);background:none;border:0;border-radius:var(--%NS%kpt-radius-sm);cursor:pointer}.kpt-map-poi__row-action:hover{color:var(--%NS%kpt-color-primary);background:var(--%NS%kpt-color-surface-selected)}.kpt-map-poi__row-action:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}@media(hover:hover)and (pointer:fine){.kpt-map-poi__row-actions{opacity:0;transition:opacity var(--%NS%kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__row:hover .kpt-map-poi__row-actions,.kpt-map-poi__row:focus-within .kpt-map-poi__row-actions{opacity:1}}.kpt-map-poi__live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"], encapsulation: 2 }); }
4579
3293
  }
4580
3294
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapPoiCard, [{
4581
3295
  type: Component,
4582
- args: [{ selector: 'kpt-map-poi-card', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptButton, KptIcon, KptIconButton], host: { class: 'kpt-map-poi' }, template: "<header class=\"kpt-map-poi__head\">\n <!--\n Nag\u0142\u00F3wek jako `role=\"heading\"`, nie `<h3>`: style komponent\u00F3w siedz\u0105 w `@layer\n kpt.components`, a CSS aplikacji spoza warstw wygrywa z nimi zawsze. Zwyk\u0142y `<h3>`\n \u0142apa\u0142by wi\u0119c ka\u017Cd\u0105 regu\u0142\u0119 `h3 { \u2026 }` z uk\u0142adu strony \u2014 a nag\u0142\u00F3wek sekcji strony\n i tytu\u0142 miejsca to dwie r\u00F3\u017Cne rzeczy. Dla czytnika ekranu to wci\u0105\u017C nag\u0142\u00F3wek poziomu 3.\n -->\n <p class=\"kpt-map-poi__title\" role=\"heading\" aria-level=\"3\">{{ poi().title }}</p>\n @if (closable()) {\n <kpt-icon-button\n class=\"kpt-map-poi__close\"\n variant=\"text\"\n size=\"sm\"\n [ariaLabel]=\"i18n.t('common.close')\"\n (click)=\"onClose()\"\n >\n <kpt-icon name=\"x\" />\n </kpt-icon-button>\n }\n</header>\n\n<p class=\"kpt-map-poi__meta\">\n @if (distanceLabel(); as distance) {\n <strong class=\"kpt-map-poi__distance\">{{ distance }}</strong>\n }\n @if (poi().address; as address) {\n <span class=\"kpt-map-poi__address\">{{ address }}</span>\n }\n</p>\n\n<!--\n Wszystko poni\u017Cej nag\u0142\u00F3wka przewija si\u0119 osobno. Kontenerem przewijania jest ten div,\n a nie popup mapy \u2014 inaczej tytu\u0142 z krzy\u017Cykiem odje\u017Cd\u017Ca\u0142by razem ze zdj\u0119ciem.\n-->\n<div class=\"kpt-map-poi__scroll\">\n <div class=\"kpt-map-poi__media\">\n @if (poi().photo; as photo) {\n <img\n class=\"kpt-map-poi__photo\"\n [src]=\"photo\"\n [alt]=\"poi().photoAlt ?? poi().title\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n } @else {\n <div class=\"kpt-map-poi__photo-empty\" [attr.aria-label]=\"i18n.t('map.poi.noPhoto')\" role=\"img\">\n <kpt-icon name=\"photo\" size=\"1.75rem\" />\n </div>\n }\n </div>\n\n <div class=\"kpt-map-poi__actions\">\n @if (primaryLabel()) {\n <kpt-button class=\"kpt-map-poi__cta\" size=\"sm\" fullWidth (click)=\"onPrimary()\">\n {{ primaryLabel() }}\n </kpt-button>\n }\n @if (showShare()) {\n <kpt-icon-button\n variant=\"outline\"\n size=\"sm\"\n [ariaLabel]=\"i18n.t('map.poi.share')\"\n (click)=\"share()\"\n >\n <kpt-icon name=\"share\" />\n </kpt-icon-button>\n }\n @if (showFavourite()) {\n <kpt-icon-button\n class=\"kpt-map-poi__fav\"\n variant=\"outline\"\n size=\"sm\"\n [attr.aria-pressed]=\"favourite()\"\n [attr.data-on]=\"favourite() ? '' : null\"\n [ariaLabel]=\"favouriteLabel()\"\n (click)=\"toggleFavourite()\"\n >\n <kpt-icon name=\"star\" />\n </kpt-icon-button>\n }\n </div>\n\n @if (category(); as category) {\n <p class=\"kpt-map-poi__category\" [style.--_kpt-poi-color]=\"categoryColor()\">\n @if (category.icon) {\n <kpt-icon class=\"kpt-map-poi__category-icon\" [name]=\"category.icon\" />\n }\n {{ category.label }}\n </p>\n }\n\n @if (hasHours()) {\n <div class=\"kpt-map-poi__hours\">\n <button\n class=\"kpt-map-poi__hours-toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"hoursExpanded()\"\n [attr.aria-controls]=\"hoursId\"\n (click)=\"toggleHours()\"\n >\n <kpt-icon class=\"kpt-map-poi__row-icon\" name=\"clock\" />\n <span class=\"kpt-map-poi__status\" [attr.data-open]=\"open() || alwaysOpen()\">\n {{ statusLabel() }}\n </span>\n @if (statusHint(); as hint) {\n <span class=\"kpt-map-poi__status-hint\">\u00B7 {{ hint }}</span>\n }\n <kpt-icon class=\"kpt-map-poi__chevron\" name=\"chevron-down\" />\n </button>\n\n @if (hoursExpanded()) {\n <dl class=\"kpt-map-poi__week\" [id]=\"hoursId\">\n @for (group of hoursGroups(); track group.label) {\n <dt class=\"kpt-map-poi__week-day\">{{ group.label }}</dt>\n <dd class=\"kpt-map-poi__week-hours\">\n @if (group.closed) {\n <span class=\"kpt-map-poi__week-closed\">{{ i18n.t('map.poi.closedDay') }}</span>\n } @else {\n @for (range of group.ranges; track range) {\n <span class=\"kpt-map-poi__week-range\">{{ range }}</span>\n }\n }\n </dd>\n }\n </dl>\n }\n </div>\n }\n\n @if (rows().length || poi().facts?.length) {\n <ul class=\"kpt-map-poi__rows\">\n @for (row of rows(); track row.kind) {\n <li class=\"kpt-map-poi__row\">\n <kpt-icon class=\"kpt-map-poi__row-icon\" [name]=\"row.icon\" />\n\n @if (row.href) {\n <a\n class=\"kpt-map-poi__row-value kpt-map-poi__row-link\"\n [href]=\"row.href\"\n [attr.target]=\"row.external ? '_blank' : null\"\n [attr.rel]=\"row.external ? 'noopener' : null\"\n >\n {{ row.text }}\n </a>\n } @else {\n <span class=\"kpt-map-poi__row-value\">{{ row.text }}</span>\n }\n\n <span class=\"kpt-map-poi__row-actions\">\n @if (row.href) {\n <a\n class=\"kpt-map-poi__row-action\"\n [href]=\"row.href\"\n [attr.target]=\"row.external ? '_blank' : null\"\n [attr.rel]=\"row.external ? 'noopener' : null\"\n [attr.aria-label]=\"row.actionLabel\"\n [title]=\"row.actionLabel\"\n >\n <kpt-icon [name]=\"row.actionIcon\" />\n </a>\n }\n @if (canCopy) {\n <button\n class=\"kpt-map-poi__row-action\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('map.poi.copy')\"\n [title]=\"i18n.t('map.poi.copy')\"\n (click)=\"copy(row)\"\n >\n <kpt-icon [name]=\"justCopied() === row.kind ? 'check' : 'copy'\" />\n </button>\n }\n </span>\n </li>\n }\n\n @for (fact of poi().facts ?? []; track fact.label) {\n <li class=\"kpt-map-poi__row kpt-map-poi__row--fact\">\n @if (fact.icon) {\n <kpt-icon class=\"kpt-map-poi__row-icon\" [name]=\"fact.icon\" />\n } @else {\n <span class=\"kpt-map-poi__row-icon\" aria-hidden=\"true\"></span>\n }\n <span class=\"kpt-map-poi__row-value\">\n <span class=\"kpt-map-poi__fact-label\">{{ fact.label }}</span>\n {{ fact.value }}\n </span>\n </li>\n }\n </ul>\n }\n\n <p class=\"kpt-map-poi__live\" aria-live=\"polite\">\n @if (justCopied()) {\n {{ i18n.t('map.poi.copied') }}\n }\n </p>\n</div>\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-poi{display:flex;flex-direction:column;min-height:0;color:var(--kpt-color-on-surface);font-size:var(--kpt-font-size-sm);text-align:start}.kpt-map-poi__scroll{flex:0 1 auto;min-height:0;padding:0 var(--kpt-space-4) var(--kpt-space-4);overflow-y:auto;overscroll-behavior:contain}.kpt-map-poi__head{flex:none;padding:var(--kpt-space-4) var(--kpt-space-4) 0;display:flex;gap:var(--kpt-space-2);align-items:flex-start;justify-content:space-between}.kpt-map-poi__title{margin:0;font-size:var(--kpt-font-size-lg);font-weight:var(--kpt-font-weight-bold);line-height:1.25;letter-spacing:normal;text-transform:none}.kpt-map-poi__close{flex:none;margin:calc(var(--kpt-space-1) * -1) calc(var(--kpt-space-2) * -1) 0 0}.kpt-map-poi__meta{flex:none;display:flex;flex-wrap:wrap;gap:0 var(--kpt-space-2);margin:var(--kpt-space-1) 0 0;padding:0 var(--kpt-space-4) var(--kpt-space-3);border-bottom:var(--kpt-border-width-thin) solid var(--kpt-color-border)}.kpt-map-poi__distance{font-weight:var(--kpt-font-weight-bold)}.kpt-map-poi__address{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__media{margin-top:var(--kpt-space-4);overflow:hidden;border-radius:var(--kpt-map-poi-media-radius, var(--kpt-radius-md))}.kpt-map-poi__photo,.kpt-map-poi__photo-empty{display:block;width:100%;aspect-ratio:16/9}.kpt-map-poi__photo{object-fit:cover}.kpt-map-poi__photo-empty{display:grid;place-items:center;color:var(--kpt-color-text-subtle);background:var(--kpt-color-surface-sunken)}.kpt-map-poi__actions{display:flex;gap:var(--kpt-space-2);align-items:center;margin-top:var(--kpt-space-3);margin-bottom:var(--kpt-space-3)}.kpt-map-poi__cta{flex:1 1 auto;min-width:0}.kpt-map-poi__fav[data-on]{color:var(--kpt-color-warning);border-color:var(--kpt-color-warning-border)}.kpt-map-poi__category{display:flex;gap:var(--kpt-space-3);align-items:center;margin:0;padding:var(--kpt-space-3) 0;border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border)}.kpt-map-poi__category-icon{color:var(--_kpt-poi-color, var(--kpt-color-primary))}.kpt-map-poi__hours{border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border)}.kpt-map-poi__hours-toggle{display:flex;gap:var(--kpt-space-3);align-items:center;width:100%;padding:var(--kpt-space-3) 0;font:inherit;color:inherit;text-align:start;background:none;border:0;cursor:pointer}.kpt-map-poi__hours-toggle:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:calc(var(--kpt-space-1) * -1)}.kpt-map-poi__status{font-weight:var(--kpt-font-weight-medium);color:var(--kpt-color-danger)}.kpt-map-poi__status[data-open=true]{color:var(--kpt-color-success)}.kpt-map-poi__status-hint{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__chevron{margin-inline-start:auto;color:var(--kpt-color-on-surface-muted);transition:rotate var(--kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__hours-toggle[aria-expanded=true] .kpt-map-poi__chevron{rotate:180deg}.kpt-map-poi__week{display:grid;grid-template-columns:auto 1fr;gap:var(--kpt-space-1) var(--kpt-space-4);margin:0;padding:0 0 var(--kpt-space-3) calc(1em + var(--kpt-space-3))}.kpt-map-poi__week-day{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__week-hours{display:flex;flex-direction:column;margin:0}.kpt-map-poi__week-closed{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__rows{margin:0;padding:0;list-style:none}.kpt-map-poi__row{display:flex;gap:var(--kpt-space-3);align-items:center;padding:var(--kpt-space-2) var(--kpt-space-2) var(--kpt-space-2) 0;margin-inline-start:calc(var(--kpt-space-2) * -1);padding-inline-start:var(--kpt-space-2);border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-radius:var(--kpt-radius-sm)}.kpt-map-poi__row:hover,.kpt-map-poi__row:focus-within{background:var(--kpt-map-poi-row-hover, var(--kpt-color-surface-hover))}.kpt-map-poi__row-icon{flex:none;color:var(--kpt-color-primary)}.kpt-map-poi__row-value{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}.kpt-map-poi__row-link{color:inherit;text-decoration:none}.kpt-map-poi__row-link:hover{text-decoration:underline}.kpt-map-poi__fact-label{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__fact-label:after{content:\" \\b7 \"}.kpt-map-poi__row-actions{display:flex;flex:none;gap:var(--kpt-space-1)}.kpt-map-poi__row-action{display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;color:var(--kpt-color-on-surface-muted);background:none;border:0;border-radius:var(--kpt-radius-sm);cursor:pointer}.kpt-map-poi__row-action:hover{color:var(--kpt-color-primary);background:var(--kpt-color-surface-selected)}.kpt-map-poi__row-action:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}@media(hover:hover)and (pointer:fine){.kpt-map-poi__row-actions{opacity:0;transition:opacity var(--kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__row:hover .kpt-map-poi__row-actions,.kpt-map-poi__row:focus-within .kpt-map-poi__row-actions{opacity:1}}.kpt-map-poi__live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"] }]
3296
+ args: [{ selector: 'kpt-map-poi-card', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptButton, KptIcon, KptIconButton], host: { class: 'kpt-map-poi' }, template: "<header class=\"kpt-map-poi__head\">\n <!--\n Nag\u0142\u00F3wek jako `role=\"heading\"`, nie `<h3>`: style komponent\u00F3w siedz\u0105 w `@layer\n kpt.components`, a CSS aplikacji spoza warstw wygrywa z nimi zawsze. Zwyk\u0142y `<h3>`\n \u0142apa\u0142by wi\u0119c ka\u017Cd\u0105 regu\u0142\u0119 `h3 { \u2026 }` z uk\u0142adu strony \u2014 a nag\u0142\u00F3wek sekcji strony\n i tytu\u0142 miejsca to dwie r\u00F3\u017Cne rzeczy. Dla czytnika ekranu to wci\u0105\u017C nag\u0142\u00F3wek poziomu 3.\n -->\n <p class=\"kpt-map-poi__title\" role=\"heading\" aria-level=\"3\">{{ poi().title }}</p>\n @if (closable()) {\n <kpt-icon-button\n class=\"kpt-map-poi__close\"\n variant=\"text\"\n size=\"sm\"\n [ariaLabel]=\"i18n.t('common.close')\"\n (click)=\"onClose()\"\n >\n <kpt-icon name=\"x\" />\n </kpt-icon-button>\n }\n</header>\n\n<p class=\"kpt-map-poi__meta\">\n @if (distanceLabel(); as distance) {\n <strong class=\"kpt-map-poi__distance\">{{ distance }}</strong>\n }\n @if (poi().address; as address) {\n <span class=\"kpt-map-poi__address\">{{ address }}</span>\n }\n</p>\n\n<!--\n Wszystko poni\u017Cej nag\u0142\u00F3wka przewija si\u0119 osobno. Kontenerem przewijania jest ten div,\n a nie popup mapy \u2014 inaczej tytu\u0142 z krzy\u017Cykiem odje\u017Cd\u017Ca\u0142by razem ze zdj\u0119ciem.\n-->\n<div class=\"kpt-map-poi__scroll\">\n <div class=\"kpt-map-poi__media\">\n @if (poi().photo; as photo) {\n <img\n class=\"kpt-map-poi__photo\"\n [src]=\"photo\"\n [alt]=\"poi().photoAlt ?? poi().title\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n } @else {\n <div class=\"kpt-map-poi__photo-empty\" [attr.aria-label]=\"i18n.t('map.poi.noPhoto')\" role=\"img\">\n <kpt-icon name=\"photo\" size=\"1.75rem\" />\n </div>\n }\n </div>\n\n <div class=\"kpt-map-poi__actions\">\n @if (primaryLabel()) {\n <kpt-button class=\"kpt-map-poi__cta\" size=\"sm\" fullWidth (click)=\"onPrimary()\">\n {{ primaryLabel() }}\n </kpt-button>\n }\n @if (showShare()) {\n <kpt-icon-button\n variant=\"outline\"\n size=\"sm\"\n [ariaLabel]=\"i18n.t('map.poi.share')\"\n (click)=\"share()\"\n >\n <kpt-icon name=\"share\" />\n </kpt-icon-button>\n }\n @if (showFavourite()) {\n <kpt-icon-button\n class=\"kpt-map-poi__fav\"\n variant=\"outline\"\n size=\"sm\"\n [ariaPressed]=\"favourite()\"\n [attr.data-on]=\"favourite() ? '' : null\"\n [ariaLabel]=\"favouriteLabel()\"\n (click)=\"toggleFavourite()\"\n >\n <kpt-icon name=\"star\" />\n </kpt-icon-button>\n }\n </div>\n\n @if (category(); as category) {\n <p class=\"kpt-map-poi__category\" [style.--_kpt-poi-color]=\"categoryColor()\">\n @if (category.icon) {\n <kpt-icon class=\"kpt-map-poi__category-icon\" [name]=\"category.icon\" />\n }\n {{ category.label }}\n </p>\n }\n\n @if (hasHours()) {\n <div class=\"kpt-map-poi__hours\">\n <button\n class=\"kpt-map-poi__hours-toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"hoursExpanded()\"\n [attr.aria-controls]=\"hoursId\"\n (click)=\"toggleHours()\"\n >\n <kpt-icon class=\"kpt-map-poi__row-icon\" name=\"clock\" />\n <span class=\"kpt-map-poi__status\" [attr.data-open]=\"open() || alwaysOpen()\">\n {{ statusLabel() }}\n </span>\n @if (statusHint(); as hint) {\n <span class=\"kpt-map-poi__status-hint\">\u00B7 {{ hint }}</span>\n }\n <kpt-icon class=\"kpt-map-poi__chevron\" name=\"chevron-down\" />\n </button>\n\n @if (hoursExpanded()) {\n <dl class=\"kpt-map-poi__week\" [id]=\"hoursId\">\n @for (group of hoursGroups(); track group.label) {\n <dt class=\"kpt-map-poi__week-day\">{{ group.label }}</dt>\n <dd class=\"kpt-map-poi__week-hours\">\n @if (group.closed) {\n <span class=\"kpt-map-poi__week-closed\">{{ i18n.t('map.poi.closedDay') }}</span>\n } @else {\n @for (range of group.ranges; track range) {\n <span class=\"kpt-map-poi__week-range\">{{ range }}</span>\n }\n }\n </dd>\n }\n </dl>\n }\n </div>\n }\n\n @if (rows().length || poi().facts?.length) {\n <ul class=\"kpt-map-poi__rows\">\n @for (row of rows(); track row.kind) {\n <li class=\"kpt-map-poi__row\">\n <kpt-icon class=\"kpt-map-poi__row-icon\" [name]=\"row.icon\" />\n\n @if (row.href) {\n <a\n class=\"kpt-map-poi__row-value kpt-map-poi__row-link\"\n [href]=\"row.href\"\n [attr.target]=\"row.external ? '_blank' : null\"\n [attr.rel]=\"row.external ? 'noopener' : null\"\n >\n {{ row.text }}\n </a>\n } @else {\n <span class=\"kpt-map-poi__row-value\">{{ row.text }}</span>\n }\n\n <span class=\"kpt-map-poi__row-actions\">\n @if (row.href) {\n <a\n class=\"kpt-map-poi__row-action\"\n [href]=\"row.href\"\n [attr.target]=\"row.external ? '_blank' : null\"\n [attr.rel]=\"row.external ? 'noopener' : null\"\n [attr.aria-label]=\"row.actionLabel\"\n [title]=\"row.actionLabel\"\n >\n <kpt-icon [name]=\"row.actionIcon\" />\n </a>\n }\n @if (canCopy) {\n <button\n class=\"kpt-map-poi__row-action\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('map.poi.copy')\"\n [title]=\"i18n.t('map.poi.copy')\"\n (click)=\"copy(row)\"\n >\n <kpt-icon [name]=\"justCopied() === row.kind ? 'check' : 'copy'\" />\n </button>\n }\n </span>\n </li>\n }\n\n @for (fact of poi().facts ?? []; track fact.label) {\n <li class=\"kpt-map-poi__row kpt-map-poi__row--fact\">\n @if (fact.icon) {\n <kpt-icon class=\"kpt-map-poi__row-icon\" [name]=\"fact.icon\" />\n } @else {\n <span class=\"kpt-map-poi__row-icon\" aria-hidden=\"true\"></span>\n }\n <span class=\"kpt-map-poi__row-value\">\n <span class=\"kpt-map-poi__fact-label\">{{ fact.label }}</span>\n {{ fact.value }}\n </span>\n </li>\n }\n </ul>\n }\n\n <p class=\"kpt-map-poi__live\" aria-live=\"polite\">\n @if (justCopied()) {\n {{ i18n.t('map.poi.copied') }}\n }\n </p>\n</div>\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-poi{display:flex;flex-direction:column;min-height:0;color:var(--kpt-color-on-surface);font-size:var(--kpt-font-size-sm);text-align:start}.kpt-map-poi__scroll{flex:0 1 auto;min-height:0;padding:0 var(--kpt-space-4) var(--kpt-space-4);overflow-y:auto;overscroll-behavior:contain}.kpt-map-poi__head{flex:none;padding:var(--kpt-space-4) var(--kpt-space-4) 0;display:flex;gap:var(--kpt-space-2);align-items:flex-start;justify-content:space-between}.kpt-map-poi__title{margin:0;font-size:var(--kpt-font-size-lg);font-weight:var(--kpt-font-weight-bold);line-height:1.25;letter-spacing:normal;text-transform:none}.kpt-map-poi__close{flex:none;margin:calc(var(--kpt-space-1) * -1) calc(var(--kpt-space-2) * -1) 0 0}.kpt-map-poi__meta{flex:none;display:flex;flex-wrap:wrap;gap:0 var(--kpt-space-2);margin:var(--kpt-space-1) 0 0;padding:0 var(--kpt-space-4) var(--kpt-space-3);border-bottom:var(--kpt-border-width-thin) solid var(--kpt-color-border)}.kpt-map-poi__distance{font-weight:var(--kpt-font-weight-bold)}.kpt-map-poi__address{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__media{margin-top:var(--kpt-space-4);overflow:hidden;border-radius:var(--kpt-map-poi-media-radius, var(--kpt-radius-md))}.kpt-map-poi__photo,.kpt-map-poi__photo-empty{display:block;width:100%;aspect-ratio:16/9}.kpt-map-poi__photo{object-fit:cover}.kpt-map-poi__photo-empty{display:grid;place-items:center;color:var(--kpt-color-text-subtle);background:var(--kpt-color-surface-sunken)}.kpt-map-poi__actions{display:flex;gap:var(--kpt-space-2);align-items:center;margin-top:var(--kpt-space-3);margin-bottom:var(--kpt-space-3)}.kpt-map-poi__cta{flex:1 1 auto;min-width:0}.kpt-map-poi__fav[data-on] .kpt-icon-button{color:var(--kpt-color-warning);border-color:var(--kpt-color-warning-border)}.kpt-map-poi__category{display:flex;gap:var(--kpt-space-3);align-items:center;margin:0;padding:var(--kpt-space-3) 0;border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border)}.kpt-map-poi__category-icon{color:var(--_kpt-poi-color, var(--kpt-color-primary))}.kpt-map-poi__hours{border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border)}.kpt-map-poi__hours-toggle{display:flex;gap:var(--kpt-space-3);align-items:center;width:100%;padding:var(--kpt-space-3) 0;font:inherit;color:inherit;text-align:start;background:none;border:0;cursor:pointer}.kpt-map-poi__hours-toggle:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:calc(var(--kpt-space-1) * -1)}.kpt-map-poi__status{font-weight:var(--kpt-font-weight-medium);color:var(--kpt-color-danger)}.kpt-map-poi__status[data-open=true]{color:var(--kpt-color-success)}.kpt-map-poi__status-hint{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__chevron{margin-inline-start:auto;color:var(--kpt-color-on-surface-muted);transition:rotate var(--kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__hours-toggle[aria-expanded=true] .kpt-map-poi__chevron{rotate:180deg}.kpt-map-poi__week{display:grid;grid-template-columns:auto 1fr;gap:var(--kpt-space-1) var(--kpt-space-4);margin:0;padding:0 0 var(--kpt-space-3) calc(1em + var(--kpt-space-3))}.kpt-map-poi__week-day{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__week-hours{display:flex;flex-direction:column;margin:0}.kpt-map-poi__week-closed{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__rows{margin:0;padding:0;list-style:none}.kpt-map-poi__row{display:flex;gap:var(--kpt-space-3);align-items:center;padding:var(--kpt-space-2) var(--kpt-space-2) var(--kpt-space-2) 0;margin-inline-start:calc(var(--kpt-space-2) * -1);padding-inline-start:var(--kpt-space-2);border-top:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-radius:var(--kpt-radius-sm)}.kpt-map-poi__row:hover,.kpt-map-poi__row:focus-within{background:var(--kpt-map-poi-row-hover, var(--kpt-color-surface-hover))}.kpt-map-poi__row-icon{flex:none;color:var(--kpt-color-primary)}.kpt-map-poi__row-value{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}.kpt-map-poi__row-link{color:inherit;text-decoration:none}.kpt-map-poi__row-link:hover{text-decoration:underline}.kpt-map-poi__fact-label{color:var(--kpt-color-on-surface-muted)}.kpt-map-poi__fact-label:after{content:\" \\b7 \"}.kpt-map-poi__row-actions{display:flex;flex:none;gap:var(--kpt-space-1)}.kpt-map-poi__row-action{display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;color:var(--kpt-color-on-surface-muted);background:none;border:0;border-radius:var(--kpt-radius-sm);cursor:pointer}.kpt-map-poi__row-action:hover{color:var(--kpt-color-primary);background:var(--kpt-color-surface-selected)}.kpt-map-poi__row-action:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}@media(hover:hover)and (pointer:fine){.kpt-map-poi__row-actions{opacity:0;transition:opacity var(--kpt-motion-duration-fast, .15s) ease}.kpt-map-poi__row:hover .kpt-map-poi__row-actions,.kpt-map-poi__row:focus-within .kpt-map-poi__row-actions{opacity:1}}.kpt-map-poi__live{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"] }]
4583
3297
  }], null, { poi: [{ type: i0.Input, args: [{ isSignal: true, alias: "poi", required: true }] }], distance: [{ type: i0.Input, args: [{ isSignal: true, alias: "distance", required: false }] }], category: [{ type: i0.Input, args: [{ isSignal: true, alias: "category", required: false }] }], primaryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryLabel", required: false }] }], showShare: [{ type: i0.Input, args: [{ isSignal: true, alias: "showShare", required: false }] }], showFavourite: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFavourite", required: false }] }], favourite: [{ type: i0.Input, args: [{ isSignal: true, alias: "favourite", required: false }] }, { type: i0.Output, args: ["favouriteChange"] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], hoursExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoursExpanded", required: false }] }, { type: i0.Output, args: ["hoursExpandedChange"] }], primaryAction: [{ type: i0.Output, args: ["primaryAction"] }], closed: [{ type: i0.Output, args: ["closed"] }], shared: [{ type: i0.Output, args: ["shared"] }] }); })();
4584
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapPoiCard, { className: "KptMapPoiCard", filePath: "lib/map-poi-card.component.ts", lineNumber: 72 }); })();
3298
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapPoiCard, { className: "KptMapPoiCard", filePath: "lib/map-poi-card.component.ts", lineNumber: 69 }); })();
4585
3299
 
4586
3300
  /*
4587
3301
  * Public API — @konce-pt/angular/map
4588
- * Mapa z konfigurowalnymi znacznikami (Leaflet + OpenStreetMap). Rdzeń obliczeń: pure TS.
3302
+ * Mapa z konfigurowalnymi znacznikami (Leaflet + OpenStreetMap). Rdzeń obliczeń i adaptery
3303
+ * silników mieszkają w `@konce-pt/map` — paczce bez frameworka, wspólnej z portem Reacta;
3304
+ * tu tylko re-eksport, żeby publiczne API subpathu się nie zmieniło.
4589
3305
  *
4590
3306
  * Wymaga zależności opcjonalnej `leaflet` i dołączenia `leaflet/dist/leaflet.css`
4591
3307
  * po stronie aplikacji.
@@ -4595,5 +3311,5 @@ class KptMapPoiCard {
4595
3311
  * Generated bundle index. Do not edit.
4596
3312
  */
4597
3313
 
4598
- export { KPT_CLUSTER_DEFAULTS, KPT_EARTH_RADIUS, KPT_MAP_COUNTRIES, KPT_MAP_DEFAULT_ATTRIBUTION, KPT_MAP_DEFAULT_COUNTRY, KPT_MAP_DEFAULT_TILE_URL, KPT_MAP_ENGINE, KPT_MAP_GEOCODER, KPT_MAP_GEOLOCATION, KPT_MAX_LATITUDE, KPT_MIN_FIT_SPAN, KPT_TILE_SIZE, KptBrowserGeolocation, KptMap, KptMapCreateDialog, KptMapCreateFormTemplate, KptMapGeocoderStub, KptMapGeolocationFailure, KptMapGeolocationStub, KptMapHoursField, KptMapImageField, KptMapLegend, KptMapPoiCard, KptMapPopupTemplate, KptMapSearch, KptNominatimGeocoder, boundsCenter, boundsContain, boundsOf, centerOfBounds, clampLat, clusterLocations, clusterTier, colorVarOf, contrastVarOf, countByCategory, countryCodes, countryPreset, createGoogleEngine, createLeafletEngine, dominantCategory, emptyWeek, foregroundVarOf, formatDistance, formatLatLng, formatTime, groupWeek, haversine, isAlwaysOpen, isCluster, isFittableBounds, isOpenAt, isSplittable, isoWeekday, loadGoogleMapsApi, metersPerPixel, nextChange, normalizeLng, padBounds, parseTime, project, provideKptGoogleMap, provideKptLeafletMap, provideKptMapGeocoderStub, provideKptMapGeolocationStub, provideKptNominatimGeocoder, renderClusterHtml, renderMarkerHtml, renderUserMarkerHtml, resolveCountryView, unproject, weekdayName, worldSize, zoomForBounds };
3314
+ export { KPT_MAP_ENGINE, KPT_MAP_GEOCODER, KPT_MAP_GEOLOCATION, KptBrowserGeolocation, KptMap, KptMapCreateDialog, KptMapCreateFormTemplate, KptMapGeocoderStub, KptMapGeolocationFailure, KptMapGeolocationStub, KptMapHoursField, KptMapImageField, KptMapLegend, KptMapPoiCard, KptMapPopupTemplate, KptMapSearch, KptNominatimGeocoder, provideKptGoogleMap, provideKptLeafletMap, provideKptMapGeocoderStub, provideKptMapGeolocationStub, provideKptNominatimGeocoder };
4599
3315
  //# sourceMappingURL=konce-pt-angular-map.mjs.map