@konce-pt/angular 0.7.0 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +317 -1
- package/fesm2022/konce-pt-angular-map.mjs +4568 -0
- package/fesm2022/konce-pt-angular-map.mjs.map +1 -0
- package/fesm2022/konce-pt-angular.mjs +515 -77
- package/fesm2022/konce-pt-angular.mjs.map +1 -1
- package/map/src/lib/llms.txt +741 -0
- package/package.json +17 -4
- package/src/lib/app-shell/llms.txt +70 -10
- package/src/lib/breakpoint/llms.txt +45 -0
- package/src/lib/datepicker/llms.txt +3 -0
- package/src/lib/dialog/llms.txt +62 -21
- package/src/lib/form-field/llms.txt +19 -1
- package/src/lib/icon/llms.txt +2 -1
- package/src/lib/input/llms.txt +1 -1
- package/src/lib/select/llms.txt +9 -0
- package/src/lib/sidenav/llms.txt +29 -20
- package/src/lib/toolbar/llms.txt +31 -23
- package/types/konce-pt-angular-map.d.ts +1585 -0
- package/types/konce-pt-angular-map.d.ts.map +1 -0
- package/types/konce-pt-angular.d.ts +304 -20
- package/types/konce-pt-angular.d.ts.map +1 -1
|
@@ -0,0 +1,4568 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
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
|
+
import { KptI18n, KptCheckbox, KptDatepicker, KptIcon, KptIconButton, KptFileUpload, KptInput, KptFormField, KptTextarea, KptInputNumber, KptSelect, KptButton, KptIconRegistry, KptDialog } from '@konce-pt/angular';
|
|
5
|
+
import { form, required, min, max, email, pattern, validate, submit, FormRoot, FormField } from '@angular/forms/signals';
|
|
6
|
+
import { of, Observable } from 'rxjs';
|
|
7
|
+
|
|
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 = '© <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
|
+
/** Rola koloru → zmienna CSS. Gotowe `var(--kpt-*)` przechodzi bez zmian. */
|
|
326
|
+
function colorVarOf(color) {
|
|
327
|
+
return ROLE_VARS[color] ?? color;
|
|
328
|
+
}
|
|
329
|
+
/** Kolor treści na tle `color` — dla ról znany, dla własnych zmiennych fallback na biel tokenową. */
|
|
330
|
+
function contrastVarOf(color) {
|
|
331
|
+
return ROLE_CONTRAST_VARS[color] ?? 'var(--kpt-color-text-inverse)';
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Kolor **glifu stojącego wprost na powierzchni**, bez własnego tła.
|
|
335
|
+
*
|
|
336
|
+
* Różni się od `colorVarOf()` tylko dla roli `muted`: `--kpt-color-muted` to neutral-100,
|
|
337
|
+
* czyli token tła, a nie treści — goły znak w tym kolorze na jasnej powierzchni jest
|
|
338
|
+
* niewidoczny. Bierzemy więc czytelny szary tekstowy. Pozostałe role są nasycone
|
|
339
|
+
* i jako treść czytają się bez zmian.
|
|
340
|
+
*/
|
|
341
|
+
const ROLE_FOREGROUND_VARS = {
|
|
342
|
+
...ROLE_VARS,
|
|
343
|
+
muted: 'var(--kpt-color-on-surface-muted)',
|
|
344
|
+
};
|
|
345
|
+
function foregroundVarOf(color) {
|
|
346
|
+
return ROLE_FOREGROUND_VARS[color] ?? color;
|
|
347
|
+
}
|
|
348
|
+
/** Ucieka znaki, które rozbiłyby atrybut albo wstrzyknęły znacznik. */
|
|
349
|
+
function escapeHtml(value) {
|
|
350
|
+
return value
|
|
351
|
+
.replace(/&/g, '&')
|
|
352
|
+
.replace(/</g, '<')
|
|
353
|
+
.replace(/>/g, '>')
|
|
354
|
+
.replace(/"/g, '"')
|
|
355
|
+
.replace(/'/g, ''');
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Pinezka o wysokości `--kpt-map-marker-size`, zakotwiczona czubkiem w punkcie.
|
|
359
|
+
* `viewBox` 24×32: 24 szerokości koła + ogon do 32.
|
|
360
|
+
*/
|
|
361
|
+
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';
|
|
362
|
+
function renderMarkerHtml(input) {
|
|
363
|
+
const shape = input.shape ?? 'pin';
|
|
364
|
+
const color = colorVarOf(input.color);
|
|
365
|
+
const contrast = contrastVarOf(input.color);
|
|
366
|
+
const label = escapeHtml(input.label);
|
|
367
|
+
const cls = `kpt-map__marker kpt-map__marker--${shape}${input.selected ? ' kpt-map__marker--selected' : ''}`;
|
|
368
|
+
const glyph = input.iconSvgInner
|
|
369
|
+
? `<svg class="kpt-map__marker-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"` +
|
|
370
|
+
` stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${input.iconSvgInner}</svg>`
|
|
371
|
+
: '';
|
|
372
|
+
const body = shape === 'pin'
|
|
373
|
+
? `<svg class="kpt-map__marker-shape" viewBox="0 0 24 32" aria-hidden="true">` +
|
|
374
|
+
`<path d="${PIN_PATH}" /></svg><span class="kpt-map__marker-glyph">${glyph}</span>`
|
|
375
|
+
: `<span class="kpt-map__marker-glyph">${glyph}</span>`;
|
|
376
|
+
return (`<div class="${cls}" role="img" aria-label="${label}" title="${label}"` +
|
|
377
|
+
` style="--_kpt-map-marker-bg:${color};--_kpt-map-marker-fg:${contrast}">${body}</div>`);
|
|
378
|
+
}
|
|
379
|
+
/** Koło z licznikiem; rozmiar rośnie schodkowo z liczebnością grupy. */
|
|
380
|
+
function renderClusterHtml(input) {
|
|
381
|
+
const color = colorVarOf(input.color);
|
|
382
|
+
const contrast = contrastVarOf(input.color);
|
|
383
|
+
const label = escapeHtml(input.label);
|
|
384
|
+
const tier = clusterTier(input.count);
|
|
385
|
+
return (`<div class="kpt-map__cluster kpt-map__cluster--${tier}" role="img" aria-label="${label}" title="${label}"` +
|
|
386
|
+
` style="--_kpt-map-cluster-bg:${color};--_kpt-map-cluster-fg:${contrast}">` +
|
|
387
|
+
`<span class="kpt-map__cluster-count">${formatCount(input.count)}</span></div>`);
|
|
388
|
+
}
|
|
389
|
+
/** Próg wielkości grupy — trzy stopnie wystarczą, żeby zobaczyć różnicę bez skalowania ciągłego. */
|
|
390
|
+
function clusterTier(count) {
|
|
391
|
+
if (count < 10)
|
|
392
|
+
return 'sm';
|
|
393
|
+
if (count < 100)
|
|
394
|
+
return 'md';
|
|
395
|
+
return 'lg';
|
|
396
|
+
}
|
|
397
|
+
/** Powyżej 999 licznik przestaje się mieścić — skracamy. */
|
|
398
|
+
function formatCount(count) {
|
|
399
|
+
return count > 999 ? '999+' : String(count);
|
|
400
|
+
}
|
|
401
|
+
/** Znacznik pozycji użytkownika — ikona podawana z zewnątrz, jak przy zwykłym znaczniku. */
|
|
402
|
+
function renderUserMarkerHtml(input) {
|
|
403
|
+
const label = escapeHtml(input.label);
|
|
404
|
+
const glyph = input.iconSvgInner
|
|
405
|
+
? `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"` +
|
|
406
|
+
` stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${input.iconSvgInner}</svg>`
|
|
407
|
+
: '';
|
|
408
|
+
return `<div class="kpt-map__user-marker" role="img" aria-label="${label}" title="${label}">${glyph}</div>`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/*
|
|
412
|
+
* Typy rdzenia mapy — pure TS, zero Angulara i zero silnika mapowego.
|
|
413
|
+
* Ten plik przenosi się do portu React bez zmian.
|
|
414
|
+
*/
|
|
415
|
+
/** Zawęża wynik `clusterLocations` do grupy. */
|
|
416
|
+
function isCluster(entry) {
|
|
417
|
+
return entry.count !== undefined;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/*
|
|
421
|
+
* Godziny otwarcia — pure TS, bez Angulara i bez silnika mapowego.
|
|
422
|
+
* Ten plik przenosi się do portu React bez zmian.
|
|
423
|
+
*
|
|
424
|
+
* Umowa o czasie: `HH:MM` w czasie lokalnym miejsca, a porównania robimy
|
|
425
|
+
* na lokalnym czasie przeglądarki. Miejsce w innej strefie niż użytkownik
|
|
426
|
+
* pokaże więc status wg zegara użytkownika — to świadome uproszczenie,
|
|
427
|
+
* bo model nie niesie strefy. Kto potrzebuje inaczej, liczy status u siebie.
|
|
428
|
+
*/
|
|
429
|
+
/** Minuta doby dla `HH:MM`; `24:00` to 1440. `null`, gdy zapis jest nieprawidłowy. */
|
|
430
|
+
function parseTime(value) {
|
|
431
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
|
|
432
|
+
if (!match)
|
|
433
|
+
return null;
|
|
434
|
+
const hours = Number(match[1]);
|
|
435
|
+
const minutes = Number(match[2]);
|
|
436
|
+
if (minutes > 59)
|
|
437
|
+
return null;
|
|
438
|
+
const total = hours * 60 + minutes;
|
|
439
|
+
return total > 1440 ? null : total;
|
|
440
|
+
}
|
|
441
|
+
/** `HH:MM` z minuty doby. 1440 wraca jako `24:00`, nie `00:00`. */
|
|
442
|
+
function formatTime(minutes) {
|
|
443
|
+
const m = Math.max(0, Math.min(1440, Math.round(minutes)));
|
|
444
|
+
return `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`;
|
|
445
|
+
}
|
|
446
|
+
/** Dzień tygodnia wg ISO (1 = poniedziałek) z natywnej daty. */
|
|
447
|
+
function isoWeekday(date) {
|
|
448
|
+
const day = date.getDay();
|
|
449
|
+
return (day === 0 ? 7 : day);
|
|
450
|
+
}
|
|
451
|
+
function previousDay(day) {
|
|
452
|
+
return (day === 1 ? 7 : day - 1);
|
|
453
|
+
}
|
|
454
|
+
function dayOf(hours, day) {
|
|
455
|
+
return hours.find((entry) => entry.day === day);
|
|
456
|
+
}
|
|
457
|
+
/** Przedział przechodzi przez północ, gdy kończy się nie później niż zaczyna. */
|
|
458
|
+
function crossesMidnight(range) {
|
|
459
|
+
const from = parseTime(range.from);
|
|
460
|
+
const to = parseTime(range.to);
|
|
461
|
+
return from !== null && to !== null && to <= from;
|
|
462
|
+
}
|
|
463
|
+
function usableRanges(entry) {
|
|
464
|
+
if (!entry || entry.closed)
|
|
465
|
+
return [];
|
|
466
|
+
return entry.ranges.filter((r) => parseTime(r.from) !== null && parseTime(r.to) !== null);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Czy miejsce jest otwarte w danym momencie.
|
|
470
|
+
*
|
|
471
|
+
* Sprawdza dwa dni: bieżący oraz poprzedni, bo przedział przez północ
|
|
472
|
+
* (`22:00 – 01:00`) obejmuje wczesne godziny dnia następnego.
|
|
473
|
+
*/
|
|
474
|
+
function isOpenAt(hours, date) {
|
|
475
|
+
if (!hours || hours.length === 0)
|
|
476
|
+
return false;
|
|
477
|
+
const minute = date.getHours() * 60 + date.getMinutes();
|
|
478
|
+
const today = isoWeekday(date);
|
|
479
|
+
for (const range of usableRanges(dayOf(hours, today))) {
|
|
480
|
+
const from = parseTime(range.from);
|
|
481
|
+
const to = parseTime(range.to);
|
|
482
|
+
if (to > from ? minute >= from && minute < to : minute >= from)
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
for (const range of usableRanges(dayOf(hours, previousDay(today)))) {
|
|
486
|
+
if (!crossesMidnight(range))
|
|
487
|
+
continue;
|
|
488
|
+
if (minute < parseTime(range.to))
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
/** Czy rozkład to pełna doba przez siedem dni — wtedy karta pisze po prostu „24/7". */
|
|
494
|
+
function isAlwaysOpen(hours) {
|
|
495
|
+
if (!hours)
|
|
496
|
+
return false;
|
|
497
|
+
for (let day = 1; day <= 7; day++) {
|
|
498
|
+
const ranges = usableRanges(dayOf(hours, day));
|
|
499
|
+
const full = ranges.some((r) => parseTime(r.from) === 0 && parseTime(r.to) === 1440);
|
|
500
|
+
if (!full)
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
/** Najbliższa zmiana stanu otwarte/zamknięte albo `null`, gdy w ciągu tygodnia jej nie ma. */
|
|
506
|
+
function nextChange(hours, date) {
|
|
507
|
+
if (!hours || hours.length === 0)
|
|
508
|
+
return null;
|
|
509
|
+
const now = isOpenAt(hours, date);
|
|
510
|
+
// Granice przedziałów na najbliższe osiem dób; pierwsza różnica wygrywa.
|
|
511
|
+
for (let offset = 0; offset <= 8; offset++) {
|
|
512
|
+
const base = new Date(date);
|
|
513
|
+
base.setHours(0, 0, 0, 0);
|
|
514
|
+
base.setDate(base.getDate() + offset);
|
|
515
|
+
const day = isoWeekday(base);
|
|
516
|
+
const minutes = new Set();
|
|
517
|
+
for (const range of usableRanges(dayOf(hours, day))) {
|
|
518
|
+
minutes.add(parseTime(range.from));
|
|
519
|
+
const to = parseTime(range.to);
|
|
520
|
+
if (to > parseTime(range.from))
|
|
521
|
+
minutes.add(to);
|
|
522
|
+
}
|
|
523
|
+
for (const range of usableRanges(dayOf(hours, previousDay(day)))) {
|
|
524
|
+
if (crossesMidnight(range))
|
|
525
|
+
minutes.add(parseTime(range.to));
|
|
526
|
+
}
|
|
527
|
+
for (const minute of [...minutes].sort((a, b) => a - b)) {
|
|
528
|
+
const at = new Date(base);
|
|
529
|
+
at.setMinutes(minute);
|
|
530
|
+
if (at.getTime() <= date.getTime())
|
|
531
|
+
continue;
|
|
532
|
+
const open = isOpenAt(hours, at);
|
|
533
|
+
if (open !== now)
|
|
534
|
+
return { open, at };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
/** Skrócona nazwa dnia z `Intl` — bez słownika, więc działa dla dowolnego locale. */
|
|
540
|
+
function weekdayName(day, locale) {
|
|
541
|
+
// 2024-01-01 był poniedziałkiem, więc dzień ISO wypada na tę samą liczbę w miesiącu.
|
|
542
|
+
const date = new Date(Date.UTC(2024, 0, day));
|
|
543
|
+
return new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' }).format(date);
|
|
544
|
+
}
|
|
545
|
+
function signatureOf(entry) {
|
|
546
|
+
const ranges = usableRanges(entry);
|
|
547
|
+
if (ranges.length === 0)
|
|
548
|
+
return 'closed';
|
|
549
|
+
return ranges.map((r) => `${parseTime(r.from)}-${parseTime(r.to)}`).join(',');
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Rozkład tygodnia z sąsiadującymi dniami sklejonymi w jeden wiersz.
|
|
553
|
+
*
|
|
554
|
+
* Niedziela i poniedziałek się nie sklejają, choć w tablicy sąsiadują — tydzień
|
|
555
|
+
* czyta się liniowo od poniedziałku i zawijanie byłoby mylące.
|
|
556
|
+
*/
|
|
557
|
+
function groupWeek(hours, locale, separator = ' – ') {
|
|
558
|
+
if (!hours || hours.length === 0)
|
|
559
|
+
return [];
|
|
560
|
+
const groups = [];
|
|
561
|
+
let signature = null;
|
|
562
|
+
for (let day = 1; day <= 7; day++) {
|
|
563
|
+
const entry = dayOf(hours, day);
|
|
564
|
+
const current = signatureOf(entry);
|
|
565
|
+
const previous = groups[groups.length - 1];
|
|
566
|
+
if (current === signature && previous) {
|
|
567
|
+
previous.days.push(day);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
signature = current;
|
|
571
|
+
const ranges = usableRanges(entry);
|
|
572
|
+
groups.push({
|
|
573
|
+
label: '',
|
|
574
|
+
days: [day],
|
|
575
|
+
closed: ranges.length === 0,
|
|
576
|
+
ranges: ranges.map((r) => `${r.from}${separator}${r.to}`),
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
for (const group of groups) {
|
|
580
|
+
const first = weekdayName(group.days[0], locale);
|
|
581
|
+
const last = weekdayName(group.days[group.days.length - 1], locale);
|
|
582
|
+
group.label = group.days.length === 1 ? first : `${first}–${last}`;
|
|
583
|
+
}
|
|
584
|
+
return groups;
|
|
585
|
+
}
|
|
586
|
+
/** Siedem dni oznaczonych jako zamknięte — wartość startowa pola `hours` w formularzu. */
|
|
587
|
+
function emptyWeek() {
|
|
588
|
+
return Array.from({ length: 7 }, (_, i) => ({
|
|
589
|
+
day: (i + 1),
|
|
590
|
+
closed: true,
|
|
591
|
+
ranges: [],
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const KPT_MAP_GEOCODER = new InjectionToken('KPT_MAP_GEOCODER');
|
|
596
|
+
/** Publiczna instancja OSM. Ma twardą politykę użycia — patrz JSDoc providera. */
|
|
597
|
+
const NOMINATIM_URL = 'https://nominatim.openstreetmap.org';
|
|
598
|
+
/** Polityka publicznej instancji Nominatim: maksymalnie jedno zapytanie na sekundę. */
|
|
599
|
+
const NOMINATIM_MIN_INTERVAL_MS = 1000;
|
|
600
|
+
/**
|
|
601
|
+
* Adapter Nominatim (OpenStreetMap). Kolejkuje zapytania z odstępem sekundy, żeby
|
|
602
|
+
* nie łamać polityki użycia, i przerywa je, gdy subskrybent zniknie.
|
|
603
|
+
*/
|
|
604
|
+
class KptNominatimGeocoder {
|
|
605
|
+
constructor(options = {}) {
|
|
606
|
+
this.options = options;
|
|
607
|
+
/** Ogon kolejki — każde kolejne zapytanie czeka na swoją kolej. */
|
|
608
|
+
this.next = Promise.resolve();
|
|
609
|
+
/** Kiedy wystrzeliło poprzednie zapytanie. `0` = jeszcze żadne. */
|
|
610
|
+
this.lastAt = 0;
|
|
611
|
+
this.minInterval = options.minIntervalMs ?? (options.baseUrl ? 0 : NOMINATIM_MIN_INTERVAL_MS);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Rezerwuje miejsce w kolejce: czeka na poprzednie zapytanie, a potem tylko tyle,
|
|
615
|
+
* ile brakuje do pełnego odstępu od jego wystrzału.
|
|
616
|
+
*
|
|
617
|
+
* Odstęp liczony od zegara, a nie stałym `delay()` przed każdym zapytaniem —
|
|
618
|
+
* inaczej pierwsza podpowiedź po minucie bezczynności też płaciłaby sekundę, a przy
|
|
619
|
+
* podpowiadaniu w trakcie pisania to jest różnica między „działa" a „zacina się".
|
|
620
|
+
* Górna granica jednego zapytania na sekundę zostaje nienaruszona.
|
|
621
|
+
*
|
|
622
|
+
* Bez odstępu nie ma czego kolejkować: łańcuch obietnic zostaje nietknięty, żeby
|
|
623
|
+
* podpowiedzi z własnej instancji nie ustawiały się jedna za drugą bez powodu.
|
|
624
|
+
*/
|
|
625
|
+
slot() {
|
|
626
|
+
if (this.minInterval <= 0)
|
|
627
|
+
return Promise.resolve();
|
|
628
|
+
const mine = this.next.then(async () => {
|
|
629
|
+
const wait = this.lastAt + this.minInterval - Date.now();
|
|
630
|
+
if (wait > 0)
|
|
631
|
+
await delay(wait);
|
|
632
|
+
this.lastAt = Date.now();
|
|
633
|
+
});
|
|
634
|
+
this.next = mine;
|
|
635
|
+
return mine;
|
|
636
|
+
}
|
|
637
|
+
reverse(at, options) {
|
|
638
|
+
if (typeof fetch === 'undefined')
|
|
639
|
+
return of(null);
|
|
640
|
+
return new Observable((subscriber) => {
|
|
641
|
+
const controller = new AbortController();
|
|
642
|
+
const external = options?.signal;
|
|
643
|
+
external?.addEventListener('abort', () => controller.abort(), { once: true });
|
|
644
|
+
// Wpinamy się w kolejkę: własne miejsce zajmujemy od razu, ale start dopiero po odstępie.
|
|
645
|
+
const mine = this.slot();
|
|
646
|
+
mine
|
|
647
|
+
.then(() => {
|
|
648
|
+
if (controller.signal.aborted)
|
|
649
|
+
return null;
|
|
650
|
+
return this.fetchAddress(at, controller.signal, options?.language);
|
|
651
|
+
})
|
|
652
|
+
.then((address) => {
|
|
653
|
+
if (controller.signal.aborted)
|
|
654
|
+
return;
|
|
655
|
+
subscriber.next(address);
|
|
656
|
+
subscriber.complete();
|
|
657
|
+
})
|
|
658
|
+
.catch((error) => {
|
|
659
|
+
if (controller.signal.aborted || error?.name === 'AbortError')
|
|
660
|
+
return;
|
|
661
|
+
// Brak adresu to nie awaria formularza — pole zostaje puste.
|
|
662
|
+
subscriber.next(null);
|
|
663
|
+
subscriber.complete();
|
|
664
|
+
});
|
|
665
|
+
return () => controller.abort();
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Podpowiedzi adresowe. Wchodzi w **tę samą** kolejkę co `reverse()` — polityka Nominatim
|
|
670
|
+
* liczy jedno zapytanie na sekundę na całego klienta, a nie na metodę.
|
|
671
|
+
*/
|
|
672
|
+
search(query, options) {
|
|
673
|
+
const q = query.trim();
|
|
674
|
+
if (typeof fetch === 'undefined' || !q)
|
|
675
|
+
return of([]);
|
|
676
|
+
return new Observable((subscriber) => {
|
|
677
|
+
const controller = new AbortController();
|
|
678
|
+
const external = options?.signal;
|
|
679
|
+
external?.addEventListener('abort', () => controller.abort(), { once: true });
|
|
680
|
+
const mine = this.slot();
|
|
681
|
+
mine
|
|
682
|
+
.then(() => {
|
|
683
|
+
if (controller.signal.aborted)
|
|
684
|
+
return [];
|
|
685
|
+
return this.fetchPlaces(q, controller.signal, options);
|
|
686
|
+
})
|
|
687
|
+
.then((results) => {
|
|
688
|
+
if (controller.signal.aborted)
|
|
689
|
+
return;
|
|
690
|
+
subscriber.next(results);
|
|
691
|
+
subscriber.complete();
|
|
692
|
+
})
|
|
693
|
+
.catch((error) => {
|
|
694
|
+
if (controller.signal.aborted || error?.name === 'AbortError')
|
|
695
|
+
return;
|
|
696
|
+
// Nieudane zapytanie to pusta lista, a nie wywrotka pola wyszukiwania.
|
|
697
|
+
subscriber.next([]);
|
|
698
|
+
subscriber.complete();
|
|
699
|
+
});
|
|
700
|
+
return () => controller.abort();
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
async fetchPlaces(query, signal, options) {
|
|
704
|
+
const base = this.options.baseUrl ?? NOMINATIM_URL;
|
|
705
|
+
const url = new URL('/search', base);
|
|
706
|
+
url.searchParams.set('format', 'jsonv2');
|
|
707
|
+
url.searchParams.set('q', query);
|
|
708
|
+
url.searchParams.set('addressdetails', '1');
|
|
709
|
+
url.searchParams.set('limit', String(options?.limit ?? 5));
|
|
710
|
+
const box = options?.viewbox;
|
|
711
|
+
if (box) {
|
|
712
|
+
// Nominatim czyta viewbox jako `lewo,góra,prawo,dół`, czyli lon/lat, nie lat/lon.
|
|
713
|
+
url.searchParams.set('viewbox', [box.west, box.north, box.east, box.south].join(','));
|
|
714
|
+
}
|
|
715
|
+
const countries = options?.countryCodes;
|
|
716
|
+
if (countries?.length) {
|
|
717
|
+
url.searchParams.set('countrycodes', countries.join(',').toLowerCase());
|
|
718
|
+
}
|
|
719
|
+
const lang = options?.language ?? this.options.language;
|
|
720
|
+
const headers = { Accept: 'application/json' };
|
|
721
|
+
if (lang)
|
|
722
|
+
headers['Accept-Language'] = lang;
|
|
723
|
+
const response = await fetch(url, { signal, headers });
|
|
724
|
+
if (!response.ok)
|
|
725
|
+
return [];
|
|
726
|
+
const data = (await response.json());
|
|
727
|
+
return Array.isArray(data) ? data.map(mapPlace).filter((r) => r !== null) : [];
|
|
728
|
+
}
|
|
729
|
+
async fetchAddress(at, signal, language) {
|
|
730
|
+
const base = this.options.baseUrl ?? NOMINATIM_URL;
|
|
731
|
+
const url = new URL('/reverse', base);
|
|
732
|
+
url.searchParams.set('format', 'jsonv2');
|
|
733
|
+
url.searchParams.set('lat', String(at.lat));
|
|
734
|
+
url.searchParams.set('lon', String(at.lng));
|
|
735
|
+
url.searchParams.set('zoom', String(this.options.zoom ?? 18));
|
|
736
|
+
url.searchParams.set('addressdetails', '1');
|
|
737
|
+
const lang = language ?? this.options.language;
|
|
738
|
+
const headers = { Accept: 'application/json' };
|
|
739
|
+
if (lang)
|
|
740
|
+
headers['Accept-Language'] = lang;
|
|
741
|
+
const response = await fetch(url, { signal, headers });
|
|
742
|
+
if (!response.ok)
|
|
743
|
+
return null;
|
|
744
|
+
const data = (await response.json());
|
|
745
|
+
return data.address ? mapNominatim(data.address, data.display_name) : null;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function mapNominatim(a, label) {
|
|
749
|
+
const street = a.road;
|
|
750
|
+
const houseNumber = a.house_number;
|
|
751
|
+
return {
|
|
752
|
+
street,
|
|
753
|
+
houseNumber,
|
|
754
|
+
// Jedno pole „Adres" chce ulicy z numerem; bez ulicy nie ma czego sklejać.
|
|
755
|
+
address: street ? [street, houseNumber].filter(Boolean).join(' ') : undefined,
|
|
756
|
+
// Nominatim rozrzuca miejscowość po kilku kluczach zależnie od jej wielkości.
|
|
757
|
+
city: a.city ?? a.town ?? a.village ?? a.municipality ?? a.hamlet,
|
|
758
|
+
postcode: a.postcode,
|
|
759
|
+
state: a.state,
|
|
760
|
+
county: a.county,
|
|
761
|
+
country: a.country,
|
|
762
|
+
countryCode: a.country_code?.toUpperCase(),
|
|
763
|
+
label,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
function mapPlace(place, index) {
|
|
767
|
+
const lat = Number(place.lat);
|
|
768
|
+
const lng = Number(place.lon);
|
|
769
|
+
// Wynik bez współrzędnych jest bezużyteczny — nie ma dokąd polecieć.
|
|
770
|
+
if (!Number.isFinite(lat) || !Number.isFinite(lng))
|
|
771
|
+
return null;
|
|
772
|
+
return {
|
|
773
|
+
// `place_id` bywa niestabilne między instancjami, ale w obrębie jednej odpowiedzi
|
|
774
|
+
// wystarcza za klucz listy; `osm_id` i indeks są zapasem, gdy go w ogóle nie ma.
|
|
775
|
+
id: String(place.place_id ?? `${place.osm_type ?? 'p'}:${place.osm_id ?? index}`),
|
|
776
|
+
label: place.display_name ?? `${lat}, ${lng}`,
|
|
777
|
+
at: { lat, lng },
|
|
778
|
+
bounds: mapBoundingBox(place.boundingbox),
|
|
779
|
+
address: place.address ? mapNominatim(place.address, place.display_name) : undefined,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* `[south, north, west, east]` jako napisy → `KptMapBounds`.
|
|
784
|
+
*
|
|
785
|
+
* `undefined` dla zasięgu zbyt ciasnego, żeby go wpasowywać w kadr — pojedynczy adres
|
|
786
|
+
* dostaje wtedy `[searchZoom]` mapy i ląduje w powtarzalnym zbliżeniu, zamiast
|
|
787
|
+
* w przypadkowym, zależnym od tego, jak dostawca obrysował budynek. Regułę trzyma
|
|
788
|
+
* `isFittableBounds()` z rdzenia — jest testowana i wspólna dla wszystkich adapterów.
|
|
789
|
+
*/
|
|
790
|
+
function mapBoundingBox(box) {
|
|
791
|
+
if (!box || box.length !== 4)
|
|
792
|
+
return undefined;
|
|
793
|
+
const [south, north, west, east] = box.map(Number);
|
|
794
|
+
if (![south, north, west, east].every(Number.isFinite))
|
|
795
|
+
return undefined;
|
|
796
|
+
const bounds = { south, west, north, east };
|
|
797
|
+
return isFittableBounds(bounds) ? bounds : undefined;
|
|
798
|
+
}
|
|
799
|
+
function delay(ms) {
|
|
800
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Włącza podpowiadanie adresu z Nominatim (OpenStreetMap) w formularzu „dodaj lokalizację".
|
|
804
|
+
*
|
|
805
|
+
* **Polityka użycia.** Publiczna instancja `nominatim.openstreetmap.org` dopuszcza
|
|
806
|
+
* maksymalnie jedno zapytanie na sekundę, wymaga rozpoznawalnego `Referer`/`User-Agent`
|
|
807
|
+
* i zabrania masowego odpytywania; ruch może zostać zablokowany bez uprzedzenia,
|
|
808
|
+
* w szczególności komercyjny. Adapter sam trzyma odstęp sekundy, ale do produkcji
|
|
809
|
+
* podstaw własną instancję (`baseUrl`) albo komercyjnego dostawcę przez własny
|
|
810
|
+
* `KPT_MAP_GEOCODER`.
|
|
811
|
+
*
|
|
812
|
+
* Podanie `baseUrl` **zdejmuje sekundowy odstęp** (`minIntervalMs` domyślnie 0) — regulamin
|
|
813
|
+
* publicznej instancji nie obowiązuje na Twoim serwerze. Cudzą, współdzieloną instancję
|
|
814
|
+
* podstawiaj z jawnym `minIntervalMs`.
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* providers: [provideKptLeafletMap(), provideKptNominatimGeocoder({ language: 'pl' })]
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* // Własna instancja: bez odstępu, podpowiedzi lecą od razu.
|
|
821
|
+
* provideKptNominatimGeocoder({ baseUrl: 'https://geo.firma.local', language: 'pl' })
|
|
822
|
+
*/
|
|
823
|
+
function provideKptNominatimGeocoder(options = {}) {
|
|
824
|
+
return { provide: KPT_MAP_GEOCODER, useValue: new KptNominatimGeocoder(options) };
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Stały adres i stała lista podpowiedzi — Storybook i testy, bez ruchu sieciowego.
|
|
828
|
+
*
|
|
829
|
+
* Podpowiedzi filtruje po `label` bez znaków diakrytycznych, żeby „warszawa" trafiało
|
|
830
|
+
* w „Warszawa". Prawdziwy geokoder dopasowuje po swojemu — atrapa tylko udaje, że umie.
|
|
831
|
+
*/
|
|
832
|
+
class KptMapGeocoderStub {
|
|
833
|
+
constructor(address, results = [], options = {}) {
|
|
834
|
+
this.address = address;
|
|
835
|
+
this.results = results;
|
|
836
|
+
this.options = options;
|
|
837
|
+
}
|
|
838
|
+
reverse() {
|
|
839
|
+
return of(this.address);
|
|
840
|
+
}
|
|
841
|
+
search(query, options) {
|
|
842
|
+
const q = fold(query);
|
|
843
|
+
if (!q)
|
|
844
|
+
return of([]);
|
|
845
|
+
const hits = this.results.filter((r) => fold(r.label).includes(q)).slice(0, options?.limit ?? 5);
|
|
846
|
+
if (!this.options.deferCoordinates)
|
|
847
|
+
return of(hits);
|
|
848
|
+
// Współrzędne i zasięg zostają po stronie atrapy — pole ma po nie sięgnąć `resolve()`.
|
|
849
|
+
return of(hits.map(({ at: _at, bounds: _bounds, ...rest }) => rest));
|
|
850
|
+
}
|
|
851
|
+
resolve(suggestion) {
|
|
852
|
+
// Podpowiedź ze współrzędnymi wraca nietknięta — tak samo zachowa się prawdziwy adapter.
|
|
853
|
+
if (suggestion.at)
|
|
854
|
+
return of(suggestion);
|
|
855
|
+
return of(this.results.find((r) => r.id === suggestion.id) ?? null);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/** Bez znaków diakrytycznych i wielkości liter — tylko na potrzeby atrapy. */
|
|
859
|
+
function fold(value) {
|
|
860
|
+
return value.trim().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
|
|
861
|
+
}
|
|
862
|
+
function provideKptMapGeocoderStub(address, results = [], options = {}) {
|
|
863
|
+
return { provide: KPT_MAP_GEOCODER, useValue: new KptMapGeocoderStub(address, results, options) };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const _forTrack0$5 = ($index, $item) => $item.day;
|
|
867
|
+
function KptMapHoursField_For_3_Conditional_4_For_1_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
868
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
869
|
+
i0.ɵɵelementStart(0, "kpt-icon-button", 11);
|
|
870
|
+
i0.ɵɵlistener("click", function KptMapHoursField_For_3_Conditional_4_For_1_Conditional_5_Template_kpt_icon_button_click_0_listener() { i0.ɵɵrestoreView(_r6); const $index_r3 = i0.ɵɵnextContext().$index; const row_r4 = i0.ɵɵnextContext(2).$implicit; const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.removeRange(row_r4, $index_r3)); });
|
|
871
|
+
i0.ɵɵelement(1, "kpt-icon", 12);
|
|
872
|
+
i0.ɵɵelementEnd();
|
|
873
|
+
} if (rf & 2) {
|
|
874
|
+
const row_r4 = i0.ɵɵnextContext(3).$implicit;
|
|
875
|
+
const ctx_r4 = i0.ɵɵnextContext();
|
|
876
|
+
i0.ɵɵproperty("disabled", ctx_r4.locked(row_r4))("ariaLabel", ctx_r4.i18n.t("map.field.hoursRemoveRange"));
|
|
877
|
+
} }
|
|
878
|
+
function KptMapHoursField_For_3_Conditional_4_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
879
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
880
|
+
i0.ɵɵelementStart(0, "div", 6)(1, "kpt-datepicker", 8);
|
|
881
|
+
i0.ɵɵlistener("valueChange", function KptMapHoursField_For_3_Conditional_4_For_1_Template_kpt_datepicker_valueChange_1_listener($event) { const $index_r3 = i0.ɵɵrestoreView(_r2).$index; const row_r4 = i0.ɵɵnextContext(2).$implicit; const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.setTime(row_r4, $index_r3, "from", $event)); })("touch", function KptMapHoursField_For_3_Conditional_4_For_1_Template_kpt_datepicker_touch_1_listener() { i0.ɵɵrestoreView(_r2); const ctx_r4 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r4.touch.emit()); });
|
|
882
|
+
i0.ɵɵelementEnd();
|
|
883
|
+
i0.ɵɵelementStart(2, "span", 9);
|
|
884
|
+
i0.ɵɵtext(3, "\u2013");
|
|
885
|
+
i0.ɵɵelementEnd();
|
|
886
|
+
i0.ɵɵelementStart(4, "kpt-datepicker", 8);
|
|
887
|
+
i0.ɵɵlistener("valueChange", function KptMapHoursField_For_3_Conditional_4_For_1_Template_kpt_datepicker_valueChange_4_listener($event) { const $index_r3 = i0.ɵɵrestoreView(_r2).$index; const row_r4 = i0.ɵɵnextContext(2).$implicit; const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.setTime(row_r4, $index_r3, "to", $event)); })("touch", function KptMapHoursField_For_3_Conditional_4_For_1_Template_kpt_datepicker_touch_4_listener() { i0.ɵɵrestoreView(_r2); const ctx_r4 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r4.touch.emit()); });
|
|
888
|
+
i0.ɵɵelementEnd();
|
|
889
|
+
i0.ɵɵconditionalCreate(5, KptMapHoursField_For_3_Conditional_4_For_1_Conditional_5_Template, 2, 2, "kpt-icon-button", 10);
|
|
890
|
+
i0.ɵɵelementEnd();
|
|
891
|
+
} if (rf & 2) {
|
|
892
|
+
const range_r7 = ctx.$implicit;
|
|
893
|
+
const row_r4 = i0.ɵɵnextContext(2).$implicit;
|
|
894
|
+
const ctx_r4 = i0.ɵɵnextContext();
|
|
895
|
+
i0.ɵɵadvance();
|
|
896
|
+
i0.ɵɵproperty("minuteStep", ctx_r4.minuteStep)("value", range_r7.from)("disabled", ctx_r4.locked(row_r4))("ariaLabel", ctx_r4.i18n.t("map.field.hoursFrom") + " \u2014 " + row_r4.name);
|
|
897
|
+
i0.ɵɵadvance(3);
|
|
898
|
+
i0.ɵɵproperty("minuteStep", ctx_r4.minuteStep)("value", range_r7.to)("disabled", ctx_r4.locked(row_r4))("ariaLabel", ctx_r4.i18n.t("map.field.hoursTo") + " \u2014 " + row_r4.name);
|
|
899
|
+
i0.ɵɵadvance();
|
|
900
|
+
i0.ɵɵconditional(row_r4.ranges.length > 1 ? 5 : -1);
|
|
901
|
+
} }
|
|
902
|
+
function KptMapHoursField_For_3_Conditional_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
903
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
904
|
+
i0.ɵɵelementStart(0, "button", 13);
|
|
905
|
+
i0.ɵɵlistener("click", function KptMapHoursField_For_3_Conditional_4_Conditional_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const row_r4 = i0.ɵɵnextContext(2).$implicit; const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.addRange(row_r4)); });
|
|
906
|
+
i0.ɵɵelement(1, "kpt-icon", 14);
|
|
907
|
+
i0.ɵɵtext(2);
|
|
908
|
+
i0.ɵɵelementEnd();
|
|
909
|
+
} if (rf & 2) {
|
|
910
|
+
const row_r4 = i0.ɵɵnextContext(2).$implicit;
|
|
911
|
+
const ctx_r4 = i0.ɵɵnextContext();
|
|
912
|
+
i0.ɵɵproperty("disabled", ctx_r4.locked(row_r4));
|
|
913
|
+
i0.ɵɵadvance(2);
|
|
914
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r4.i18n.t("map.field.hoursAddRange"), " ");
|
|
915
|
+
} }
|
|
916
|
+
function KptMapHoursField_For_3_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
917
|
+
i0.ɵɵrepeaterCreate(0, KptMapHoursField_For_3_Conditional_4_For_1_Template, 6, 9, "div", 6, i0.ɵɵrepeaterTrackByIndex);
|
|
918
|
+
i0.ɵɵconditionalCreate(2, KptMapHoursField_For_3_Conditional_4_Conditional_2_Template, 3, 2, "button", 7);
|
|
919
|
+
} if (rf & 2) {
|
|
920
|
+
const row_r4 = i0.ɵɵnextContext().$implicit;
|
|
921
|
+
const ctx_r4 = i0.ɵɵnextContext();
|
|
922
|
+
i0.ɵɵrepeater(row_r4.ranges);
|
|
923
|
+
i0.ɵɵadvance(2);
|
|
924
|
+
i0.ɵɵconditional(row_r4.ranges.length < ctx_r4.maxRanges ? 2 : -1);
|
|
925
|
+
} }
|
|
926
|
+
function KptMapHoursField_For_3_Template(rf, ctx) { if (rf & 1) {
|
|
927
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
928
|
+
i0.ɵɵelementStart(0, "li", 2)(1, "span", 3);
|
|
929
|
+
i0.ɵɵtext(2);
|
|
930
|
+
i0.ɵɵelementEnd();
|
|
931
|
+
i0.ɵɵelementStart(3, "div", 4);
|
|
932
|
+
i0.ɵɵconditionalCreate(4, KptMapHoursField_For_3_Conditional_4_Template, 3, 1);
|
|
933
|
+
i0.ɵɵelementEnd();
|
|
934
|
+
i0.ɵɵelementStart(5, "kpt-checkbox", 5);
|
|
935
|
+
i0.ɵɵlistener("checkedChange", function KptMapHoursField_For_3_Template_kpt_checkbox_checkedChange_5_listener($event) { const row_r4 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r4 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r4.setClosed(row_r4, $event)); });
|
|
936
|
+
i0.ɵɵelementEnd()();
|
|
937
|
+
} if (rf & 2) {
|
|
938
|
+
const row_r4 = ctx.$implicit;
|
|
939
|
+
const ctx_r4 = i0.ɵɵnextContext();
|
|
940
|
+
i0.ɵɵattribute("data-locked", ctx_r4.locked(row_r4) ? "" : null);
|
|
941
|
+
i0.ɵɵadvance(2);
|
|
942
|
+
i0.ɵɵtextInterpolate(row_r4.name);
|
|
943
|
+
i0.ɵɵadvance(2);
|
|
944
|
+
i0.ɵɵconditional(!row_r4.closed ? 4 : -1);
|
|
945
|
+
i0.ɵɵadvance();
|
|
946
|
+
i0.ɵɵproperty("checked", row_r4.closed)("disabled", ctx_r4.locked(row_r4))("label", ctx_r4.i18n.t("map.field.hoursClosed"));
|
|
947
|
+
} }
|
|
948
|
+
/** Ile przedziałów na dobę dopuszczamy. Dwa pokrywają przerwę i dobę przez północ. */
|
|
949
|
+
const MAX_RANGES = 2;
|
|
950
|
+
/**
|
|
951
|
+
* Snap minut na tarczy zegara. Godziny otwarcia chodzą co kwadrans, a nie co minutę —
|
|
952
|
+
* krok 5 daje trafienie w 07:30 czy 22:45 bez celowania w pojedynczą minutę.
|
|
953
|
+
*/
|
|
954
|
+
const MINUTE_STEP = 5;
|
|
955
|
+
/**
|
|
956
|
+
* Pole „godziny otwarcia" formularza dodawania lokalizacji (`type: 'hours'` w schemie).
|
|
957
|
+
*
|
|
958
|
+
* Siedem wierszy od poniedziałku, każdy z parą godzin i znacznikiem „zamknięte".
|
|
959
|
+
* Skrót „tak samo przez cały tydzień" kopiuje poniedziałek na pozostałe dni — to
|
|
960
|
+
* najczęstszy przypadek, a bez niego wypełnienie formularza to czternaście pól.
|
|
961
|
+
*
|
|
962
|
+
* Nazwy dni idą z `Intl`, nie ze słownika `KptMessages`: rozkład tygodnia jest
|
|
963
|
+
* uniwersalny, a tłumaczenie nazw dni przeglądarka ma już zrobione.
|
|
964
|
+
*/
|
|
965
|
+
class KptMapHoursField {
|
|
966
|
+
constructor() {
|
|
967
|
+
this.value = model(emptyWeek(), /* @ts-ignore */
|
|
968
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
969
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
970
|
+
this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
971
|
+
this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
972
|
+
this.touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
973
|
+
this.touch = output();
|
|
974
|
+
this.i18n = inject(KptI18n);
|
|
975
|
+
this.maxRanges = MAX_RANGES;
|
|
976
|
+
this.minuteStep = MINUTE_STEP;
|
|
977
|
+
/** Skrót „tak samo przez cały tydzień" — trzymany w modelu, nie w wartości. */
|
|
978
|
+
this.sameAllWeek = model(false, /* @ts-ignore */
|
|
979
|
+
...(ngDevMode ? [{ debugName: "sameAllWeek" }] : /* istanbul ignore next */ []));
|
|
980
|
+
this.rows = computed(() => {
|
|
981
|
+
const locale = this.i18n.locale();
|
|
982
|
+
const week = this.value();
|
|
983
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
984
|
+
const day = (i + 1);
|
|
985
|
+
const entry = week.find((d) => d.day === day);
|
|
986
|
+
return {
|
|
987
|
+
day,
|
|
988
|
+
name: weekdayName(day, locale),
|
|
989
|
+
closed: entry?.closed ?? !entry,
|
|
990
|
+
ranges: (entry?.ranges ?? []).map((r) => ({ from: r.from, to: r.to })),
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
}, /* @ts-ignore */
|
|
994
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
995
|
+
}
|
|
996
|
+
/** Dni poza poniedziałkiem są zablokowane, gdy tydzień jest jednakowy. */
|
|
997
|
+
locked(row) {
|
|
998
|
+
return this.disabled() || this.readonly() || (this.sameAllWeek() && row.day !== 1);
|
|
999
|
+
}
|
|
1000
|
+
onSameAllWeek(same) {
|
|
1001
|
+
this.sameAllWeek.set(same);
|
|
1002
|
+
if (!same)
|
|
1003
|
+
return;
|
|
1004
|
+
const monday = this.rows()[0];
|
|
1005
|
+
this.write((week) => week.map((day) => day.day === 1
|
|
1006
|
+
? day
|
|
1007
|
+
: { day: day.day, closed: monday.closed, ranges: monday.ranges.map((r) => ({ ...r })) }));
|
|
1008
|
+
}
|
|
1009
|
+
setClosed(row, closed) {
|
|
1010
|
+
this.patch(row.day, (day) => ({
|
|
1011
|
+
...day,
|
|
1012
|
+
closed,
|
|
1013
|
+
// Przy odznaczeniu dajemy jeden pusty przedział, żeby było co wypełnić.
|
|
1014
|
+
ranges: closed ? [] : day.ranges.length ? day.ranges : [{ from: '', to: '' }],
|
|
1015
|
+
}));
|
|
1016
|
+
}
|
|
1017
|
+
setTime(row, index, edge, time) {
|
|
1018
|
+
this.patch(row.day, (day) => {
|
|
1019
|
+
const ranges = day.ranges.map((r) => ({ ...r }));
|
|
1020
|
+
while (ranges.length <= index)
|
|
1021
|
+
ranges.push({ from: '', to: '' });
|
|
1022
|
+
ranges[index][edge] = time;
|
|
1023
|
+
return { ...day, closed: false, ranges };
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
addRange(row) {
|
|
1027
|
+
this.patch(row.day, (day) => ({
|
|
1028
|
+
...day,
|
|
1029
|
+
closed: false,
|
|
1030
|
+
ranges: [...day.ranges.map((r) => ({ ...r })), { from: '', to: '' }],
|
|
1031
|
+
}));
|
|
1032
|
+
}
|
|
1033
|
+
removeRange(row, index) {
|
|
1034
|
+
this.patch(row.day, (day) => ({
|
|
1035
|
+
...day,
|
|
1036
|
+
ranges: day.ranges.filter((_, i) => i !== index).map((r) => ({ ...r })),
|
|
1037
|
+
}));
|
|
1038
|
+
}
|
|
1039
|
+
/** Zmiana jednego dnia; przy jednakowym tygodniu poniedziałek rozlewa się na resztę. */
|
|
1040
|
+
patch(day, change) {
|
|
1041
|
+
this.write((week) => {
|
|
1042
|
+
const next = week.map((entry) => (entry.day === day ? change(entry) : entry));
|
|
1043
|
+
if (!this.sameAllWeek() || day !== 1)
|
|
1044
|
+
return next;
|
|
1045
|
+
const monday = next[0];
|
|
1046
|
+
return next.map((entry) => entry.day === 1
|
|
1047
|
+
? entry
|
|
1048
|
+
: { day: entry.day, closed: monday.closed, ranges: monday.ranges.map((r) => ({ ...r })) });
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
/** Zapis zawsze przez pełny, uporządkowany tydzień — model nie ma dziur. */
|
|
1052
|
+
write(change) {
|
|
1053
|
+
const current = this.value();
|
|
1054
|
+
const base = emptyWeek().map((day) => current.find((d) => d.day === day.day) ?? day);
|
|
1055
|
+
this.value.set(change(base));
|
|
1056
|
+
this.touch.emit();
|
|
1057
|
+
}
|
|
1058
|
+
static { this.ɵfac = function KptMapHoursField_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapHoursField)(); }; }
|
|
1059
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapHoursField, selectors: [["kpt-map-hours-field"]], hostAttrs: [1, "kpt-map-hours-field"], inputs: { value: [1, "value"], disabled: [1, "disabled"], readonly: [1, "readonly"], invalid: [1, "invalid"], touched: [1, "touched"], sameAllWeek: [1, "sameAllWeek"] }, outputs: { value: "valueChange", touch: "touch", sameAllWeek: "sameAllWeekChange" }, decls: 4, vars: 3, consts: [[1, "kpt-map-hours-field__same", 3, "checkedChange", "checked", "disabled", "label"], [1, "kpt-map-hours-field__week"], [1, "kpt-map-hours-field__day"], [1, "kpt-map-hours-field__name"], [1, "kpt-map-hours-field__ranges"], [1, "kpt-map-hours-field__closed", 3, "checkedChange", "checked", "disabled", "label"], [1, "kpt-map-hours-field__range"], ["type", "button", 1, "kpt-map-hours-field__add", 3, "disabled"], ["selectionMode", "time", 3, "valueChange", "touch", "minuteStep", "value", "disabled", "ariaLabel"], ["aria-hidden", "true", 1, "kpt-map-hours-field__dash"], ["variant", "text", "size", "sm", 3, "disabled", "ariaLabel"], ["variant", "text", "size", "sm", 3, "click", "disabled", "ariaLabel"], ["name", "minus"], ["type", "button", 1, "kpt-map-hours-field__add", 3, "click", "disabled"], ["name", "plus"]], template: function KptMapHoursField_Template(rf, ctx) { if (rf & 1) {
|
|
1060
|
+
i0.ɵɵelementStart(0, "kpt-checkbox", 0);
|
|
1061
|
+
i0.ɵɵlistener("checkedChange", function KptMapHoursField_Template_kpt_checkbox_checkedChange_0_listener($event) { return ctx.onSameAllWeek($event); });
|
|
1062
|
+
i0.ɵɵelementEnd();
|
|
1063
|
+
i0.ɵɵelementStart(1, "ul", 1);
|
|
1064
|
+
i0.ɵɵrepeaterCreate(2, KptMapHoursField_For_3_Template, 6, 6, "li", 2, _forTrack0$5);
|
|
1065
|
+
i0.ɵɵelementEnd();
|
|
1066
|
+
} if (rf & 2) {
|
|
1067
|
+
i0.ɵɵproperty("checked", ctx.sameAllWeek())("disabled", ctx.disabled() || ctx.readonly())("label", ctx.i18n.t("map.field.hoursSameAllWeek"));
|
|
1068
|
+
i0.ɵɵadvance(2);
|
|
1069
|
+
i0.ɵɵrepeater(ctx.rows());
|
|
1070
|
+
} }, dependencies: [KptCheckbox, KptDatepicker, KptIcon, KptIconButton], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-hours-field{display:flex;flex-direction:column;gap:var(--%NS%kpt-space-2)}.kpt-map-hours-field__week{display:flex;flex-direction:column;gap:var(--%NS%kpt-space-1);margin:0;padding:0;list-style:none}.kpt-map-hours-field__day{display:grid;grid-template-columns:3.5rem 1fr auto;gap:var(--%NS%kpt-space-2);align-items:center;padding:var(--%NS%kpt-space-1) 0}.kpt-map-hours-field__day[data-locked]{opacity:.6}.kpt-map-hours-field__name{font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-hours-field__ranges{display:flex;flex-direction:column;gap:var(--%NS%kpt-space-1);min-width:0}.kpt-map-hours-field__range{display:flex;gap:var(--%NS%kpt-space-1);align-items:center}.kpt-map-hours-field__range .kpt-datepicker-host{flex:1 1 0;min-width:0}.kpt-map-hours-field__dash{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-hours-field__add{display:inline-flex;gap:var(--%NS%kpt-space-1);align-items:center;align-self:flex-start;padding:0;font:inherit;font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-primary);cursor:pointer;background:none;border:0}.kpt-map-hours-field__add:disabled{color:var(--%NS%kpt-color-text-subtle);cursor:default}.kpt-map-hours-field__add:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:2px}}\n"], encapsulation: 2 }); }
|
|
1071
|
+
}
|
|
1072
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapHoursField, [{
|
|
1073
|
+
type: Component,
|
|
1074
|
+
args: [{ selector: 'kpt-map-hours-field', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptCheckbox, KptDatepicker, KptIcon, KptIconButton], host: { class: 'kpt-map-hours-field' }, template: "<kpt-checkbox\n class=\"kpt-map-hours-field__same\"\n [checked]=\"sameAllWeek()\"\n (checkedChange)=\"onSameAllWeek($event)\"\n [disabled]=\"disabled() || readonly()\"\n [label]=\"i18n.t('map.field.hoursSameAllWeek')\"\n/>\n\n<ul class=\"kpt-map-hours-field__week\">\n @for (row of rows(); track row.day) {\n <li class=\"kpt-map-hours-field__day\" [attr.data-locked]=\"locked(row) ? '' : null\">\n <span class=\"kpt-map-hours-field__name\">{{ row.name }}</span>\n\n <!-- Przy zamkni\u0119tym dniu kolumna godzin zostaje pusta: \u201Ezamkni\u0119te\" m\u00F3wi ju\u017C checkbox. -->\n <div class=\"kpt-map-hours-field__ranges\">\n @if (!row.closed) {\n @for (range of row.ranges; track $index) {\n <div class=\"kpt-map-hours-field__range\">\n <kpt-datepicker\n selectionMode=\"time\"\n [minuteStep]=\"minuteStep\"\n [value]=\"range.from\"\n (valueChange)=\"setTime(row, $index, 'from', $event)\"\n [disabled]=\"locked(row)\"\n [ariaLabel]=\"i18n.t('map.field.hoursFrom') + ' \u2014 ' + row.name\"\n (touch)=\"touch.emit()\"\n />\n <span class=\"kpt-map-hours-field__dash\" aria-hidden=\"true\">\u2013</span>\n <kpt-datepicker\n selectionMode=\"time\"\n [minuteStep]=\"minuteStep\"\n [value]=\"range.to\"\n (valueChange)=\"setTime(row, $index, 'to', $event)\"\n [disabled]=\"locked(row)\"\n [ariaLabel]=\"i18n.t('map.field.hoursTo') + ' \u2014 ' + row.name\"\n (touch)=\"touch.emit()\"\n />\n @if (row.ranges.length > 1) {\n <kpt-icon-button\n variant=\"text\"\n size=\"sm\"\n [disabled]=\"locked(row)\"\n [ariaLabel]=\"i18n.t('map.field.hoursRemoveRange')\"\n (click)=\"removeRange(row, $index)\"\n >\n <kpt-icon name=\"minus\" />\n </kpt-icon-button>\n }\n </div>\n }\n\n @if (row.ranges.length < maxRanges) {\n <button\n class=\"kpt-map-hours-field__add\"\n type=\"button\"\n [disabled]=\"locked(row)\"\n (click)=\"addRange(row)\"\n >\n <kpt-icon name=\"plus\" />\n {{ i18n.t('map.field.hoursAddRange') }}\n </button>\n }\n }\n </div>\n\n <kpt-checkbox\n class=\"kpt-map-hours-field__closed\"\n [checked]=\"row.closed\"\n (checkedChange)=\"setClosed(row, $event)\"\n [disabled]=\"locked(row)\"\n [label]=\"i18n.t('map.field.hoursClosed')\"\n />\n </li>\n }\n</ul>\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-hours-field{display:flex;flex-direction:column;gap:var(--kpt-space-2)}.kpt-map-hours-field__week{display:flex;flex-direction:column;gap:var(--kpt-space-1);margin:0;padding:0;list-style:none}.kpt-map-hours-field__day{display:grid;grid-template-columns:3.5rem 1fr auto;gap:var(--kpt-space-2);align-items:center;padding:var(--kpt-space-1) 0}.kpt-map-hours-field__day[data-locked]{opacity:.6}.kpt-map-hours-field__name{font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface-muted)}.kpt-map-hours-field__ranges{display:flex;flex-direction:column;gap:var(--kpt-space-1);min-width:0}.kpt-map-hours-field__range{display:flex;gap:var(--kpt-space-1);align-items:center}.kpt-map-hours-field__range .kpt-datepicker-host{flex:1 1 0;min-width:0}.kpt-map-hours-field__dash{color:var(--kpt-color-on-surface-muted)}.kpt-map-hours-field__add{display:inline-flex;gap:var(--kpt-space-1);align-items:center;align-self:flex-start;padding:0;font:inherit;font-size:var(--kpt-font-size-sm);color:var(--kpt-color-primary);cursor:pointer;background:none;border:0}.kpt-map-hours-field__add:disabled{color:var(--kpt-color-text-subtle);cursor:default}.kpt-map-hours-field__add:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:2px}}\n"] }]
|
|
1075
|
+
}], null, { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], sameAllWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "sameAllWeek", required: false }] }, { type: i0.Output, args: ["sameAllWeekChange"] }] }); })();
|
|
1076
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapHoursField, { className: "KptMapHoursField", filePath: "lib/map-hours-field.component.ts", lineNumber: 53 }); })();
|
|
1077
|
+
|
|
1078
|
+
function KptMapImageField_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
1079
|
+
i0.ɵɵelementStart(0, "p", 4);
|
|
1080
|
+
i0.ɵɵtext(1);
|
|
1081
|
+
i0.ɵɵelementEnd();
|
|
1082
|
+
} if (rf & 2) {
|
|
1083
|
+
i0.ɵɵadvance();
|
|
1084
|
+
i0.ɵɵtextInterpolate(ctx);
|
|
1085
|
+
} }
|
|
1086
|
+
function KptMapImageField_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
1087
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
1088
|
+
i0.ɵɵelementStart(0, "figure", 5);
|
|
1089
|
+
i0.ɵɵelement(1, "img", 6);
|
|
1090
|
+
i0.ɵɵelementStart(2, "button", 7);
|
|
1091
|
+
i0.ɵɵlistener("click", function KptMapImageField_Conditional_6_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.clear()); });
|
|
1092
|
+
i0.ɵɵelement(3, "kpt-icon", 8);
|
|
1093
|
+
i0.ɵɵelementEnd()();
|
|
1094
|
+
} if (rf & 2) {
|
|
1095
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
1096
|
+
i0.ɵɵadvance();
|
|
1097
|
+
i0.ɵɵproperty("src", ctx, i0.ɵɵsanitizeUrl)("alt", ctx_r1.i18n.t("map.field.imagePreview"));
|
|
1098
|
+
i0.ɵɵadvance();
|
|
1099
|
+
i0.ɵɵproperty("title", ctx_r1.i18n.t("map.field.removeImage"))("disabled", ctx_r1.disabled() || ctx_r1.readonly());
|
|
1100
|
+
i0.ɵɵattribute("aria-label", ctx_r1.i18n.t("map.field.removeImage"));
|
|
1101
|
+
} }
|
|
1102
|
+
/** Domyślny limit pliku. Data URL puchnie o ~⅓, a wartość leci w `locationCreate`. */
|
|
1103
|
+
const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
|
|
1104
|
+
/**
|
|
1105
|
+
* Pole „zdjęcie" formularza dodawania lokalizacji (`type: 'image'` w schemie kategorii).
|
|
1106
|
+
*
|
|
1107
|
+
* Wartością jest zawsze **string**: albo `data:` z wybranego pliku, albo adres wklejony
|
|
1108
|
+
* przez użytkownika. Karta miejsca nie rozróżnia jednego od drugiego, więc formularz
|
|
1109
|
+
* nie musi decydować za aplikację, gdzie zdjęcia mieszkają.
|
|
1110
|
+
*
|
|
1111
|
+
* Plik czytamy w przeglądarce i nigdzie nie wysyłamy — biblioteka nie ma i nie chce
|
|
1112
|
+
* mieć wiedzy o storage'u aplikacji.
|
|
1113
|
+
*/
|
|
1114
|
+
class KptMapImageField {
|
|
1115
|
+
constructor() {
|
|
1116
|
+
this.value = model('', /* @ts-ignore */
|
|
1117
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1118
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1119
|
+
this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1120
|
+
this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1121
|
+
this.touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1122
|
+
/** Górny limit pliku w bajtach. */
|
|
1123
|
+
this.maxBytes = input(DEFAULT_MAX_BYTES, /* @ts-ignore */
|
|
1124
|
+
...(ngDevMode ? [{ debugName: "maxBytes" }] : /* istanbul ignore next */ []));
|
|
1125
|
+
this.touch = output();
|
|
1126
|
+
this.i18n = inject(KptI18n);
|
|
1127
|
+
/** Pliki trzymane wyłącznie po to, żeby `kpt-file-upload` miał co pokazać. */
|
|
1128
|
+
this.files = model([], /* @ts-ignore */
|
|
1129
|
+
...(ngDevMode ? [{ debugName: "files" }] : /* istanbul ignore next */ []));
|
|
1130
|
+
/** Komunikat o zbyt dużym pliku; walidacja rozmiaru nie ma sensu w schemie. */
|
|
1131
|
+
this.sizeError = signal(null, /* @ts-ignore */
|
|
1132
|
+
...(ngDevMode ? [{ debugName: "sizeError" }] : /* istanbul ignore next */ []));
|
|
1133
|
+
this.urlValue = computed(() => (this.value().startsWith('data:') ? '' : this.value()), /* @ts-ignore */
|
|
1134
|
+
...(ngDevMode ? [{ debugName: "urlValue" }] : /* istanbul ignore next */ []));
|
|
1135
|
+
this.preview = computed(() => this.value() || null, /* @ts-ignore */
|
|
1136
|
+
...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
|
|
1137
|
+
this.maxLabel = computed(() => `${Math.round(this.maxBytes() / (1024 * 1024))} MB`, /* @ts-ignore */
|
|
1138
|
+
...(ngDevMode ? [{ debugName: "maxLabel" }] : /* istanbul ignore next */ []));
|
|
1139
|
+
}
|
|
1140
|
+
onFiles(files) {
|
|
1141
|
+
const file = files[0];
|
|
1142
|
+
this.files.set(file ? [file] : []);
|
|
1143
|
+
this.sizeError.set(null);
|
|
1144
|
+
this.touch.emit();
|
|
1145
|
+
if (!file) {
|
|
1146
|
+
// Usunięcie pliku czyści wartość tylko wtedy, gdy to plik ją ustawił.
|
|
1147
|
+
if (this.value().startsWith('data:'))
|
|
1148
|
+
this.value.set('');
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (file.size > this.maxBytes()) {
|
|
1152
|
+
this.sizeError.set(this.i18n.t('map.field.imageTooLarge', { max: this.maxLabel() }));
|
|
1153
|
+
this.files.set([]);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
const reader = new FileReader();
|
|
1157
|
+
reader.onload = () => this.value.set(String(reader.result ?? ''));
|
|
1158
|
+
reader.onerror = () => this.sizeError.set(this.i18n.t('map.field.imageTooLarge', { max: this.maxLabel() }));
|
|
1159
|
+
reader.readAsDataURL(file);
|
|
1160
|
+
}
|
|
1161
|
+
onUrl(url) {
|
|
1162
|
+
// Adres i plik wykluczają się — dwa źródła jednej wartości myliłyby użytkownika.
|
|
1163
|
+
if (url)
|
|
1164
|
+
this.files.set([]);
|
|
1165
|
+
this.sizeError.set(null);
|
|
1166
|
+
this.value.set(url);
|
|
1167
|
+
}
|
|
1168
|
+
clear() {
|
|
1169
|
+
this.files.set([]);
|
|
1170
|
+
this.sizeError.set(null);
|
|
1171
|
+
this.value.set('');
|
|
1172
|
+
this.touch.emit();
|
|
1173
|
+
}
|
|
1174
|
+
static { this.ɵfac = function KptMapImageField_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapImageField)(); }; }
|
|
1175
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapImageField, selectors: [["kpt-map-image-field"]], hostAttrs: [1, "kpt-map-image-field"], inputs: { value: [1, "value"], disabled: [1, "disabled"], readonly: [1, "readonly"], invalid: [1, "invalid"], touched: [1, "touched"], maxBytes: [1, "maxBytes"], files: [1, "files"] }, outputs: { value: "valueChange", touch: "touch", files: "filesChange" }, decls: 7, vars: 10, consts: [["accept", "image/*", 3, "valueChange", "value", "disabled", "invalid", "touched"], [1, "kpt-map-image-field__url"], [1, "kpt-map-image-field__url-label"], ["type", "url", "placeholder", "https://", 3, "valueChange", "touch", "value", "disabled", "readonly"], ["aria-live", "polite", 1, "kpt-map-image-field__error"], [1, "kpt-map-image-field__preview"], [3, "src", "alt"], ["type", "button", 1, "kpt-map-image-field__clear", 3, "click", "title", "disabled"], ["name", "x"]], template: function KptMapImageField_Template(rf, ctx) { if (rf & 1) {
|
|
1176
|
+
i0.ɵɵelementStart(0, "kpt-file-upload", 0);
|
|
1177
|
+
i0.ɵɵlistener("valueChange", function KptMapImageField_Template_kpt_file_upload_valueChange_0_listener($event) { return ctx.onFiles($event); });
|
|
1178
|
+
i0.ɵɵelementEnd();
|
|
1179
|
+
i0.ɵɵelementStart(1, "label", 1)(2, "span", 2);
|
|
1180
|
+
i0.ɵɵtext(3);
|
|
1181
|
+
i0.ɵɵelementEnd();
|
|
1182
|
+
i0.ɵɵelementStart(4, "kpt-input", 3);
|
|
1183
|
+
i0.ɵɵlistener("valueChange", function KptMapImageField_Template_kpt_input_valueChange_4_listener($event) { return ctx.onUrl($event); })("touch", function KptMapImageField_Template_kpt_input_touch_4_listener() { return ctx.touch.emit(); });
|
|
1184
|
+
i0.ɵɵelementEnd()();
|
|
1185
|
+
i0.ɵɵconditionalCreate(5, KptMapImageField_Conditional_5_Template, 2, 1, "p", 4);
|
|
1186
|
+
i0.ɵɵconditionalCreate(6, KptMapImageField_Conditional_6_Template, 4, 5, "figure", 5);
|
|
1187
|
+
} if (rf & 2) {
|
|
1188
|
+
let tmp_8_0;
|
|
1189
|
+
let tmp_9_0;
|
|
1190
|
+
i0.ɵɵproperty("value", ctx.files())("disabled", ctx.disabled() || ctx.readonly())("invalid", ctx.invalid())("touched", ctx.touched());
|
|
1191
|
+
i0.ɵɵadvance(3);
|
|
1192
|
+
i0.ɵɵtextInterpolate(ctx.i18n.t("map.field.orPasteUrl"));
|
|
1193
|
+
i0.ɵɵadvance();
|
|
1194
|
+
i0.ɵɵproperty("value", ctx.urlValue())("disabled", ctx.disabled())("readonly", ctx.readonly());
|
|
1195
|
+
i0.ɵɵadvance();
|
|
1196
|
+
i0.ɵɵconditional((tmp_8_0 = ctx.sizeError()) ? 5 : -1, tmp_8_0);
|
|
1197
|
+
i0.ɵɵadvance();
|
|
1198
|
+
i0.ɵɵconditional((tmp_9_0 = ctx.preview()) ? 6 : -1, tmp_9_0);
|
|
1199
|
+
} }, dependencies: [KptFileUpload, KptIcon, KptInput], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-image-field{display:flex;flex-direction:column;gap:var(--%NS%kpt-space-2)}.kpt-map-image-field__url{display:flex;flex-direction:column;gap:var(--%NS%kpt-space-1)}.kpt-map-image-field__url-label{font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-image-field__error{margin:0;font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-danger)}.kpt-map-image-field__preview{position:relative;margin:0;overflow:hidden;border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-map-poi-media-radius, var(--%NS%kpt-radius-md))}.kpt-map-image-field__preview img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.kpt-map-image-field__clear{position:absolute;top:var(--%NS%kpt-space-2);right:var(--%NS%kpt-space-2);display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;color:var(--%NS%kpt-color-on-surface);cursor:pointer;background:var(--%NS%kpt-color-surface-raised);border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-full)}.kpt-map-image-field__clear:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}}\n"], encapsulation: 2 }); }
|
|
1200
|
+
}
|
|
1201
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapImageField, [{
|
|
1202
|
+
type: Component,
|
|
1203
|
+
args: [{ selector: 'kpt-map-image-field', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptFileUpload, KptIcon, KptInput], host: { class: 'kpt-map-image-field' }, template: "<kpt-file-upload\n accept=\"image/*\"\n [value]=\"files()\"\n (valueChange)=\"onFiles($event)\"\n [disabled]=\"disabled() || readonly()\"\n [invalid]=\"invalid()\"\n [touched]=\"touched()\"\n/>\n\n<label class=\"kpt-map-image-field__url\">\n <span class=\"kpt-map-image-field__url-label\">{{ i18n.t('map.field.orPasteUrl') }}</span>\n <kpt-input\n type=\"url\"\n [value]=\"urlValue()\"\n (valueChange)=\"onUrl($event)\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (touch)=\"touch.emit()\"\n placeholder=\"https://\"\n />\n</label>\n\n@if (sizeError(); as error) {\n <p class=\"kpt-map-image-field__error\" aria-live=\"polite\">{{ error }}</p>\n}\n\n@if (preview(); as src) {\n <figure class=\"kpt-map-image-field__preview\">\n <img [src]=\"src\" [alt]=\"i18n.t('map.field.imagePreview')\" />\n <button\n class=\"kpt-map-image-field__clear\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('map.field.removeImage')\"\n [title]=\"i18n.t('map.field.removeImage')\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"clear()\"\n >\n <kpt-icon name=\"x\" />\n </button>\n </figure>\n}\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-image-field{display:flex;flex-direction:column;gap:var(--kpt-space-2)}.kpt-map-image-field__url{display:flex;flex-direction:column;gap:var(--kpt-space-1)}.kpt-map-image-field__url-label{font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface-muted)}.kpt-map-image-field__error{margin:0;font-size:var(--kpt-font-size-sm);color:var(--kpt-color-danger)}.kpt-map-image-field__preview{position:relative;margin:0;overflow:hidden;border:var(--kpt-border-width-thin) solid var(--kpt-color-border);border-radius:var(--kpt-map-poi-media-radius, var(--kpt-radius-md))}.kpt-map-image-field__preview img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover}.kpt-map-image-field__clear{position:absolute;top:var(--kpt-space-2);right:var(--kpt-space-2);display:inline-grid;place-items:center;width:1.75rem;height:1.75rem;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-full)}.kpt-map-image-field__clear:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}}\n"] }]
|
|
1204
|
+
}], null, { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], maxBytes: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxBytes", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], files: [{ type: i0.Input, args: [{ isSignal: true, alias: "files", required: false }] }, { type: i0.Output, args: ["filesChange"] }] }); })();
|
|
1205
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapImageField, { className: "KptMapImageField", filePath: "lib/map-image-field.component.ts", lineNumber: 38 }); })();
|
|
1206
|
+
|
|
1207
|
+
const _forTrack0$4 = ($index, $item) => $item.key;
|
|
1208
|
+
function KptMapCreateDialog_Conditional_0_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
1209
|
+
i0.ɵɵelementStart(0, "p", 3);
|
|
1210
|
+
i0.ɵɵtext(1);
|
|
1211
|
+
i0.ɵɵelementEnd();
|
|
1212
|
+
} if (rf & 2) {
|
|
1213
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1214
|
+
i0.ɵɵadvance();
|
|
1215
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.lookingUpAddress"));
|
|
1216
|
+
} }
|
|
1217
|
+
function KptMapCreateDialog_Conditional_0_Conditional_11_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
1218
|
+
i0.ɵɵelementStart(0, "p", 3);
|
|
1219
|
+
i0.ɵɵtext(1);
|
|
1220
|
+
i0.ɵɵelementEnd();
|
|
1221
|
+
} if (rf & 2) {
|
|
1222
|
+
const adres_r3 = i0.ɵɵnextContext();
|
|
1223
|
+
i0.ɵɵadvance();
|
|
1224
|
+
i0.ɵɵtextInterpolate(adres_r3.label);
|
|
1225
|
+
} }
|
|
1226
|
+
function KptMapCreateDialog_Conditional_0_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
1227
|
+
i0.ɵɵconditionalCreate(0, KptMapCreateDialog_Conditional_0_Conditional_11_Conditional_0_Template, 2, 1, "p", 3);
|
|
1228
|
+
} if (rf & 2) {
|
|
1229
|
+
i0.ɵɵconditional(ctx.label ? 0 : -1);
|
|
1230
|
+
} }
|
|
1231
|
+
function KptMapCreateDialog_Conditional_0_Conditional_12_Template(rf, ctx) { if (rf & 1) {
|
|
1232
|
+
i0.ɵɵelementStart(0, "p", 3);
|
|
1233
|
+
i0.ɵɵtext(1);
|
|
1234
|
+
i0.ɵɵelementEnd();
|
|
1235
|
+
} if (rf & 2) {
|
|
1236
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1237
|
+
i0.ɵɵadvance();
|
|
1238
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.addressNotFound"));
|
|
1239
|
+
} }
|
|
1240
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_1_Template(rf, ctx) { if (rf & 1) {
|
|
1241
|
+
i0.ɵɵelement(0, "kpt-textarea", 8);
|
|
1242
|
+
i0.ɵɵcontrolCreate();
|
|
1243
|
+
} if (rf & 2) {
|
|
1244
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1245
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1246
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("placeholder", def_r4.placeholder ?? "");
|
|
1247
|
+
i0.ɵɵcontrol();
|
|
1248
|
+
} }
|
|
1249
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_2_Template(rf, ctx) { if (rf & 1) {
|
|
1250
|
+
i0.ɵɵelement(0, "kpt-input-number", 8);
|
|
1251
|
+
i0.ɵɵcontrolCreate();
|
|
1252
|
+
} if (rf & 2) {
|
|
1253
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1254
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1255
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("placeholder", def_r4.placeholder ?? "");
|
|
1256
|
+
i0.ɵɵcontrol();
|
|
1257
|
+
} }
|
|
1258
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_3_Template(rf, ctx) { if (rf & 1) {
|
|
1259
|
+
i0.ɵɵelement(0, "kpt-datepicker", 9);
|
|
1260
|
+
i0.ɵɵcontrolCreate();
|
|
1261
|
+
} if (rf & 2) {
|
|
1262
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1263
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1264
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("placeholder", def_r4.placeholder ?? "")("minDate", def_r4.min ?? null)("maxDate", def_r4.max ?? null);
|
|
1265
|
+
i0.ɵɵcontrol();
|
|
1266
|
+
} }
|
|
1267
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_4_Template(rf, ctx) { if (rf & 1) {
|
|
1268
|
+
i0.ɵɵelement(0, "kpt-select", 10);
|
|
1269
|
+
i0.ɵɵcontrolCreate();
|
|
1270
|
+
} if (rf & 2) {
|
|
1271
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1272
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1273
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("options", ctx_r1.optionsOf(def_r4))("placeholder", def_r4.placeholder ?? "");
|
|
1274
|
+
i0.ɵɵcontrol();
|
|
1275
|
+
} }
|
|
1276
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_5_Template(rf, ctx) { if (rf & 1) {
|
|
1277
|
+
i0.ɵɵelement(0, "kpt-select", 11);
|
|
1278
|
+
i0.ɵɵcontrolCreate();
|
|
1279
|
+
} if (rf & 2) {
|
|
1280
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1281
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1282
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("options", ctx_r1.optionsOf(def_r4))("placeholder", def_r4.placeholder ?? "");
|
|
1283
|
+
i0.ɵɵcontrol();
|
|
1284
|
+
} }
|
|
1285
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_6_Template(rf, ctx) { if (rf & 1) {
|
|
1286
|
+
i0.ɵɵelement(0, "kpt-checkbox", 12);
|
|
1287
|
+
i0.ɵɵcontrolCreate();
|
|
1288
|
+
} if (rf & 2) {
|
|
1289
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1290
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1291
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("label", def_r4.placeholder ?? "");
|
|
1292
|
+
i0.ɵɵcontrol();
|
|
1293
|
+
} }
|
|
1294
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_7_Template(rf, ctx) { if (rf & 1) {
|
|
1295
|
+
i0.ɵɵelement(0, "kpt-input", 13);
|
|
1296
|
+
i0.ɵɵcontrolCreate();
|
|
1297
|
+
} if (rf & 2) {
|
|
1298
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1299
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1300
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("id", ctx_r1.controlId(def_r4))("placeholder", def_r4.placeholder ?? "");
|
|
1301
|
+
i0.ɵɵcontrol();
|
|
1302
|
+
} }
|
|
1303
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_8_Template(rf, ctx) { if (rf & 1) {
|
|
1304
|
+
i0.ɵɵelement(0, "kpt-input", 14);
|
|
1305
|
+
i0.ɵɵcontrolCreate();
|
|
1306
|
+
} if (rf & 2) {
|
|
1307
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1308
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1309
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("id", ctx_r1.controlId(def_r4))("placeholder", def_r4.placeholder ?? "");
|
|
1310
|
+
i0.ɵɵcontrol();
|
|
1311
|
+
} }
|
|
1312
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_9_Template(rf, ctx) { if (rf & 1) {
|
|
1313
|
+
i0.ɵɵelement(0, "kpt-input", 15);
|
|
1314
|
+
i0.ɵɵcontrolCreate();
|
|
1315
|
+
} if (rf & 2) {
|
|
1316
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1317
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1318
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("id", ctx_r1.controlId(def_r4))("placeholder", def_r4.placeholder ?? "https://");
|
|
1319
|
+
i0.ɵɵcontrol();
|
|
1320
|
+
} }
|
|
1321
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_10_Template(rf, ctx) { if (rf & 1) {
|
|
1322
|
+
i0.ɵɵelement(0, "kpt-map-image-field", 16);
|
|
1323
|
+
i0.ɵɵcontrolCreate();
|
|
1324
|
+
} if (rf & 2) {
|
|
1325
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1326
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1327
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key));
|
|
1328
|
+
i0.ɵɵcontrol();
|
|
1329
|
+
} }
|
|
1330
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_11_Template(rf, ctx) { if (rf & 1) {
|
|
1331
|
+
i0.ɵɵelement(0, "kpt-map-hours-field", 16);
|
|
1332
|
+
i0.ɵɵcontrolCreate();
|
|
1333
|
+
} if (rf & 2) {
|
|
1334
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1335
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1336
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key));
|
|
1337
|
+
i0.ɵɵcontrol();
|
|
1338
|
+
} }
|
|
1339
|
+
function KptMapCreateDialog_Conditional_0_For_14_Case_12_Template(rf, ctx) { if (rf & 1) {
|
|
1340
|
+
i0.ɵɵelement(0, "kpt-input", 17);
|
|
1341
|
+
i0.ɵɵcontrolCreate();
|
|
1342
|
+
} if (rf & 2) {
|
|
1343
|
+
const def_r4 = i0.ɵɵnextContext().$implicit;
|
|
1344
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1345
|
+
i0.ɵɵproperty("formField", ctx_r1.fieldOf(def_r4.key))("id", ctx_r1.controlId(def_r4))("placeholder", def_r4.placeholder ?? "");
|
|
1346
|
+
i0.ɵɵcontrol();
|
|
1347
|
+
} }
|
|
1348
|
+
function KptMapCreateDialog_Conditional_0_For_14_Template(rf, ctx) { if (rf & 1) {
|
|
1349
|
+
i0.ɵɵelementStart(0, "kpt-form-field", 4);
|
|
1350
|
+
i0.ɵɵconditionalCreate(1, KptMapCreateDialog_Conditional_0_For_14_Case_1_Template, 1, 2, "kpt-textarea", 8)(2, KptMapCreateDialog_Conditional_0_For_14_Case_2_Template, 1, 2, "kpt-input-number", 8)(3, KptMapCreateDialog_Conditional_0_For_14_Case_3_Template, 1, 4, "kpt-datepicker", 9)(4, KptMapCreateDialog_Conditional_0_For_14_Case_4_Template, 1, 3, "kpt-select", 10)(5, KptMapCreateDialog_Conditional_0_For_14_Case_5_Template, 1, 3, "kpt-select", 11)(6, KptMapCreateDialog_Conditional_0_For_14_Case_6_Template, 1, 2, "kpt-checkbox", 12)(7, KptMapCreateDialog_Conditional_0_For_14_Case_7_Template, 1, 3, "kpt-input", 13)(8, KptMapCreateDialog_Conditional_0_For_14_Case_8_Template, 1, 3, "kpt-input", 14)(9, KptMapCreateDialog_Conditional_0_For_14_Case_9_Template, 1, 3, "kpt-input", 15)(10, KptMapCreateDialog_Conditional_0_For_14_Case_10_Template, 1, 1, "kpt-map-image-field", 16)(11, KptMapCreateDialog_Conditional_0_For_14_Case_11_Template, 1, 1, "kpt-map-hours-field", 16)(12, KptMapCreateDialog_Conditional_0_For_14_Case_12_Template, 1, 3, "kpt-input", 17);
|
|
1351
|
+
i0.ɵɵelementEnd();
|
|
1352
|
+
} if (rf & 2) {
|
|
1353
|
+
let tmp_17_0;
|
|
1354
|
+
const def_r4 = ctx.$implicit;
|
|
1355
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1356
|
+
i0.ɵɵproperty("label", def_r4.label)("hint", def_r4.hint ?? "")("error", ctx_r1.errorOf(def_r4.key))("required", !!def_r4.required)("controlId", ctx_r1.hasOwnInput(def_r4) ? ctx_r1.controlId(def_r4) : "");
|
|
1357
|
+
i0.ɵɵadvance();
|
|
1358
|
+
i0.ɵɵconditional((tmp_17_0 = def_r4.type) === "textarea" ? 1 : tmp_17_0 === "number" ? 2 : tmp_17_0 === "date" ? 3 : tmp_17_0 === "select" ? 4 : tmp_17_0 === "multiselect" ? 5 : tmp_17_0 === "checkbox" ? 6 : tmp_17_0 === "tel" ? 7 : tmp_17_0 === "email" ? 8 : tmp_17_0 === "url" ? 9 : tmp_17_0 === "image" ? 10 : tmp_17_0 === "hours" ? 11 : 12);
|
|
1359
|
+
} }
|
|
1360
|
+
function KptMapCreateDialog_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
1361
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
1362
|
+
i0.ɵɵelementStart(0, "form", 1);
|
|
1363
|
+
i0.ɵɵlistener("submit", function KptMapCreateDialog_Conditional_0_Template_form_submit_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSubmit()); });
|
|
1364
|
+
i0.ɵɵelementStart(1, "p", 2)(2, "span");
|
|
1365
|
+
i0.ɵɵtext(3);
|
|
1366
|
+
i0.ɵɵelementStart(4, "strong");
|
|
1367
|
+
i0.ɵɵtext(5);
|
|
1368
|
+
i0.ɵɵelementEnd()();
|
|
1369
|
+
i0.ɵɵelementStart(6, "span");
|
|
1370
|
+
i0.ɵɵtext(7);
|
|
1371
|
+
i0.ɵɵelementStart(8, "strong");
|
|
1372
|
+
i0.ɵɵtext(9);
|
|
1373
|
+
i0.ɵɵelementEnd()()();
|
|
1374
|
+
i0.ɵɵconditionalCreate(10, KptMapCreateDialog_Conditional_0_Conditional_10_Template, 2, 1, "p", 3)(11, KptMapCreateDialog_Conditional_0_Conditional_11_Template, 1, 1)(12, KptMapCreateDialog_Conditional_0_Conditional_12_Template, 2, 1, "p", 3);
|
|
1375
|
+
i0.ɵɵrepeaterCreate(13, KptMapCreateDialog_Conditional_0_For_14_Template, 13, 6, "kpt-form-field", 4, _forTrack0$4);
|
|
1376
|
+
i0.ɵɵelementStart(15, "div", 5)(16, "kpt-button", 6);
|
|
1377
|
+
i0.ɵɵlistener("click", function KptMapCreateDialog_Conditional_0_Template_kpt_button_click_16_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelled.emit()); });
|
|
1378
|
+
i0.ɵɵtext(17);
|
|
1379
|
+
i0.ɵɵelementEnd();
|
|
1380
|
+
i0.ɵɵelementStart(18, "kpt-button", 7);
|
|
1381
|
+
i0.ɵɵtext(19);
|
|
1382
|
+
i0.ɵɵelementEnd()()();
|
|
1383
|
+
} if (rf & 2) {
|
|
1384
|
+
let tmp_7_0;
|
|
1385
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
1386
|
+
i0.ɵɵproperty("formRoot", ctx);
|
|
1387
|
+
i0.ɵɵadvance(3);
|
|
1388
|
+
i0.ɵɵtextInterpolate1("", ctx_r1.i18n.t("map.latitude"), ": ");
|
|
1389
|
+
i0.ɵɵadvance(2);
|
|
1390
|
+
i0.ɵɵtextInterpolate(ctx_r1.coords().lat);
|
|
1391
|
+
i0.ɵɵadvance(2);
|
|
1392
|
+
i0.ɵɵtextInterpolate1("", ctx_r1.i18n.t("map.longitude"), ": ");
|
|
1393
|
+
i0.ɵɵadvance(2);
|
|
1394
|
+
i0.ɵɵtextInterpolate(ctx_r1.coords().lng);
|
|
1395
|
+
i0.ɵɵadvance();
|
|
1396
|
+
i0.ɵɵconditional(ctx_r1.geocoding() ? 10 : (tmp_7_0 = ctx_r1.address()) ? 11 : ctx_r1.hasAutofill() ? 12 : -1, tmp_7_0);
|
|
1397
|
+
i0.ɵɵadvance(3);
|
|
1398
|
+
i0.ɵɵrepeater(ctx_r1.fields());
|
|
1399
|
+
i0.ɵɵadvance(4);
|
|
1400
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.i18n.t("map.cancel"), " ");
|
|
1401
|
+
i0.ɵɵadvance();
|
|
1402
|
+
i0.ɵɵproperty("disabled", ctx_r1.formInvalid());
|
|
1403
|
+
i0.ɵɵadvance();
|
|
1404
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.save"));
|
|
1405
|
+
} }
|
|
1406
|
+
/** Pusta wartość właściwa dla typu pola — formularz startuje bez `undefined`. */
|
|
1407
|
+
function initialValue(def) {
|
|
1408
|
+
if (def.initial !== undefined)
|
|
1409
|
+
return def.initial;
|
|
1410
|
+
switch (def.type) {
|
|
1411
|
+
case 'number':
|
|
1412
|
+
return null;
|
|
1413
|
+
case 'checkbox':
|
|
1414
|
+
return false;
|
|
1415
|
+
case 'multiselect':
|
|
1416
|
+
return [];
|
|
1417
|
+
case 'select':
|
|
1418
|
+
return null;
|
|
1419
|
+
case 'hours':
|
|
1420
|
+
return emptyWeek();
|
|
1421
|
+
default:
|
|
1422
|
+
return '';
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
/** Typy pól, które przyjmą fragment adresu. Data czy liczba nie mają z nim nic wspólnego. */
|
|
1426
|
+
const TEKSTOWE = new Set(['text', 'textarea']);
|
|
1427
|
+
function initialModel(fields) {
|
|
1428
|
+
const out = {};
|
|
1429
|
+
for (const def of fields)
|
|
1430
|
+
out[def.key] = initialValue(def);
|
|
1431
|
+
return out;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Treść dialogu „dodaj lokalizację" budowana ze schemy kategorii (`category.fields`).
|
|
1435
|
+
*
|
|
1436
|
+
* Przy dynamicznej schemie typowanie pól znika — model to `Record<string, unknown>`,
|
|
1437
|
+
* a ścieżki walidatorów trzeba rzutować. Reszta kontraktu Signal Forms zostaje bez zmian:
|
|
1438
|
+
* walidatory deklarujemy raz, a bramka „nie strasz czerwienią" siedzi w prezentacji.
|
|
1439
|
+
*/
|
|
1440
|
+
class KptMapCreateDialog {
|
|
1441
|
+
constructor() {
|
|
1442
|
+
this.category = input.required(/* @ts-ignore */
|
|
1443
|
+
...(ngDevMode ? [{ debugName: "category" }] : /* istanbul ignore next */ []));
|
|
1444
|
+
this.at = input.required(/* @ts-ignore */
|
|
1445
|
+
...(ngDevMode ? [{ debugName: "at" }] : /* istanbul ignore next */ []));
|
|
1446
|
+
/**
|
|
1447
|
+
* Geokoder tego formularza. Podany wygrywa nad `KPT_MAP_GEOCODER` z DI — `kpt-map` podaje
|
|
1448
|
+
* tędy port swojej mapy, bo geokoder jest per mapa, a nie per aplikacja.
|
|
1449
|
+
*/
|
|
1450
|
+
this.geocoder = input(null, /* @ts-ignore */
|
|
1451
|
+
...(ngDevMode ? [{ debugName: "geocoder" }] : /* istanbul ignore next */ []));
|
|
1452
|
+
this.save = output();
|
|
1453
|
+
this.cancelled = output();
|
|
1454
|
+
this.i18n = inject(KptI18n);
|
|
1455
|
+
this.injector = inject(Injector);
|
|
1456
|
+
/** Opcjonalny — wejście `[geocoder]` i tak ma nad nim pierwszeństwo. */
|
|
1457
|
+
this.injectedGeocoder = inject(KPT_MAP_GEOCODER, { optional: true });
|
|
1458
|
+
/** Rozstrzygnięty port: wejście przed DI. Bez obu formularz po prostu nie podpowiada adresu. */
|
|
1459
|
+
this.port = computed(() => this.geocoder() ?? this.injectedGeocoder, /* @ts-ignore */
|
|
1460
|
+
...(ngDevMode ? [{ debugName: "port" }] : /* istanbul ignore next */ []));
|
|
1461
|
+
/** Trwa zapytanie do geokodera — szablon pokazuje wtedy notkę zamiast ciszy. */
|
|
1462
|
+
this.geocoding = signal(false, /* @ts-ignore */
|
|
1463
|
+
...(ngDevMode ? [{ debugName: "geocoding" }] : /* istanbul ignore next */ []));
|
|
1464
|
+
/** Ostatnio ustalony adres; `label` bywa czytelniejszy niż suma pól. */
|
|
1465
|
+
this.address = signal(null, /* @ts-ignore */
|
|
1466
|
+
...(ngDevMode ? [{ debugName: "address" }] : /* istanbul ignore next */ []));
|
|
1467
|
+
this.fields = computed(() => this.category().fields ?? [], /* @ts-ignore */
|
|
1468
|
+
...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
|
|
1469
|
+
/** Czy kategoria ma pola do podpowiedzenia i jest czym je podpowiedzieć. */
|
|
1470
|
+
this.hasAutofill = computed(() => !!this.port() && this.fields().some((f) => f.autofill && TEKSTOWE.has(f.type)), /* @ts-ignore */
|
|
1471
|
+
...(ngDevMode ? [{ debugName: "hasAutofill" }] : /* istanbul ignore next */ []));
|
|
1472
|
+
/**
|
|
1473
|
+
* Model i formularz dla aktualnej kategorii. Budowane w efekcie, nie w `computed()`,
|
|
1474
|
+
* bo `form()` zakłada dwie rzeczy, których ciało `computed()` nie spełnia:
|
|
1475
|
+
*
|
|
1476
|
+
* - wstrzykuje sobie zależności → stąd `runInInjectionContext`;
|
|
1477
|
+
* - zakłada własny `effect()`, a tego nie wolno tworzyć w kontekście reaktywnym
|
|
1478
|
+
* (NG0602) → stąd `untracked`, które zdejmuje aktywnego konsumenta.
|
|
1479
|
+
*
|
|
1480
|
+
* Zależność od `fields()` zostaje śledzona na zewnątrz `untracked`, więc zmiana
|
|
1481
|
+
* kategorii przebudowuje formularz — inny zestaw pól to inny formularz, a Signal Forms
|
|
1482
|
+
* wiąże schemę tylko raz, przy tworzeniu.
|
|
1483
|
+
*/
|
|
1484
|
+
this.built = signal(null, /* @ts-ignore */
|
|
1485
|
+
...(ngDevMode ? [{ debugName: "built" }] : /* istanbul ignore next */ []));
|
|
1486
|
+
/** `null` tylko między utworzeniem komponentu a pierwszym przebiegiem efektu. */
|
|
1487
|
+
this.formRoot = computed(() => this.built()?.form ?? null, /* @ts-ignore */
|
|
1488
|
+
...(ngDevMode ? [{ debugName: "formRoot" }] : /* istanbul ignore next */ []));
|
|
1489
|
+
this.formInvalid = computed(() => this.formRoot()?.().invalid() ?? true, /* @ts-ignore */
|
|
1490
|
+
...(ngDevMode ? [{ debugName: "formInvalid" }] : /* istanbul ignore next */ []));
|
|
1491
|
+
/** Współrzędne z prawego kliknięcia — pokazujemy je, ale nie pozwalamy edytować. */
|
|
1492
|
+
this.coords = computed(() => {
|
|
1493
|
+
const at = this.at();
|
|
1494
|
+
return { lat: at.lat.toFixed(5), lng: at.lng.toFixed(5) };
|
|
1495
|
+
}, /* @ts-ignore */
|
|
1496
|
+
...(ngDevMode ? [{ debugName: "coords" }] : /* istanbul ignore next */ []));
|
|
1497
|
+
effect(() => {
|
|
1498
|
+
const fields = this.fields();
|
|
1499
|
+
untracked(() => {
|
|
1500
|
+
const model = signal(initialModel(fields), /* @ts-ignore */
|
|
1501
|
+
...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
|
|
1502
|
+
const f = runInInjectionContext(this.injector, () => form(model, (path) => {
|
|
1503
|
+
// Ścieżki są dynamiczne, więc indeksujemy proxy po kluczu — stąd rzutowanie.
|
|
1504
|
+
const paths = path;
|
|
1505
|
+
for (const def of fields) {
|
|
1506
|
+
const node = paths[def.key];
|
|
1507
|
+
if (!node)
|
|
1508
|
+
continue;
|
|
1509
|
+
if (def.required) {
|
|
1510
|
+
required(node, { message: this.i18n.t('map.fieldRequired', { label: def.label }) });
|
|
1511
|
+
}
|
|
1512
|
+
if (def.type === 'number') {
|
|
1513
|
+
if (typeof def.min === 'number')
|
|
1514
|
+
min(node, def.min);
|
|
1515
|
+
if (typeof def.max === 'number')
|
|
1516
|
+
max(node, def.max);
|
|
1517
|
+
}
|
|
1518
|
+
if (def.type === 'email') {
|
|
1519
|
+
email(node, { message: this.i18n.t('map.field.invalidEmail') });
|
|
1520
|
+
}
|
|
1521
|
+
if (def.type === 'url') {
|
|
1522
|
+
pattern(node, /^https?:\/\/\S+$/i, {
|
|
1523
|
+
message: this.i18n.t('map.field.invalidUrl'),
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
if (def.type === 'hours') {
|
|
1527
|
+
validate(node, (ctx) => this.hoursError(ctx.value()));
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}));
|
|
1531
|
+
this.built.set({ model, form: f });
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
this.watchGeocoder();
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Podpowiada adres z geokodowania wstecznego współrzędnych kliknięcia.
|
|
1538
|
+
*
|
|
1539
|
+
* Wypełnia wyłącznie pola z `autofill` i wyłącznie te, które są jeszcze puste —
|
|
1540
|
+
* podpowiedź nigdy nie kasuje tego, co użytkownik zdążył wpisać. Brak portu
|
|
1541
|
+
* `KPT_MAP_GEOCODER` albo nieudane zapytanie to nie błąd: pola zostają puste.
|
|
1542
|
+
*/
|
|
1543
|
+
watchGeocoder() {
|
|
1544
|
+
effect((onCleanup) => {
|
|
1545
|
+
const geocoder = this.port();
|
|
1546
|
+
const at = this.at();
|
|
1547
|
+
const fields = this.fields();
|
|
1548
|
+
const built = this.built();
|
|
1549
|
+
if (!geocoder || !built)
|
|
1550
|
+
return;
|
|
1551
|
+
const doUzupelnienia = fields.filter((f) => f.autofill && TEKSTOWE.has(f.type));
|
|
1552
|
+
if (doUzupelnienia.length === 0)
|
|
1553
|
+
return;
|
|
1554
|
+
this.geocoding.set(true);
|
|
1555
|
+
const subscription = geocoder
|
|
1556
|
+
.reverse(at, { language: this.i18n.locale() })
|
|
1557
|
+
.subscribe({
|
|
1558
|
+
next: (address) => {
|
|
1559
|
+
this.geocoding.set(false);
|
|
1560
|
+
this.address.set(address);
|
|
1561
|
+
if (!address)
|
|
1562
|
+
return;
|
|
1563
|
+
built.model.update((current) => {
|
|
1564
|
+
const next = { ...current };
|
|
1565
|
+
for (const def of doUzupelnienia) {
|
|
1566
|
+
const wartosc = address[def.autofill];
|
|
1567
|
+
// Puste pole = to, czego użytkownik nie tknął.
|
|
1568
|
+
if (typeof wartosc === 'string' && wartosc && !next[def.key])
|
|
1569
|
+
next[def.key] = wartosc;
|
|
1570
|
+
}
|
|
1571
|
+
return next;
|
|
1572
|
+
});
|
|
1573
|
+
},
|
|
1574
|
+
error: () => this.geocoding.set(false),
|
|
1575
|
+
});
|
|
1576
|
+
onCleanup(() => {
|
|
1577
|
+
subscription.unsubscribe();
|
|
1578
|
+
this.geocoding.set(false);
|
|
1579
|
+
});
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
/** Kontrolka pola po kluczu — szablon podaje ją do `[formField]`. */
|
|
1583
|
+
fieldOf(key) {
|
|
1584
|
+
return this.formRoot()?.[key];
|
|
1585
|
+
}
|
|
1586
|
+
/** Komunikat pokazujemy dopiero po dotknięciu pola — bramka żyje w prezentacji. */
|
|
1587
|
+
errorOf(key) {
|
|
1588
|
+
const node = this.fieldOf(key);
|
|
1589
|
+
if (!node)
|
|
1590
|
+
return null;
|
|
1591
|
+
const state = node();
|
|
1592
|
+
if (!state.touched() || !state.invalid())
|
|
1593
|
+
return null;
|
|
1594
|
+
return state.errors()[0]?.message ?? null;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Dzień otwarty musi mieć obie godziny i różne od siebie. Dzień oznaczony jako
|
|
1598
|
+
* zamknięty przechodzi bez zastrzeżeń — to prawidłowa odpowiedź, a nie brak danych.
|
|
1599
|
+
*/
|
|
1600
|
+
hoursError(week) {
|
|
1601
|
+
if (!week)
|
|
1602
|
+
return null;
|
|
1603
|
+
for (const day of week) {
|
|
1604
|
+
if (day.closed)
|
|
1605
|
+
continue;
|
|
1606
|
+
for (const range of day.ranges) {
|
|
1607
|
+
const from = parseTime(range.from);
|
|
1608
|
+
const to = parseTime(range.to);
|
|
1609
|
+
if (from === null || to === null || from === to) {
|
|
1610
|
+
return {
|
|
1611
|
+
kind: 'kptMapHoursIncomplete',
|
|
1612
|
+
message: this.i18n.t('map.field.hoursIncomplete', {
|
|
1613
|
+
day: weekdayName(day.day, this.i18n.locale()),
|
|
1614
|
+
}),
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if (day.ranges.length === 0) {
|
|
1619
|
+
return {
|
|
1620
|
+
kind: 'kptMapHoursIncomplete',
|
|
1621
|
+
message: this.i18n.t('map.field.hoursIncomplete', {
|
|
1622
|
+
day: weekdayName(day.day, this.i18n.locale()),
|
|
1623
|
+
}),
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
optionsOf(def) {
|
|
1630
|
+
return (def.options ?? []).map((o) => ({ value: o.value, label: o.label }));
|
|
1631
|
+
}
|
|
1632
|
+
controlId(def) {
|
|
1633
|
+
return `kpt-map-field-${def.key}`;
|
|
1634
|
+
}
|
|
1635
|
+
/** Czy pole renderuje jeden `<input>`, na który może wskazać `label[for]`. */
|
|
1636
|
+
hasOwnInput(def) {
|
|
1637
|
+
return def.type === 'text' || def.type === 'tel' || def.type === 'email' || def.type === 'url';
|
|
1638
|
+
}
|
|
1639
|
+
async onSubmit() {
|
|
1640
|
+
const built = this.built();
|
|
1641
|
+
if (!built)
|
|
1642
|
+
return;
|
|
1643
|
+
const { model, form: f } = built;
|
|
1644
|
+
// `submit()` sam oznacza całe drzewo jako dotknięte, więc błędy pokażą się od razu.
|
|
1645
|
+
await submit(f, {
|
|
1646
|
+
action: async () => {
|
|
1647
|
+
if (f().invalid())
|
|
1648
|
+
return;
|
|
1649
|
+
this.save.emit({ ...model() });
|
|
1650
|
+
},
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
static { this.ɵfac = function KptMapCreateDialog_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapCreateDialog)(); }; }
|
|
1654
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapCreateDialog, selectors: [["kpt-map-create-dialog"]], hostAttrs: [1, "kpt-map-create"], inputs: { category: [1, "category"], at: [1, "at"], geocoder: [1, "geocoder"] }, outputs: { save: "save", cancelled: "cancelled" }, decls: 1, vars: 1, consts: [[1, "kpt-map-create__form", 3, "formRoot"], [1, "kpt-map-create__form", 3, "submit", "formRoot"], [1, "kpt-map-create__coords"], ["aria-live", "polite", 1, "kpt-map-create__geo"], [3, "label", "hint", "error", "required", "controlId"], [1, "kpt-map-create__actions"], ["type", "button", "variant", "text", 3, "click"], ["type", "submit", 3, "disabled"], [3, "formField", "placeholder"], [3, "formField", "placeholder", "minDate", "maxDate"], [3, "formField", "options", "placeholder"], ["multiple", "", 3, "formField", "options", "placeholder"], [3, "formField", "label"], ["type", "tel", 3, "formField", "id", "placeholder"], ["type", "email", 3, "formField", "id", "placeholder"], ["type", "url", 3, "formField", "id", "placeholder"], [3, "formField"], [3, "formField", "id", "placeholder"]], template: function KptMapCreateDialog_Template(rf, ctx) { if (rf & 1) {
|
|
1655
|
+
i0.ɵɵconditionalCreate(0, KptMapCreateDialog_Conditional_0_Template, 20, 9, "form", 0);
|
|
1656
|
+
} if (rf & 2) {
|
|
1657
|
+
let tmp_0_0;
|
|
1658
|
+
i0.ɵɵconditional((tmp_0_0 = ctx.formRoot()) ? 0 : -1, tmp_0_0);
|
|
1659
|
+
} }, dependencies: [FormRoot,
|
|
1660
|
+
FormField,
|
|
1661
|
+
KptFormField,
|
|
1662
|
+
KptInput,
|
|
1663
|
+
KptTextarea,
|
|
1664
|
+
KptInputNumber,
|
|
1665
|
+
KptDatepicker,
|
|
1666
|
+
KptSelect,
|
|
1667
|
+
KptCheckbox,
|
|
1668
|
+
KptButton,
|
|
1669
|
+
KptMapHoursField,
|
|
1670
|
+
KptMapImageField], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-create__form{display:flex;flex-direction:column;gap:var(--%NS%kpt-space-4);min-width:min(28rem,100%)}.kpt-map-create__coords{display:flex;gap:var(--%NS%kpt-space-4);margin:0;font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-create__coords strong{font-variant-numeric:tabular-nums;color:var(--%NS%kpt-color-on-surface)}.kpt-map-create__geo{margin:calc(var(--%NS%kpt-space-3) * -1) 0 0;font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-create__actions{position:sticky;bottom:calc(var(--%NS%kpt-space-6) * -1);display:flex;gap:var(--%NS%kpt-space-2);justify-content:flex-end;margin:var(--%NS%kpt-space-2) calc(var(--%NS%kpt-space-6) * -1) calc(var(--%NS%kpt-space-6) * -1);padding:var(--%NS%kpt-space-4) var(--%NS%kpt-space-6);background:var(--%NS%kpt-color-surface-raised);border-top:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border)}}\n"], encapsulation: 2 }); }
|
|
1671
|
+
}
|
|
1672
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapCreateDialog, [{
|
|
1673
|
+
type: Component,
|
|
1674
|
+
args: [{ selector: 'kpt-map-create-dialog', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
|
|
1675
|
+
FormRoot,
|
|
1676
|
+
FormField,
|
|
1677
|
+
KptFormField,
|
|
1678
|
+
KptInput,
|
|
1679
|
+
KptTextarea,
|
|
1680
|
+
KptInputNumber,
|
|
1681
|
+
KptDatepicker,
|
|
1682
|
+
KptSelect,
|
|
1683
|
+
KptCheckbox,
|
|
1684
|
+
KptButton,
|
|
1685
|
+
KptMapHoursField,
|
|
1686
|
+
KptMapImageField,
|
|
1687
|
+
], 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"] }]
|
|
1688
|
+
}], () => [], { 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"] }] }); })();
|
|
1689
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapCreateDialog, { className: "KptMapCreateDialog", filePath: "lib/map-create-dialog.component.ts", lineNumber: 110 }); })();
|
|
1690
|
+
|
|
1691
|
+
const _forTrack0$3 = ($index, $item) => $item.category.id;
|
|
1692
|
+
function KptMapLegend_For_1_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
1693
|
+
i0.ɵɵelement(0, "kpt-icon", 3);
|
|
1694
|
+
} if (rf & 2) {
|
|
1695
|
+
const entry_r2 = i0.ɵɵnextContext().$implicit;
|
|
1696
|
+
i0.ɵɵproperty("name", entry_r2.category.icon);
|
|
1697
|
+
} }
|
|
1698
|
+
function KptMapLegend_For_1_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
1699
|
+
i0.ɵɵelementStart(0, "span", 5);
|
|
1700
|
+
i0.ɵɵtext(1);
|
|
1701
|
+
i0.ɵɵelementEnd();
|
|
1702
|
+
} if (rf & 2) {
|
|
1703
|
+
const entry_r2 = i0.ɵɵnextContext().$implicit;
|
|
1704
|
+
i0.ɵɵadvance();
|
|
1705
|
+
i0.ɵɵtextInterpolate(entry_r2.count);
|
|
1706
|
+
} }
|
|
1707
|
+
function KptMapLegend_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
1708
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
1709
|
+
i0.ɵɵelementStart(0, "button", 1);
|
|
1710
|
+
i0.ɵɵlistener("click", function KptMapLegend_For_1_Template_button_click_0_listener() { const entry_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.toggle(entry_r2)); });
|
|
1711
|
+
i0.ɵɵelementStart(1, "span", 2);
|
|
1712
|
+
i0.ɵɵconditionalCreate(2, KptMapLegend_For_1_Conditional_2_Template, 1, 1, "kpt-icon", 3);
|
|
1713
|
+
i0.ɵɵelementEnd();
|
|
1714
|
+
i0.ɵɵelementStart(3, "span", 4);
|
|
1715
|
+
i0.ɵɵtext(4);
|
|
1716
|
+
i0.ɵɵelementEnd();
|
|
1717
|
+
i0.ɵɵconditionalCreate(5, KptMapLegend_For_1_Conditional_5_Template, 2, 1, "span", 5);
|
|
1718
|
+
i0.ɵɵelementEnd();
|
|
1719
|
+
} if (rf & 2) {
|
|
1720
|
+
const entry_r2 = ctx.$implicit;
|
|
1721
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
1722
|
+
i0.ɵɵstyleProp("--%NS%_kpt-legend-color", entry_r2.colorVar)("--%NS%_kpt-legend-fg", entry_r2.contrastVar);
|
|
1723
|
+
i0.ɵɵclassProp("kpt-map-legend__item--off", entry_r2.hidden);
|
|
1724
|
+
i0.ɵɵproperty("disabled", !ctx_r2.interactive());
|
|
1725
|
+
i0.ɵɵattribute("aria-pressed", ctx_r2.interactive() ? !entry_r2.hidden : null)("aria-label", ctx_r2.interactive() ? ctx_r2.label(entry_r2) : null);
|
|
1726
|
+
i0.ɵɵadvance(2);
|
|
1727
|
+
i0.ɵɵconditional(entry_r2.category.icon ? 2 : -1);
|
|
1728
|
+
i0.ɵɵadvance(2);
|
|
1729
|
+
i0.ɵɵtextInterpolate(entry_r2.category.label);
|
|
1730
|
+
i0.ɵɵadvance();
|
|
1731
|
+
i0.ɵɵconditional(entry_r2.count !== null ? 5 : -1);
|
|
1732
|
+
} }
|
|
1733
|
+
/**
|
|
1734
|
+
* Legenda mapy — lista kategorii ze znacznikiem koloru i ikoną. Pozycje są przyciskami:
|
|
1735
|
+
* kliknięcie chowa i pokazuje kategorię (`hidden` dwukierunkowe).
|
|
1736
|
+
*
|
|
1737
|
+
* `kpt-map` renderuje ją sam pod mapą przy `[legend]="true"`; kto chce ją gdzie indziej,
|
|
1738
|
+
* wyłącza tamto i wstawia ten komponent ręcznie.
|
|
1739
|
+
*
|
|
1740
|
+
* @example
|
|
1741
|
+
* <kpt-map-legend [categories]="categories" [(hidden)]="hiddenCategories" [counts]="counts" />
|
|
1742
|
+
*/
|
|
1743
|
+
class KptMapLegend {
|
|
1744
|
+
constructor() {
|
|
1745
|
+
this.categories = input.required(/* @ts-ignore */
|
|
1746
|
+
...(ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []));
|
|
1747
|
+
/** Identyfikatory ukrytych kategorii (dwukierunkowe). */
|
|
1748
|
+
this.hidden = model([], /* @ts-ignore */
|
|
1749
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
1750
|
+
/** Opcjonalne liczniki `{ categoryId: liczba }` — pokazywane przy etykiecie. */
|
|
1751
|
+
this.counts = input(null, /* @ts-ignore */
|
|
1752
|
+
...(ngDevMode ? [{ debugName: "counts" }] : /* istanbul ignore next */ []));
|
|
1753
|
+
this.orientation = input('horizontal', /* @ts-ignore */
|
|
1754
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
1755
|
+
/** Wyłącza chowanie kategorii — legenda staje się czysto informacyjna. */
|
|
1756
|
+
this.interactive = input(true, /* @ts-ignore */
|
|
1757
|
+
...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
1758
|
+
this.i18n = inject(KptI18n);
|
|
1759
|
+
this.entries = computed(() => {
|
|
1760
|
+
const hidden = new Set(this.hidden());
|
|
1761
|
+
const counts = this.counts();
|
|
1762
|
+
return this.categories()
|
|
1763
|
+
.filter((c) => c.legend !== false)
|
|
1764
|
+
.map((category) => ({
|
|
1765
|
+
category,
|
|
1766
|
+
colorVar: colorVarOf(category.color),
|
|
1767
|
+
contrastVar: contrastVarOf(category.color),
|
|
1768
|
+
hidden: hidden.has(category.id),
|
|
1769
|
+
count: counts ? (counts[category.id] ?? 0) : null,
|
|
1770
|
+
}));
|
|
1771
|
+
}, /* @ts-ignore */
|
|
1772
|
+
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
1773
|
+
}
|
|
1774
|
+
toggle(entry) {
|
|
1775
|
+
if (!this.interactive())
|
|
1776
|
+
return;
|
|
1777
|
+
const id = entry.category.id;
|
|
1778
|
+
this.hidden.update((current) => current.includes(id) ? current.filter((c) => c !== id) : [...current, id]);
|
|
1779
|
+
}
|
|
1780
|
+
label(entry) {
|
|
1781
|
+
const key = entry.hidden ? 'map.showCategory' : 'map.hideCategory';
|
|
1782
|
+
return this.i18n.t(key, { category: entry.category.label });
|
|
1783
|
+
}
|
|
1784
|
+
static { this.ɵfac = function KptMapLegend_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapLegend)(); }; }
|
|
1785
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapLegend, selectors: [["kpt-map-legend"]], hostAttrs: ["role", "group", 1, "kpt-map-legend"], hostVars: 2, hostBindings: function KptMapLegend_HostBindings(rf, ctx) { if (rf & 2) {
|
|
1786
|
+
i0.ɵɵattribute("data-orientation", ctx.orientation())("aria-label", ctx.i18n.t("map.legend"));
|
|
1787
|
+
} }, inputs: { categories: [1, "categories"], hidden: [1, "hidden"], counts: [1, "counts"], orientation: [1, "orientation"], interactive: [1, "interactive"] }, outputs: { hidden: "hiddenChange" }, decls: 2, vars: 0, consts: [["type", "button", 1, "kpt-map-legend__item", 3, "--%NS%_kpt-legend-color", "--%NS%_kpt-legend-fg", "kpt-map-legend__item--off", "disabled"], ["type", "button", 1, "kpt-map-legend__item", 3, "click", "disabled"], ["aria-hidden", "true", 1, "kpt-map-legend__swatch"], [3, "name"], [1, "kpt-map-legend__label"], [1, "kpt-map-legend__count"]], template: function KptMapLegend_Template(rf, ctx) { if (rf & 1) {
|
|
1788
|
+
i0.ɵɵrepeaterCreate(0, KptMapLegend_For_1_Template, 6, 12, "button", 0, _forTrack0$3);
|
|
1789
|
+
} if (rf & 2) {
|
|
1790
|
+
i0.ɵɵrepeater(ctx.entries());
|
|
1791
|
+
} }, dependencies: [KptIcon], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-legend{display:flex;flex-wrap:wrap;gap:var(--%NS%kpt-space-2);align-items:center}.kpt-map-legend[data-orientation=vertical]{flex-direction:column;align-items:stretch}.kpt-map-legend__item{display:inline-flex;gap:var(--%NS%kpt-space-2);align-items:center;padding:var(--%NS%kpt-space-1) var(--%NS%kpt-space-2);font:inherit;font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface);cursor:pointer;background:transparent;border:var(--%NS%kpt-border-width-thin) solid transparent;border-radius:var(--%NS%kpt-radius-full);transition:background-color var(--%NS%kpt-motion-duration-fast) var(--%NS%kpt-motion-easing-standard)}.kpt-map-legend__item:hover:not(:disabled){background:var(--%NS%kpt-color-surface-hover)}.kpt-map-legend__item:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:2px}.kpt-map-legend__item:disabled{cursor:default}.kpt-map-legend__item.kpt-map-legend__item--off{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-legend__item.kpt-map-legend__item--off .kpt-map-legend__swatch{background:transparent;border-color:var(--%NS%kpt-color-border-strong)}.kpt-map-legend__swatch{display:inline-flex;flex:none;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;font-size:.875rem;color:var(--%NS%_kpt-legend-fg, var(--%NS%kpt-color-text-inverse));background:var(--%NS%_kpt-legend-color, var(--%NS%kpt-color-muted));border:var(--%NS%kpt-border-width-thin) solid transparent;border-radius:var(--%NS%kpt-radius-full)}.kpt-map-legend__count{font-variant-numeric:tabular-nums;color:var(--%NS%kpt-color-on-surface-muted)}@media(prefers-reduced-motion:reduce){.kpt-map-legend__item{transition:none}}}\n"], encapsulation: 2 }); }
|
|
1792
|
+
}
|
|
1793
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapLegend, [{
|
|
1794
|
+
type: Component,
|
|
1795
|
+
args: [{ selector: 'kpt-map-legend', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptIcon], host: {
|
|
1796
|
+
class: 'kpt-map-legend',
|
|
1797
|
+
'[attr.data-orientation]': 'orientation()',
|
|
1798
|
+
role: 'group',
|
|
1799
|
+
'[attr.aria-label]': 'i18n.t("map.legend")',
|
|
1800
|
+
}, template: "@for (entry of entries(); track entry.category.id) {\n <button\n class=\"kpt-map-legend__item\"\n type=\"button\"\n [style.--_kpt-legend-color]=\"entry.colorVar\"\n [style.--_kpt-legend-fg]=\"entry.contrastVar\"\n [class.kpt-map-legend__item--off]=\"entry.hidden\"\n [attr.aria-pressed]=\"interactive() ? !entry.hidden : null\"\n [attr.aria-label]=\"interactive() ? label(entry) : null\"\n [disabled]=\"!interactive()\"\n (click)=\"toggle(entry)\"\n >\n <span class=\"kpt-map-legend__swatch\" aria-hidden=\"true\">\n @if (entry.category.icon) {\n <kpt-icon [name]=\"entry.category.icon\" />\n }\n </span>\n <span class=\"kpt-map-legend__label\">{{ entry.category.label }}</span>\n @if (entry.count !== null) {\n <span class=\"kpt-map-legend__count\">{{ entry.count }}</span>\n }\n </button>\n}\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-legend{display:flex;flex-wrap:wrap;gap:var(--kpt-space-2);align-items:center}.kpt-map-legend[data-orientation=vertical]{flex-direction:column;align-items:stretch}.kpt-map-legend__item{display:inline-flex;gap:var(--kpt-space-2);align-items:center;padding:var(--kpt-space-1) var(--kpt-space-2);font:inherit;font-size:var(--kpt-font-size-sm);color:var(--kpt-color-on-surface);cursor:pointer;background:transparent;border:var(--kpt-border-width-thin) solid transparent;border-radius:var(--kpt-radius-full);transition:background-color var(--kpt-motion-duration-fast) var(--kpt-motion-easing-standard)}.kpt-map-legend__item:hover:not(:disabled){background:var(--kpt-color-surface-hover)}.kpt-map-legend__item:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:2px}.kpt-map-legend__item:disabled{cursor:default}.kpt-map-legend__item.kpt-map-legend__item--off{color:var(--kpt-color-on-surface-muted)}.kpt-map-legend__item.kpt-map-legend__item--off .kpt-map-legend__swatch{background:transparent;border-color:var(--kpt-color-border-strong)}.kpt-map-legend__swatch{display:inline-flex;flex:none;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;font-size:.875rem;color:var(--_kpt-legend-fg, var(--kpt-color-text-inverse));background:var(--_kpt-legend-color, var(--kpt-color-muted));border:var(--kpt-border-width-thin) solid transparent;border-radius:var(--kpt-radius-full)}.kpt-map-legend__count{font-variant-numeric:tabular-nums;color:var(--kpt-color-on-surface-muted)}@media(prefers-reduced-motion:reduce){.kpt-map-legend__item{transition:none}}}\n"] }]
|
|
1801
|
+
}], null, { categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: true }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }, { type: i0.Output, args: ["hiddenChange"] }], counts: [{ type: i0.Input, args: [{ isSignal: true, alias: "counts", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }] }); })();
|
|
1802
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapLegend, { className: "KptMapLegend", filePath: "lib/map-legend.component.ts", lineNumber: 47 }); })();
|
|
1803
|
+
|
|
1804
|
+
const _c0$2 = a0 => ({ count: a0 });
|
|
1805
|
+
const _forTrack0$2 = ($index, $item) => $item.id;
|
|
1806
|
+
function KptMapSearch_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
1807
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
1808
|
+
i0.ɵɵelementStart(0, "button", 7);
|
|
1809
|
+
i0.ɵɵlistener("click", function KptMapSearch_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.clear()); });
|
|
1810
|
+
i0.ɵɵelement(1, "kpt-icon", 8);
|
|
1811
|
+
i0.ɵɵelementEnd();
|
|
1812
|
+
} if (rf & 2) {
|
|
1813
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
1814
|
+
i0.ɵɵproperty("title", ctx_r1.i18n.t("map.search.clear"));
|
|
1815
|
+
i0.ɵɵattribute("aria-label", ctx_r1.i18n.t("map.search.clear"));
|
|
1816
|
+
} }
|
|
1817
|
+
function KptMapSearch_Conditional_5_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
1818
|
+
i0.ɵɵelementStart(0, "p", 9);
|
|
1819
|
+
i0.ɵɵtext(1);
|
|
1820
|
+
i0.ɵɵelementEnd();
|
|
1821
|
+
} if (rf & 2) {
|
|
1822
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1823
|
+
i0.ɵɵadvance();
|
|
1824
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.search.minLength", i0.ɵɵpureFunction1(1, _c0$2, ctx_r1.minLength())));
|
|
1825
|
+
} }
|
|
1826
|
+
function KptMapSearch_Conditional_5_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
1827
|
+
i0.ɵɵelementStart(0, "p", 9);
|
|
1828
|
+
i0.ɵɵtext(1);
|
|
1829
|
+
i0.ɵɵelementEnd();
|
|
1830
|
+
} if (rf & 2) {
|
|
1831
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1832
|
+
i0.ɵɵadvance();
|
|
1833
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.search.searching"));
|
|
1834
|
+
} }
|
|
1835
|
+
function KptMapSearch_Conditional_5_Conditional_3_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
1836
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
1837
|
+
i0.ɵɵelementStart(0, "li", 12);
|
|
1838
|
+
i0.ɵɵlistener("mousedown", function KptMapSearch_Conditional_5_Conditional_3_For_2_Template_li_mousedown_0_listener($event) { const result_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); $event.preventDefault(); return i0.ɵɵresetView(ctx_r1.pick(result_r4)); })("mouseenter", function KptMapSearch_Conditional_5_Conditional_3_For_2_Template_li_mouseenter_0_listener() { const ɵ$index_28_r5 = i0.ɵɵrestoreView(_r3).$index; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.activeIndex.set(ɵ$index_28_r5)); });
|
|
1839
|
+
i0.ɵɵelementStart(1, "span", 13);
|
|
1840
|
+
i0.ɵɵelement(2, "kpt-icon", 14);
|
|
1841
|
+
i0.ɵɵelementEnd();
|
|
1842
|
+
i0.ɵɵelementStart(3, "span", 15);
|
|
1843
|
+
i0.ɵɵtext(4);
|
|
1844
|
+
i0.ɵɵelementEnd()();
|
|
1845
|
+
} if (rf & 2) {
|
|
1846
|
+
const result_r4 = ctx.$implicit;
|
|
1847
|
+
const ɵ$index_28_r5 = ctx.$index;
|
|
1848
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
1849
|
+
i0.ɵɵclassProp("is-active", ɵ$index_28_r5 === ctx_r1.activeIndex());
|
|
1850
|
+
i0.ɵɵproperty("id", ctx_r1.optionId(ɵ$index_28_r5));
|
|
1851
|
+
i0.ɵɵattribute("aria-selected", ɵ$index_28_r5 === ctx_r1.activeIndex());
|
|
1852
|
+
i0.ɵɵadvance(4);
|
|
1853
|
+
i0.ɵɵtextInterpolate(result_r4.label);
|
|
1854
|
+
} }
|
|
1855
|
+
function KptMapSearch_Conditional_5_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
1856
|
+
i0.ɵɵelementStart(0, "ul", 10);
|
|
1857
|
+
i0.ɵɵrepeaterCreate(1, KptMapSearch_Conditional_5_Conditional_3_For_2_Template, 5, 5, "li", 11, _forTrack0$2);
|
|
1858
|
+
i0.ɵɵelementEnd();
|
|
1859
|
+
} if (rf & 2) {
|
|
1860
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1861
|
+
i0.ɵɵattribute("aria-label", ctx_r1.i18n.t("map.search.results"));
|
|
1862
|
+
i0.ɵɵadvance();
|
|
1863
|
+
i0.ɵɵrepeater(ctx_r1.results());
|
|
1864
|
+
} }
|
|
1865
|
+
function KptMapSearch_Conditional_5_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
1866
|
+
i0.ɵɵelementStart(0, "p", 9);
|
|
1867
|
+
i0.ɵɵtext(1);
|
|
1868
|
+
i0.ɵɵelementEnd();
|
|
1869
|
+
} if (rf & 2) {
|
|
1870
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
1871
|
+
i0.ɵɵadvance();
|
|
1872
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.search.noResults"));
|
|
1873
|
+
} }
|
|
1874
|
+
function KptMapSearch_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
1875
|
+
i0.ɵɵelementStart(0, "div", 5);
|
|
1876
|
+
i0.ɵɵconditionalCreate(1, KptMapSearch_Conditional_5_Conditional_1_Template, 2, 3, "p", 9)(2, KptMapSearch_Conditional_5_Conditional_2_Template, 2, 1, "p", 9)(3, KptMapSearch_Conditional_5_Conditional_3_Template, 3, 1, "ul", 10)(4, KptMapSearch_Conditional_5_Conditional_4_Template, 2, 1, "p", 9);
|
|
1877
|
+
i0.ɵɵelementEnd();
|
|
1878
|
+
} if (rf & 2) {
|
|
1879
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
1880
|
+
i0.ɵɵproperty("id", ctx_r1.uid + "-panel");
|
|
1881
|
+
i0.ɵɵadvance();
|
|
1882
|
+
i0.ɵɵconditional(ctx_r1.tooShort() ? 1 : ctx_r1.busy() ? 2 : ctx_r1.results().length ? 3 : 4);
|
|
1883
|
+
} }
|
|
1884
|
+
/** Identyfikatory pozycji listy dla `aria-activedescendant` — muszą być unikalne w dokumencie. */
|
|
1885
|
+
let nextId = 0;
|
|
1886
|
+
/**
|
|
1887
|
+
* Wyszukiwarka adresów: pole z podpowiedziami z geokodera. Wynik wyboru leci w `selected`,
|
|
1888
|
+
* a co z nim zrobić — decyduje rodzic (`kpt-map` przenosi na niego widok).
|
|
1889
|
+
*
|
|
1890
|
+
* Wymaga portu `KPT_MAP_GEOCODER` **z metodą `search()`** — biblioteka nie odpytuje cudzego
|
|
1891
|
+
* serwera bez zgody aplikacji. Bez portu pole renderuje się wyłączone, zamiast udawać, że
|
|
1892
|
+
* coś znajdzie; `kpt-map` idzie dalej i w ogóle go wtedy nie pokazuje.
|
|
1893
|
+
*
|
|
1894
|
+
* Świadomie **nie** stoi na `kpt-autocomplete`: tamten filtruje opcje po stronie klienta
|
|
1895
|
+
* (wyniki geokodera, tolerancyjne na literówki, wypadałyby z listy), jego wartością jest
|
|
1896
|
+
* sam `string` (nie ma czym przewieźć współrzędnych) i wymaga CDK Overlay, którego mapa
|
|
1897
|
+
* poza tym nie potrzebuje.
|
|
1898
|
+
*
|
|
1899
|
+
* @example
|
|
1900
|
+
* <kpt-map-search [viewbox]="bounds" (selected)="goTo($event)" />
|
|
1901
|
+
*/
|
|
1902
|
+
class KptMapSearch {
|
|
1903
|
+
constructor() {
|
|
1904
|
+
/** Podpowiedź w pustym polu. Pusta oddaje głos słownikowi `map.search.placeholder`. */
|
|
1905
|
+
this.placeholder = input('', /* @ts-ignore */
|
|
1906
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
1907
|
+
/** Od ilu znaków pytać geokoder. Krótsze frazy zwracają pół świata. */
|
|
1908
|
+
this.minLength = input(3, { ...(ngDevMode ? { debugName: "minLength" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1909
|
+
/** Ile milisekund ciszy przed zapytaniem. */
|
|
1910
|
+
this.debounce = input(300, { ...(ngDevMode ? { debugName: "debounce" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1911
|
+
/** Ile podpowiedzi najwyżej. */
|
|
1912
|
+
this.limit = input(5, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1913
|
+
/** Zawężenie twarde do krajów (ISO 3166-1 alpha-2) — wyniki spoza nich nie wrócą. */
|
|
1914
|
+
this.countryCodes = input(null, /* @ts-ignore */
|
|
1915
|
+
...(ngDevMode ? [{ debugName: "countryCodes" }] : /* istanbul ignore next */ []));
|
|
1916
|
+
/** Preferowany prostokąt wyników. Nie odcina reszty świata, tylko przesuwa ją niżej. */
|
|
1917
|
+
this.viewbox = input(null, /* @ts-ignore */
|
|
1918
|
+
...(ngDevMode ? [{ debugName: "viewbox" }] : /* istanbul ignore next */ []));
|
|
1919
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1920
|
+
/**
|
|
1921
|
+
* Geokoder tego pola. Podany wygrywa nad `KPT_MAP_GEOCODER` z DI — `kpt-map` podaje tędy
|
|
1922
|
+
* port swojej mapy, bo geokoder jest per mapa, a nie per aplikacja. Bez niego wszystko
|
|
1923
|
+
* działa jak dotąd: port przychodzi z DI.
|
|
1924
|
+
*/
|
|
1925
|
+
this.geocoder = input(null, /* @ts-ignore */
|
|
1926
|
+
...(ngDevMode ? [{ debugName: "geocoder" }] : /* istanbul ignore next */ []));
|
|
1927
|
+
/**
|
|
1928
|
+
* Wybrano miejsce. **Zawsze ze współrzędnymi**: podpowiedź, której dostawca ich nie podał,
|
|
1929
|
+
* pole najpierw uzupełnia przez `resolve()` portu, a dopiero potem emituje.
|
|
1930
|
+
*/
|
|
1931
|
+
this.selected = output();
|
|
1932
|
+
/** Pole wyczyszczone — rodzic sprząta po sobie (np. chowa tymczasową pinezkę). */
|
|
1933
|
+
this.cleared = output();
|
|
1934
|
+
this.i18n = inject(KptI18n);
|
|
1935
|
+
this.injectedGeocoder = inject(KPT_MAP_GEOCODER, { optional: true });
|
|
1936
|
+
/** Rozstrzygnięty port: wejście przed DI. Czytany reaktywnie, bo mapa może go podmienić. */
|
|
1937
|
+
this.port = computed(() => this.geocoder() ?? this.injectedGeocoder, /* @ts-ignore */
|
|
1938
|
+
...(ngDevMode ? [{ debugName: "port" }] : /* istanbul ignore next */ []));
|
|
1939
|
+
this.uid = `kpt-map-search-${++nextId}`;
|
|
1940
|
+
/** Tekst widoczny w polu. Wybór podpowiedzi wpisuje tu jej etykietę. */
|
|
1941
|
+
this.query = signal('', /* @ts-ignore */
|
|
1942
|
+
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
1943
|
+
/**
|
|
1944
|
+
* Fraza do odpytania geokodera. Osobna od `query`, bo wybór podpowiedzi zmienia treść
|
|
1945
|
+
* pola — a to nie może wywołać kolejnego zapytania o coś, co już znaleźliśmy.
|
|
1946
|
+
*/
|
|
1947
|
+
this.term = signal('', /* @ts-ignore */
|
|
1948
|
+
...(ngDevMode ? [{ debugName: "term" }] : /* istanbul ignore next */ []));
|
|
1949
|
+
this.results = signal([], /* @ts-ignore */
|
|
1950
|
+
...(ngDevMode ? [{ debugName: "results" }] : /* istanbul ignore next */ []));
|
|
1951
|
+
this.loading = signal(false, /* @ts-ignore */
|
|
1952
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1953
|
+
/**
|
|
1954
|
+
* Trwa dociąganie współrzędnych wybranej podpowiedzi (`resolve()` portu). Osobne od
|
|
1955
|
+
* `loading`, bo to inny moment: tam czekamy na listę, tu na jedno konkretne miejsce.
|
|
1956
|
+
*/
|
|
1957
|
+
this.resolving = signal(false, /* @ts-ignore */
|
|
1958
|
+
...(ngDevMode ? [{ debugName: "resolving" }] : /* istanbul ignore next */ []));
|
|
1959
|
+
/** Subskrypcja `resolve()` w locie — kolejny wybór i czyszczenie pola ją przerywają. */
|
|
1960
|
+
this.pending = null;
|
|
1961
|
+
/** Czy jakieś zapytanie już wróciło — bez tego „brak wyników" mignąłby przed pierwszym. */
|
|
1962
|
+
this.searched = signal(false, /* @ts-ignore */
|
|
1963
|
+
...(ngDevMode ? [{ debugName: "searched" }] : /* istanbul ignore next */ []));
|
|
1964
|
+
this.open = signal(false, /* @ts-ignore */
|
|
1965
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
1966
|
+
this.activeIndex = signal(-1, /* @ts-ignore */
|
|
1967
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
1968
|
+
/** Fraza za krótka, ale niepusta — zamiast ciszy pokazujemy, ile znaków brakuje. */
|
|
1969
|
+
this.tooShort = computed(() => {
|
|
1970
|
+
const length = this.query().trim().length;
|
|
1971
|
+
return length > 0 && length < this.minLength();
|
|
1972
|
+
}, /* @ts-ignore */
|
|
1973
|
+
...(ngDevMode ? [{ debugName: "tooShort" }] : /* istanbul ignore next */ []));
|
|
1974
|
+
this.hasPanel = computed(() => this.open() &&
|
|
1975
|
+
(this.tooShort() || this.busy() || this.results().length > 0 || this.searched()), /* @ts-ignore */
|
|
1976
|
+
...(ngDevMode ? [{ debugName: "hasPanel" }] : /* istanbul ignore next */ []));
|
|
1977
|
+
/** Cokolwiek leci do geokodera — lista albo pojedyncze miejsce. Jeden stan dla szablonu. */
|
|
1978
|
+
this.busy = computed(() => this.loading() || this.resolving(), /* @ts-ignore */
|
|
1979
|
+
...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
1980
|
+
this.canSearch = computed(() => !!this.port()?.search, /* @ts-ignore */
|
|
1981
|
+
...(ngDevMode ? [{ debugName: "canSearch" }] : /* istanbul ignore next */ []));
|
|
1982
|
+
/** Nie udało się ustalić miejsca wybranej podpowiedzi — komunikat zamiast cichej ciszy. */
|
|
1983
|
+
this.unresolved = signal(false, /* @ts-ignore */
|
|
1984
|
+
...(ngDevMode ? [{ debugName: "unresolved" }] : /* istanbul ignore next */ []));
|
|
1985
|
+
/** Komunikat dla `aria-live` — czytnik dostaje stan, którego wzrokowo nie da się przegapić. */
|
|
1986
|
+
this.status = computed(() => {
|
|
1987
|
+
if (this.tooShort())
|
|
1988
|
+
return this.i18n.t('map.search.minLength', { count: this.minLength() });
|
|
1989
|
+
if (this.busy())
|
|
1990
|
+
return this.i18n.t('map.search.searching');
|
|
1991
|
+
if (this.unresolved())
|
|
1992
|
+
return this.i18n.t('map.search.noResults');
|
|
1993
|
+
if (!this.searched())
|
|
1994
|
+
return '';
|
|
1995
|
+
const count = this.results().length;
|
|
1996
|
+
return count === 0
|
|
1997
|
+
? this.i18n.t('map.search.noResults')
|
|
1998
|
+
: this.i18n.t('map.search.found', { count });
|
|
1999
|
+
}, /* @ts-ignore */
|
|
2000
|
+
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
2001
|
+
this.placeholderText = computed(() => this.placeholder() || this.i18n.t('map.search.placeholder'), /* @ts-ignore */
|
|
2002
|
+
...(ngDevMode ? [{ debugName: "placeholderText" }] : /* istanbul ignore next */ []));
|
|
2003
|
+
this.watchTerm();
|
|
2004
|
+
inject(DestroyRef).onDestroy(() => this.pending?.unsubscribe());
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Odpytuje geokoder po odczekaniu `debounce` od ostatniego znaku.
|
|
2008
|
+
*
|
|
2009
|
+
* Debounce stoi na `setTimeout`, a nie na operatorach rxjs: porty tego pakietu używają
|
|
2010
|
+
* wyłącznie `Observable`/`of`, a rxjs nie jest zadeklarowany jako `peerDependency`
|
|
2011
|
+
* biblioteki (wchodzi tranzytywnie przez `@angular/core`). `onCleanup` kasuje zarówno
|
|
2012
|
+
* niewystrzelony timer, jak i zapytanie w locie — adapter Nominatim przerywa wtedy
|
|
2013
|
+
* `fetch` własnym `AbortController`.
|
|
2014
|
+
*/
|
|
2015
|
+
watchTerm() {
|
|
2016
|
+
effect((onCleanup) => {
|
|
2017
|
+
const term = this.term().trim();
|
|
2018
|
+
const geocoder = this.port();
|
|
2019
|
+
if (!geocoder?.search || term.length < this.minLength()) {
|
|
2020
|
+
this.results.set([]);
|
|
2021
|
+
this.loading.set(false);
|
|
2022
|
+
this.searched.set(false);
|
|
2023
|
+
this.activeIndex.set(-1);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
// Reszta opcji czytana bez śledzenia: zapytanie i tak leci po każdym znaku,
|
|
2027
|
+
// a tablica wpisana wprost w szablonie rodzica jest przy każdym przebiegu nowa
|
|
2028
|
+
// i sama w sobie wywoływałaby ten efekt w kółko.
|
|
2029
|
+
const [wait, options] = untracked(() => [
|
|
2030
|
+
this.debounce(),
|
|
2031
|
+
{
|
|
2032
|
+
language: this.i18n.locale(),
|
|
2033
|
+
limit: this.limit(),
|
|
2034
|
+
viewbox: this.viewbox() ?? undefined,
|
|
2035
|
+
countryCodes: this.countryCodes() ?? undefined,
|
|
2036
|
+
},
|
|
2037
|
+
]);
|
|
2038
|
+
this.loading.set(true);
|
|
2039
|
+
let subscription = null;
|
|
2040
|
+
const timer = setTimeout(() => {
|
|
2041
|
+
subscription = geocoder.search(term, options).subscribe({
|
|
2042
|
+
next: (results) => this.applyResults(results),
|
|
2043
|
+
// Nieudane zapytanie to pusta lista, a nie wywrotka pola.
|
|
2044
|
+
error: () => this.applyResults([]),
|
|
2045
|
+
});
|
|
2046
|
+
}, wait);
|
|
2047
|
+
onCleanup(() => {
|
|
2048
|
+
clearTimeout(timer);
|
|
2049
|
+
subscription?.unsubscribe();
|
|
2050
|
+
});
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
applyResults(results) {
|
|
2054
|
+
// Podpowiedź bez współrzędnych jest do czegokolwiek dobra tylko wtedy, gdy port umie
|
|
2055
|
+
// ją uzupełnić. Bez `resolve()` kliknięcie w nią nie miałoby dokąd polecieć, a pozycja,
|
|
2056
|
+
// która nic nie robi, jest gorsza od jej braku — więc taka nie trafia na listę.
|
|
2057
|
+
this.results.set(this.port()?.resolve ? results : results.filter((r) => r.at));
|
|
2058
|
+
this.loading.set(false);
|
|
2059
|
+
this.searched.set(true);
|
|
2060
|
+
this.activeIndex.set(-1);
|
|
2061
|
+
this.open.set(true);
|
|
2062
|
+
}
|
|
2063
|
+
optionId(index) {
|
|
2064
|
+
return `${this.uid}-opt-${index}`;
|
|
2065
|
+
}
|
|
2066
|
+
onInput(event) {
|
|
2067
|
+
const value = event.target.value;
|
|
2068
|
+
this.query.set(value);
|
|
2069
|
+
this.term.set(value);
|
|
2070
|
+
this.open.set(true);
|
|
2071
|
+
this.unresolved.set(false);
|
|
2072
|
+
}
|
|
2073
|
+
onFocus() {
|
|
2074
|
+
this.open.set(true);
|
|
2075
|
+
}
|
|
2076
|
+
onBlur() {
|
|
2077
|
+
this.open.set(false);
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Wybór podpowiedzi. Dostawca, który podał współrzędne na liście, emituje natychmiast —
|
|
2081
|
+
* dla Nominatima i każdego geokodera pytanego wprost nic się nie zmienia.
|
|
2082
|
+
*
|
|
2083
|
+
* Podpowiedź bez `at` (Google Places Autocomplete) trzeba najpierw uzupełnić: jedno
|
|
2084
|
+
* wywołanie `resolve()` na *wybór*, nigdy w trakcie pisania. Panel zostaje otwarty
|
|
2085
|
+
* z komunikatem „Szukam…", żeby czekanie nie wyglądało na zawieszenie pola.
|
|
2086
|
+
*/
|
|
2087
|
+
pick(suggestion) {
|
|
2088
|
+
// Etykieta wraca do pola, ale `term` zostaje — inaczej wybór odpaliłby kolejne zapytanie.
|
|
2089
|
+
this.query.set(suggestion.label);
|
|
2090
|
+
this.activeIndex.set(-1);
|
|
2091
|
+
this.unresolved.set(false);
|
|
2092
|
+
this.pending?.unsubscribe();
|
|
2093
|
+
this.pending = null;
|
|
2094
|
+
if (suggestion.at) {
|
|
2095
|
+
this.open.set(false);
|
|
2096
|
+
this.resolving.set(false);
|
|
2097
|
+
this.selected.emit(suggestion);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
const geocoder = this.port();
|
|
2101
|
+
// Do listy trafiają tylko pozycje możliwe do obsłużenia, więc tu już nie ma czego ratować.
|
|
2102
|
+
if (!geocoder?.resolve)
|
|
2103
|
+
return;
|
|
2104
|
+
this.resolving.set(true);
|
|
2105
|
+
this.pending = geocoder.resolve(suggestion, { language: this.i18n.locale() }).subscribe({
|
|
2106
|
+
next: (result) => this.applyResolved(result),
|
|
2107
|
+
// Nieudane dociągnięcie to komunikat, a nie wywrotka pola.
|
|
2108
|
+
error: () => this.applyResolved(null),
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
applyResolved(result) {
|
|
2112
|
+
this.resolving.set(false);
|
|
2113
|
+
this.pending = null;
|
|
2114
|
+
if (!result) {
|
|
2115
|
+
// Etykieta zostaje w polu, żeby dało się poprawić frazę zamiast pisać ją od zera.
|
|
2116
|
+
this.unresolved.set(true);
|
|
2117
|
+
this.open.set(true);
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
this.open.set(false);
|
|
2121
|
+
this.selected.emit(result);
|
|
2122
|
+
}
|
|
2123
|
+
clear() {
|
|
2124
|
+
this.pending?.unsubscribe();
|
|
2125
|
+
this.pending = null;
|
|
2126
|
+
this.resolving.set(false);
|
|
2127
|
+
this.unresolved.set(false);
|
|
2128
|
+
this.query.set('');
|
|
2129
|
+
this.term.set('');
|
|
2130
|
+
this.results.set([]);
|
|
2131
|
+
this.searched.set(false);
|
|
2132
|
+
this.activeIndex.set(-1);
|
|
2133
|
+
this.open.set(false);
|
|
2134
|
+
this.cleared.emit();
|
|
2135
|
+
}
|
|
2136
|
+
onKeydown(event) {
|
|
2137
|
+
const results = this.results();
|
|
2138
|
+
switch (event.key) {
|
|
2139
|
+
case 'ArrowDown':
|
|
2140
|
+
event.preventDefault();
|
|
2141
|
+
this.open.set(true);
|
|
2142
|
+
this.activeIndex.set(Math.min(this.activeIndex() + 1, results.length - 1));
|
|
2143
|
+
break;
|
|
2144
|
+
case 'ArrowUp':
|
|
2145
|
+
event.preventDefault();
|
|
2146
|
+
this.activeIndex.set(Math.max(this.activeIndex() - 1, 0));
|
|
2147
|
+
break;
|
|
2148
|
+
case 'Enter': {
|
|
2149
|
+
const result = results[this.activeIndex()];
|
|
2150
|
+
if (result) {
|
|
2151
|
+
// Pole bywa w formularzu aplikacji — Enter nie ma go wysyłać.
|
|
2152
|
+
event.preventDefault();
|
|
2153
|
+
this.pick(result);
|
|
2154
|
+
}
|
|
2155
|
+
break;
|
|
2156
|
+
}
|
|
2157
|
+
case 'Escape':
|
|
2158
|
+
// Pierwszy Esc zamyka listę, drugi czyści pole — jak w polu wyszukiwania przeglądarki.
|
|
2159
|
+
if (this.open())
|
|
2160
|
+
this.open.set(false);
|
|
2161
|
+
else if (this.query())
|
|
2162
|
+
this.clear();
|
|
2163
|
+
break;
|
|
2164
|
+
default:
|
|
2165
|
+
break;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
static { this.ɵfac = function KptMapSearch_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapSearch)(); }; }
|
|
2169
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMapSearch, selectors: [["kpt-map-search"]], hostAttrs: [1, "kpt-map-search"], inputs: { placeholder: [1, "placeholder"], minLength: [1, "minLength"], debounce: [1, "debounce"], limit: [1, "limit"], countryCodes: [1, "countryCodes"], viewbox: [1, "viewbox"], disabled: [1, "disabled"], geocoder: [1, "geocoder"] }, outputs: { selected: "selected", cleared: "cleared" }, decls: 8, vars: 12, consts: [[1, "kpt-map-search__field"], ["aria-hidden", "true", 1, "kpt-map-search__icon"], ["name", "search"], ["type", "text", "autocomplete", "off", "autocapitalize", "off", "spellcheck", "false", "role", "combobox", "aria-autocomplete", "list", 1, "kpt-map-search__input", 3, "input", "focus", "blur", "keydown", "id", "value", "placeholder", "disabled"], ["type", "button", 1, "kpt-map-search__clear", 3, "title"], [1, "kpt-map-search__panel", 3, "id"], ["aria-live", "polite", 1, "kpt-map-search__status"], ["type", "button", 1, "kpt-map-search__clear", 3, "click", "title"], ["name", "x"], [1, "kpt-map-search__note"], ["role", "listbox", 1, "kpt-map-search__list"], ["role", "option", 1, "kpt-map-search__option", 3, "id", "is-active"], ["role", "option", 1, "kpt-map-search__option", 3, "mousedown", "mouseenter", "id"], ["aria-hidden", "true", 1, "kpt-map-search__pin"], ["name", "map-pin"], [1, "kpt-map-search__label"]], template: function KptMapSearch_Template(rf, ctx) { if (rf & 1) {
|
|
2170
|
+
i0.ɵɵelementStart(0, "div", 0)(1, "span", 1);
|
|
2171
|
+
i0.ɵɵelement(2, "kpt-icon", 2);
|
|
2172
|
+
i0.ɵɵelementEnd();
|
|
2173
|
+
i0.ɵɵelementStart(3, "input", 3);
|
|
2174
|
+
i0.ɵɵlistener("input", function KptMapSearch_Template_input_input_3_listener($event) { return ctx.onInput($event); })("focus", function KptMapSearch_Template_input_focus_3_listener() { return ctx.onFocus(); })("blur", function KptMapSearch_Template_input_blur_3_listener() { return ctx.onBlur(); })("keydown", function KptMapSearch_Template_input_keydown_3_listener($event) { return ctx.onKeydown($event); });
|
|
2175
|
+
i0.ɵɵelementEnd();
|
|
2176
|
+
i0.ɵɵconditionalCreate(4, KptMapSearch_Conditional_4_Template, 2, 2, "button", 4);
|
|
2177
|
+
i0.ɵɵelementEnd();
|
|
2178
|
+
i0.ɵɵconditionalCreate(5, KptMapSearch_Conditional_5_Template, 5, 2, "div", 5);
|
|
2179
|
+
i0.ɵɵelementStart(6, "p", 6);
|
|
2180
|
+
i0.ɵɵtext(7);
|
|
2181
|
+
i0.ɵɵelementEnd();
|
|
2182
|
+
} if (rf & 2) {
|
|
2183
|
+
i0.ɵɵadvance(3);
|
|
2184
|
+
i0.ɵɵproperty("id", ctx.uid)("value", ctx.query())("placeholder", ctx.placeholderText())("disabled", ctx.disabled() || !ctx.canSearch());
|
|
2185
|
+
i0.ɵɵattribute("aria-label", ctx.i18n.t("map.search.label"))("aria-expanded", ctx.hasPanel())("aria-busy", ctx.busy() ? "true" : null)("aria-controls", ctx.hasPanel() ? ctx.uid + "-panel" : null)("aria-activedescendant", ctx.activeIndex() >= 0 ? ctx.optionId(ctx.activeIndex()) : null);
|
|
2186
|
+
i0.ɵɵadvance();
|
|
2187
|
+
i0.ɵɵconditional(ctx.query() ? 4 : -1);
|
|
2188
|
+
i0.ɵɵadvance();
|
|
2189
|
+
i0.ɵɵconditional(ctx.hasPanel() ? 5 : -1);
|
|
2190
|
+
i0.ɵɵadvance(2);
|
|
2191
|
+
i0.ɵɵtextInterpolate(ctx.status());
|
|
2192
|
+
} }, dependencies: [KptIcon], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-search{position:relative;display:block;width:var(--%NS%kpt-map-search-width, 20rem);max-width:100%;font-size:var(--%NS%kpt-font-size-sm)}.kpt-map-search__field{display:flex;gap:var(--%NS%kpt-space-1);align-items:center;padding-inline:var(--%NS%kpt-space-2);background:var(--%NS%kpt-color-surface-raised);border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-md);box-shadow:var(--%NS%kpt-elevation-1)}.kpt-map-search__field:focus-within{border-color:var(--%NS%kpt-color-border-strong);outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}.kpt-map-search__icon{display:inline-flex;flex:none;color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-search__input{min-width:0;flex:1 1 auto;padding:var(--%NS%kpt-space-2) 0;font:inherit;color:var(--%NS%kpt-color-on-surface);background:transparent;border:none}.kpt-map-search__input::placeholder{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-search__input:focus{outline:none}.kpt-map-search__input:disabled{color:var(--%NS%kpt-color-on-surface-muted);cursor:not-allowed}.kpt-map-search__clear{display:inline-flex;flex:none;align-items:center;justify-content:center;padding:var(--%NS%kpt-space-1);color:var(--%NS%kpt-color-on-surface-muted);cursor:pointer;background:transparent;border:none;border-radius:var(--%NS%kpt-radius-full)}.kpt-map-search__clear:hover{color:var(--%NS%kpt-color-on-surface);background:var(--%NS%kpt-color-surface-hover)}.kpt-map-search__clear:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}.kpt-map-search__panel{position:absolute;top:calc(100% + var(--%NS%kpt-space-1));right:0;left:0;z-index:1;max-height:var(--%NS%kpt-map-search-panel-max-height, 18rem);overflow-y:auto;background:var(--%NS%kpt-color-surface-raised);border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-md);box-shadow:var(--%NS%kpt-elevation-3)}.kpt-map-search__list{margin:0;padding:var(--%NS%kpt-space-1);list-style:none}.kpt-map-search__option{display:flex;gap:var(--%NS%kpt-space-2);align-items:start;padding:var(--%NS%kpt-space-2);color:var(--%NS%kpt-color-on-surface);cursor:pointer;border-radius:var(--%NS%kpt-radius-sm)}.kpt-map-search__option.is-active{background:var(--%NS%kpt-color-surface-hover)}.kpt-map-search__pin{display:inline-flex;flex:none;margin-top:.125rem;color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-search__label{overflow-wrap:anywhere}.kpt-map-search__note{margin:0;padding:var(--%NS%kpt-space-3);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map-search__status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"], encapsulation: 2 }); }
|
|
2193
|
+
}
|
|
2194
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapSearch, [{
|
|
2195
|
+
type: Component,
|
|
2196
|
+
args: [{ selector: 'kpt-map-search', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [KptIcon], host: { class: 'kpt-map-search' }, template: "<div class=\"kpt-map-search__field\">\n <span class=\"kpt-map-search__icon\" aria-hidden=\"true\">\n <kpt-icon name=\"search\" />\n </span>\n\n <input\n class=\"kpt-map-search__input\"\n type=\"text\"\n autocomplete=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [id]=\"uid\"\n [value]=\"query()\"\n [placeholder]=\"placeholderText()\"\n [disabled]=\"disabled() || !canSearch()\"\n [attr.aria-label]=\"i18n.t('map.search.label')\"\n [attr.aria-expanded]=\"hasPanel()\"\n [attr.aria-busy]=\"busy() ? 'true' : null\"\n [attr.aria-controls]=\"hasPanel() ? uid + '-panel' : null\"\n [attr.aria-activedescendant]=\"activeIndex() >= 0 ? optionId(activeIndex()) : null\"\n (input)=\"onInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n (keydown)=\"onKeydown($event)\"\n />\n\n @if (query()) {\n <button\n class=\"kpt-map-search__clear\"\n type=\"button\"\n [attr.aria-label]=\"i18n.t('map.search.clear')\"\n [title]=\"i18n.t('map.search.clear')\"\n (click)=\"clear()\"\n >\n <!-- `kpt-icon` sam ustawia sobie aria-hidden; nazw\u0119 niesie aria-label przycisku. -->\n <kpt-icon name=\"x\" />\n </button>\n }\n</div>\n\n@if (hasPanel()) {\n <div class=\"kpt-map-search__panel\" [id]=\"uid + '-panel'\">\n @if (tooShort()) {\n <p class=\"kpt-map-search__note\">{{ i18n.t('map.search.minLength', { count: minLength() }) }}</p>\n } @else if (busy()) {\n <!-- Ta sama nota dla listy i dla doci\u0105gania wsp\u00F3\u0142rz\u0119dnych wybranej pozycji. -->\n <p class=\"kpt-map-search__note\">{{ i18n.t('map.search.searching') }}</p>\n } @else if (results().length) {\n <!-- Panel stoi w DOM obok pola, wi\u0119c aktywn\u0105 pozycj\u0119 wskazuje `aria-activedescendant`. -->\n <ul class=\"kpt-map-search__list\" role=\"listbox\" [attr.aria-label]=\"i18n.t('map.search.results')\">\n @for (result of results(); track result.id; let i = $index) {\n <li\n class=\"kpt-map-search__option\"\n role=\"option\"\n [id]=\"optionId(i)\"\n [class.is-active]=\"i === activeIndex()\"\n [attr.aria-selected]=\"i === activeIndex()\"\n (mousedown)=\"$event.preventDefault(); pick(result)\"\n (mouseenter)=\"activeIndex.set(i)\"\n >\n <span class=\"kpt-map-search__pin\" aria-hidden=\"true\">\n <kpt-icon name=\"map-pin\" />\n </span>\n <span class=\"kpt-map-search__label\">{{ result.label }}</span>\n </li>\n }\n </ul>\n } @else {\n <p class=\"kpt-map-search__note\">{{ i18n.t('map.search.noResults') }}</p>\n }\n </div>\n}\n\n<!-- Stan wyszukiwania dla czytnika \u2014 wzrokowo niesie go panel. -->\n<p class=\"kpt-map-search__status\" aria-live=\"polite\">{{ status() }}</p>\n", styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map-search{position:relative;display:block;width:var(--kpt-map-search-width, 20rem);max-width:100%;font-size:var(--kpt-font-size-sm)}.kpt-map-search__field{display:flex;gap:var(--kpt-space-1);align-items:center;padding-inline: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-1)}.kpt-map-search__field:focus-within{border-color:var(--kpt-color-border-strong);outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}.kpt-map-search__icon{display:inline-flex;flex:none;color:var(--kpt-color-on-surface-muted)}.kpt-map-search__input{min-width:0;flex:1 1 auto;padding:var(--kpt-space-2) 0;font:inherit;color:var(--kpt-color-on-surface);background:transparent;border:none}.kpt-map-search__input::placeholder{color:var(--kpt-color-on-surface-muted)}.kpt-map-search__input:focus{outline:none}.kpt-map-search__input:disabled{color:var(--kpt-color-on-surface-muted);cursor:not-allowed}.kpt-map-search__clear{display:inline-flex;flex:none;align-items:center;justify-content:center;padding:var(--kpt-space-1);color:var(--kpt-color-on-surface-muted);cursor:pointer;background:transparent;border:none;border-radius:var(--kpt-radius-full)}.kpt-map-search__clear:hover{color:var(--kpt-color-on-surface);background:var(--kpt-color-surface-hover)}.kpt-map-search__clear:focus-visible{outline:var(--kpt-border-width-medium) solid var(--kpt-color-focus-ring);outline-offset:1px}.kpt-map-search__panel{position:absolute;top:calc(100% + var(--kpt-space-1));right:0;left:0;z-index:1;max-height:var(--kpt-map-search-panel-max-height, 18rem);overflow-y:auto;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-search__list{margin:0;padding:var(--kpt-space-1);list-style:none}.kpt-map-search__option{display:flex;gap:var(--kpt-space-2);align-items:start;padding:var(--kpt-space-2);color:var(--kpt-color-on-surface);cursor:pointer;border-radius:var(--kpt-radius-sm)}.kpt-map-search__option.is-active{background:var(--kpt-color-surface-hover)}.kpt-map-search__pin{display:inline-flex;flex:none;margin-top:.125rem;color:var(--kpt-color-on-surface-muted)}.kpt-map-search__label{overflow-wrap:anywhere}.kpt-map-search__note{margin:0;padding:var(--kpt-space-3);color:var(--kpt-color-on-surface-muted)}.kpt-map-search__status{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"] }]
|
|
2197
|
+
}], () => [], { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minLength", required: false }] }], debounce: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounce", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], countryCodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "countryCodes", required: false }] }], viewbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewbox", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], geocoder: [{ type: i0.Input, args: [{ isSignal: true, alias: "geocoder", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], cleared: [{ type: i0.Output, args: ["cleared"] }] }); })();
|
|
2198
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapSearch, { className: "KptMapSearch", filePath: "lib/map-search.component.ts", lineNumber: 49 }); })();
|
|
2199
|
+
|
|
2200
|
+
/**
|
|
2201
|
+
* Szablon popupu szczegółów. Bez atrybutu `category` służy za wariant zapasowy
|
|
2202
|
+
* dla kategorii, które własnego szablonu nie mają.
|
|
2203
|
+
*
|
|
2204
|
+
* Typ `data` domyślnie jest luźny (jak `let-row` w tabeli CDK), bo Angular nie potrafi
|
|
2205
|
+
* przenieść generyka z `kpt-map` na dyrektywę osadzoną w jego treści. Kto chce pełnego
|
|
2206
|
+
* wnioskowania, wiąże `[kptMapPopupTypeFor]` z tą samą tablicą, którą karmi mapę —
|
|
2207
|
+
* wtedy `data` dostaje konkretny typ.
|
|
2208
|
+
*
|
|
2209
|
+
* @example
|
|
2210
|
+
* <ng-template kptMapPopup category="closure" let-loc let-data="data" let-dist="distance">
|
|
2211
|
+
* <strong>{{ loc.title }}</strong>
|
|
2212
|
+
* <p>{{ data.address }}</p>
|
|
2213
|
+
* </ng-template>
|
|
2214
|
+
*
|
|
2215
|
+
* @example
|
|
2216
|
+
* // z wnioskowaniem typu
|
|
2217
|
+
* <ng-template kptMapPopup [kptMapPopupTypeFor]="locations()" let-data="data">
|
|
2218
|
+
* <p>{{ data.address }}</p>
|
|
2219
|
+
* </ng-template>
|
|
2220
|
+
*/
|
|
2221
|
+
class KptMapPopupTemplate {
|
|
2222
|
+
constructor() {
|
|
2223
|
+
/** Kategoria, do której szablon się stosuje; pusta wartość = szablon zapasowy. */
|
|
2224
|
+
this.category = input('', { ...(ngDevMode ? { debugName: "category" } : /* istanbul ignore next */ {}), alias: 'category' });
|
|
2225
|
+
/**
|
|
2226
|
+
* Nośnik typu — w runtime nieużywany. Jedyne zadanie: pozwolić Angularowi wywnioskować
|
|
2227
|
+
* `T` z tablicy lokalizacji, tak jak `ngFor` wnioskuje typ elementu z bindowanej kolekcji.
|
|
2228
|
+
*/
|
|
2229
|
+
this.typeFor = input(undefined, { ...(ngDevMode ? { debugName: "typeFor" } : /* istanbul ignore next */ {}), alias: 'kptMapPopupTypeFor' });
|
|
2230
|
+
this.templateRef = inject(TemplateRef);
|
|
2231
|
+
}
|
|
2232
|
+
/** Zawęża typ kontekstu w szablonie (`strictTemplates`). */
|
|
2233
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
2234
|
+
return true;
|
|
2235
|
+
}
|
|
2236
|
+
static { this.ɵfac = function KptMapPopupTemplate_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapPopupTemplate)(); }; }
|
|
2237
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: KptMapPopupTemplate, selectors: [["ng-template", "kptMapPopup", ""]], inputs: { category: [1, "category"], typeFor: [1, "kptMapPopupTypeFor", "typeFor"] } }); }
|
|
2238
|
+
}
|
|
2239
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapPopupTemplate, [{
|
|
2240
|
+
type: Directive,
|
|
2241
|
+
args: [{ selector: 'ng-template[kptMapPopup]' }]
|
|
2242
|
+
}], null, { category: [{ type: i0.Input, args: [{ isSignal: true, alias: "category", required: false }] }], typeFor: [{ type: i0.Input, args: [{ isSignal: true, alias: "kptMapPopupTypeFor", required: false }] }] }); })();
|
|
2243
|
+
/**
|
|
2244
|
+
* Własny formularz „dodaj lokalizację". Jego obecność wyłącza formularz budowany
|
|
2245
|
+
* ze schemy kategorii.
|
|
2246
|
+
*
|
|
2247
|
+
* @example
|
|
2248
|
+
* <ng-template kptMapCreateForm let-api>
|
|
2249
|
+
* <button (click)="api.submit({ title: 'Nowy punkt' })">Zapisz</button>
|
|
2250
|
+
* </ng-template>
|
|
2251
|
+
*/
|
|
2252
|
+
class KptMapCreateFormTemplate {
|
|
2253
|
+
constructor() {
|
|
2254
|
+
this.templateRef = inject(TemplateRef);
|
|
2255
|
+
}
|
|
2256
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
static { this.ɵfac = function KptMapCreateFormTemplate_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapCreateFormTemplate)(); }; }
|
|
2260
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: KptMapCreateFormTemplate, selectors: [["ng-template", "kptMapCreateForm", ""]] }); }
|
|
2261
|
+
}
|
|
2262
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapCreateFormTemplate, [{
|
|
2263
|
+
type: Directive,
|
|
2264
|
+
args: [{ selector: 'ng-template[kptMapCreateForm]' }]
|
|
2265
|
+
}], null, null); })();
|
|
2266
|
+
|
|
2267
|
+
const KPT_MAP_GEOLOCATION = new InjectionToken('KPT_MAP_GEOLOCATION');
|
|
2268
|
+
/** Błąd geolokalizacji rozpoznany po kodzie — rzucany strumieniem, nie wyjątkiem. */
|
|
2269
|
+
class KptMapGeolocationFailure extends Error {
|
|
2270
|
+
constructor(detail) {
|
|
2271
|
+
super(detail.message);
|
|
2272
|
+
this.detail = detail;
|
|
2273
|
+
this.name = 'KptMapGeolocationFailure';
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
const DEFAULT_OPTIONS = {
|
|
2277
|
+
enableHighAccuracy: true,
|
|
2278
|
+
// Pozycja starsza niż pół minuty jest już bezużyteczna dla „gdzie jestem".
|
|
2279
|
+
maximumAge: 30_000,
|
|
2280
|
+
timeout: 15_000,
|
|
2281
|
+
};
|
|
2282
|
+
/**
|
|
2283
|
+
* Domyślna implementacja na `navigator.geolocation.watchPosition`.
|
|
2284
|
+
*
|
|
2285
|
+
* Uwaga: Geolocation API działa wyłącznie w bezpiecznym kontekście (HTTPS albo
|
|
2286
|
+
* `localhost`). Na `http://` z adresem IP przeglądarka odmówi — to nie jest błąd mapy.
|
|
2287
|
+
*/
|
|
2288
|
+
class KptBrowserGeolocation {
|
|
2289
|
+
watch(options) {
|
|
2290
|
+
// SSR i przeglądarki bez API: strumień kończy się jednym `null`, bez błędu.
|
|
2291
|
+
if (typeof navigator === 'undefined' || !navigator.geolocation) {
|
|
2292
|
+
return of(null);
|
|
2293
|
+
}
|
|
2294
|
+
return new Observable((subscriber) => {
|
|
2295
|
+
const id = navigator.geolocation.watchPosition((position) => subscriber.next({
|
|
2296
|
+
at: { lat: position.coords.latitude, lng: position.coords.longitude },
|
|
2297
|
+
accuracy: position.coords.accuracy,
|
|
2298
|
+
timestamp: position.timestamp,
|
|
2299
|
+
}), (error) => subscriber.error(new KptMapGeolocationFailure(describeError(error))), { ...DEFAULT_OPTIONS, ...options });
|
|
2300
|
+
return () => navigator.geolocation.clearWatch(id);
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
function describeError(error) {
|
|
2305
|
+
switch (error.code) {
|
|
2306
|
+
case error.PERMISSION_DENIED:
|
|
2307
|
+
return { kind: 'denied', message: error.message };
|
|
2308
|
+
case error.POSITION_UNAVAILABLE:
|
|
2309
|
+
return { kind: 'unavailable', message: error.message };
|
|
2310
|
+
case error.TIMEOUT:
|
|
2311
|
+
return { kind: 'timeout', message: error.message };
|
|
2312
|
+
default:
|
|
2313
|
+
return { kind: 'unsupported', message: error.message };
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
/** Stała pozycja — Storybook, testy, demo bez pytania o zgodę. */
|
|
2317
|
+
class KptMapGeolocationStub {
|
|
2318
|
+
constructor(at, accuracy = 50) {
|
|
2319
|
+
this.at = at;
|
|
2320
|
+
this.accuracy = accuracy;
|
|
2321
|
+
}
|
|
2322
|
+
watch() {
|
|
2323
|
+
return of({ at: this.at, accuracy: this.accuracy, timestamp: Date.now() });
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Podstawia stałą pozycję zamiast prawdziwej geolokalizacji.
|
|
2328
|
+
*
|
|
2329
|
+
* @example
|
|
2330
|
+
* providers: [provideKptMapGeolocationStub({ lat: 52.23, lng: 21.01 })]
|
|
2331
|
+
*/
|
|
2332
|
+
function provideKptMapGeolocationStub(at, accuracy = 50) {
|
|
2333
|
+
return { provide: KPT_MAP_GEOLOCATION, useValue: new KptMapGeolocationStub(at, accuracy) };
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
const LOAD_ERROR = 'kpt-map: nie udało się załadować Google Maps JavaScript API. ' +
|
|
2337
|
+
'Sprawdź klucz (`apiKey`), czy Maps JavaScript API jest włączone w projekcie ' +
|
|
2338
|
+
'i czy restrykcja domenowa klucza obejmuje ten adres.';
|
|
2339
|
+
/** Nazwa globalnego callbacku, którego wymaga `loading=async`. */
|
|
2340
|
+
const CALLBACK = '__kptGoogleMapsReady';
|
|
2341
|
+
let modulePromise$1 = null;
|
|
2342
|
+
/**
|
|
2343
|
+
* Ładuje Google Maps JavaScript API raz na aplikację. Gdy `google.maps` już istnieje — bo
|
|
2344
|
+
* aplikacja wstawiła skrypt sama albo stoi tu druga mapa — korzystamy z tego, co jest: drugie
|
|
2345
|
+
* wczytanie Google i tak odrzuca, wypisując ostrzeżenie do konsoli.
|
|
2346
|
+
*
|
|
2347
|
+
* Publiczne, bo silnik nie jest jedynym, kto tego API potrzebuje. Geokoder Google **musi**
|
|
2348
|
+
* iść przez SDK, a nie przez REST: web service `/maps/api/geocode/json` odrzuca klucze
|
|
2349
|
+
* z restrykcją `Referer` („API keys with referer restrictions cannot be used with this API"),
|
|
2350
|
+
* a to jedyna restrykcja, jaką da się sensownie nałożyć na klucz leżący w bundlu przeglądarki.
|
|
2351
|
+
* Wywołania z załadowanego SDK autoryzują się tak samo jak sama mapa.
|
|
2352
|
+
*
|
|
2353
|
+
* Adaptera geokodera biblioteka nadal nie dostarcza — tylko drogę do API, którą i tak
|
|
2354
|
+
* otwiera silnikowi. Powody stoją przy `provideKptGoogleMap` i w `llms.txt`.
|
|
2355
|
+
*/
|
|
2356
|
+
async function loadGoogleMapsApi(options) {
|
|
2357
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
|
2358
|
+
throw new Error('kpt-map: silnik Google Maps wymaga przeglądarki.');
|
|
2359
|
+
}
|
|
2360
|
+
const existing = window['google']?.maps;
|
|
2361
|
+
if (existing?.importLibrary)
|
|
2362
|
+
return existing;
|
|
2363
|
+
modulePromise$1 ??= new Promise((resolve, reject) => {
|
|
2364
|
+
const params = new URLSearchParams({
|
|
2365
|
+
key: options.apiKey,
|
|
2366
|
+
v: options.version ?? 'weekly',
|
|
2367
|
+
loading: 'async',
|
|
2368
|
+
callback: CALLBACK,
|
|
2369
|
+
});
|
|
2370
|
+
if (options.language)
|
|
2371
|
+
params.set('language', options.language);
|
|
2372
|
+
if (options.region)
|
|
2373
|
+
params.set('region', options.region);
|
|
2374
|
+
window[CALLBACK] = () => {
|
|
2375
|
+
delete window[CALLBACK];
|
|
2376
|
+
resolve(window['google'].maps);
|
|
2377
|
+
};
|
|
2378
|
+
const script = document.createElement('script');
|
|
2379
|
+
script.src = `https://maps.googleapis.com/maps/api/js?${params}`;
|
|
2380
|
+
script.async = true;
|
|
2381
|
+
script.onerror = () => {
|
|
2382
|
+
modulePromise$1 = null; // pozwól spróbować ponownie po poprawieniu klucza
|
|
2383
|
+
reject(new Error(LOAD_ERROR));
|
|
2384
|
+
};
|
|
2385
|
+
document.head.append(script);
|
|
2386
|
+
});
|
|
2387
|
+
return modulePromise$1;
|
|
2388
|
+
}
|
|
2389
|
+
/** Fabryka silnika — każda mapa dostaje własną instancję. */
|
|
2390
|
+
function createGoogleEngine(options) {
|
|
2391
|
+
return new GoogleEngine(options);
|
|
2392
|
+
}
|
|
2393
|
+
class GoogleEngine {
|
|
2394
|
+
constructor(options) {
|
|
2395
|
+
this.options = options;
|
|
2396
|
+
this.kind = 'google';
|
|
2397
|
+
this.maps = null;
|
|
2398
|
+
this.markerLib = null;
|
|
2399
|
+
this.map = null;
|
|
2400
|
+
this.host = null;
|
|
2401
|
+
this.userMarker = null;
|
|
2402
|
+
/** Krąg dokładności jest własnym `<div>`, nie `google.maps.Circle` — patrz `setUserPosition`. */
|
|
2403
|
+
this.accuracyEl = null;
|
|
2404
|
+
this.accuracyMeters = 0;
|
|
2405
|
+
this.accuracyAt = null;
|
|
2406
|
+
/** Znaczniki po `id` wraz z ich sprzątaniem — bez tego każdy przelicz przerysowywałby mapę. */
|
|
2407
|
+
this.markers = new Map();
|
|
2408
|
+
this.listeners = [];
|
|
2409
|
+
this.handlers = {};
|
|
2410
|
+
}
|
|
2411
|
+
async init(host, opts) {
|
|
2412
|
+
const maps = (this.maps = await loadGoogleMapsApi(this.options));
|
|
2413
|
+
// Pod nazwą `Map` kryje się konstruktor Google, nie wbudowana kolekcja — stąd alias.
|
|
2414
|
+
const [{ Map: GoogleMap }, markerLib] = await Promise.all([
|
|
2415
|
+
maps.importLibrary('maps'),
|
|
2416
|
+
maps.importLibrary('marker'),
|
|
2417
|
+
]);
|
|
2418
|
+
this.markerLib = markerLib;
|
|
2419
|
+
this.host = host;
|
|
2420
|
+
this.map = new GoogleMap(host, {
|
|
2421
|
+
center: { lat: opts.center.lat, lng: opts.center.lng },
|
|
2422
|
+
zoom: opts.zoom,
|
|
2423
|
+
minZoom: opts.minZoom ?? this.options.minZoom ?? 2,
|
|
2424
|
+
maxZoom: opts.maxZoom ?? this.options.maxZoom ?? 21,
|
|
2425
|
+
mapId: this.options.mapId,
|
|
2426
|
+
// Kontrolki rysuje warstwa Angulara — te same przyciski w obu silnikach.
|
|
2427
|
+
disableDefaultUI: true,
|
|
2428
|
+
// POI Google przechwytywałyby kliknięcia przeznaczone dla mapy (menu „dodaj lokalizację").
|
|
2429
|
+
clickableIcons: false,
|
|
2430
|
+
scrollwheel: this.options.scrollWheelZoom ?? true,
|
|
2431
|
+
restriction: opts.maxBounds
|
|
2432
|
+
? { latLngBounds: this.toLatLngBounds(opts.maxBounds), strictBounds: true }
|
|
2433
|
+
: undefined,
|
|
2434
|
+
});
|
|
2435
|
+
// `opts.tileUrl` i `opts.attribution` są tu bez znaczenia: kafle i atrybucję daje Google,
|
|
2436
|
+
// a jej zasłonięcie albo usunięcie łamie warunki korzystania z usługi.
|
|
2437
|
+
this.on_(this.map, 'bounds_changed', () => this.emit('move'));
|
|
2438
|
+
this.on_(this.map, 'idle', () => this.emit('moveend'));
|
|
2439
|
+
this.on_(this.map, 'zoom_changed', () => {
|
|
2440
|
+
// Krąg dokładności ma stały promień w metrach, więc przy każdym zoomie zmienia piksele.
|
|
2441
|
+
this.resizeAccuracy();
|
|
2442
|
+
this.emit('zoomend');
|
|
2443
|
+
});
|
|
2444
|
+
this.on_(this.map, 'click', (e) => this.emit('click', this.toPointerEvent(e)));
|
|
2445
|
+
this.on_(this.map, 'contextmenu', (e) => {
|
|
2446
|
+
// Menu przeglądarki zasłoniłoby własne „Dodaj lokalizację".
|
|
2447
|
+
e.domEvent?.preventDefault();
|
|
2448
|
+
this.emit('contextmenu', this.toPointerEvent(e));
|
|
2449
|
+
});
|
|
2450
|
+
// Pierwszy `idle` znaczy, że mapa zna już swój rozmiar i zasięg.
|
|
2451
|
+
await new Promise((resolve) => {
|
|
2452
|
+
this.maps.event.addListenerOnce(this.map, 'idle', () => resolve());
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
destroy() {
|
|
2456
|
+
for (const { marker, dispose } of this.markers.values()) {
|
|
2457
|
+
dispose();
|
|
2458
|
+
marker.map = null;
|
|
2459
|
+
}
|
|
2460
|
+
this.markers.clear();
|
|
2461
|
+
if (this.userMarker)
|
|
2462
|
+
this.userMarker.map = null;
|
|
2463
|
+
this.userMarker = null;
|
|
2464
|
+
this.accuracyEl = null;
|
|
2465
|
+
this.accuracyAt = null;
|
|
2466
|
+
for (const listener of this.listeners)
|
|
2467
|
+
listener.remove?.();
|
|
2468
|
+
this.listeners.length = 0;
|
|
2469
|
+
// Google nie ma odpowiednika `map.remove()` — kontener czyścimy sami.
|
|
2470
|
+
if (this.map)
|
|
2471
|
+
this.maps?.event.clearInstanceListeners(this.map);
|
|
2472
|
+
this.host?.replaceChildren();
|
|
2473
|
+
this.map = null;
|
|
2474
|
+
this.host = null;
|
|
2475
|
+
}
|
|
2476
|
+
setView(center, zoom, animate = true) {
|
|
2477
|
+
if (!this.map)
|
|
2478
|
+
return;
|
|
2479
|
+
const at = { lat: center.lat, lng: center.lng };
|
|
2480
|
+
if (animate) {
|
|
2481
|
+
this.map.panTo(at);
|
|
2482
|
+
this.map.setZoom(zoom);
|
|
2483
|
+
}
|
|
2484
|
+
else {
|
|
2485
|
+
this.map.moveCamera({ center: at, zoom });
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
fitBounds(bounds, paddingPx = 0) {
|
|
2489
|
+
this.map?.fitBounds(this.toLatLngBounds(bounds), paddingPx);
|
|
2490
|
+
}
|
|
2491
|
+
panBy(offset) {
|
|
2492
|
+
this.map?.panBy(offset.x, offset.y);
|
|
2493
|
+
}
|
|
2494
|
+
getCenter() {
|
|
2495
|
+
const c = this.map.getCenter();
|
|
2496
|
+
return { lat: c.lat(), lng: c.lng() };
|
|
2497
|
+
}
|
|
2498
|
+
getZoom() {
|
|
2499
|
+
return this.map.getZoom();
|
|
2500
|
+
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Zasięg widoku. `getBounds()` bywa `undefined`, zanim mapa policzy pierwszy układ —
|
|
2503
|
+
* liczymy go wtedy z rdzenia, zamiast oddawać `null` przez kontrakt, który go nie przewiduje.
|
|
2504
|
+
*/
|
|
2505
|
+
getBounds() {
|
|
2506
|
+
const b = this.map?.getBounds();
|
|
2507
|
+
if (b) {
|
|
2508
|
+
const ne = b.getNorthEast();
|
|
2509
|
+
const sw = b.getSouthWest();
|
|
2510
|
+
return { south: sw.lat(), west: sw.lng(), north: ne.lat(), east: ne.lng() };
|
|
2511
|
+
}
|
|
2512
|
+
const size = this.getSize();
|
|
2513
|
+
const nw = this.unproject({ x: 0, y: 0 });
|
|
2514
|
+
const se = this.unproject({ x: size.width, y: size.height });
|
|
2515
|
+
return { south: se.lat, west: nw.lng, north: nw.lat, east: se.lng };
|
|
2516
|
+
}
|
|
2517
|
+
getSize() {
|
|
2518
|
+
const rect = this.host?.getBoundingClientRect();
|
|
2519
|
+
return { width: rect?.width ?? 0, height: rect?.height ?? 0 };
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Współrzędne → piksele kontenera.
|
|
2523
|
+
*
|
|
2524
|
+
* Świadomie **nie** przez `map.getProjection()`: ten zwraca `null` do pierwszego
|
|
2525
|
+
* `projection_changed` i tak czy owak wymaga ręcznego przeliczenia na piksele kontenera.
|
|
2526
|
+
* Rdzeń (`core/geo.ts`) ma dokładnie tę matematykę — tę samą, na której stoi grupowanie
|
|
2527
|
+
* i adapter Leafleta — więc popup ląduje piksel w piksel tak samo w obu silnikach.
|
|
2528
|
+
* Działa też dla zoomu ułamkowego, którego Google używa przy gestach.
|
|
2529
|
+
*/
|
|
2530
|
+
project(at) {
|
|
2531
|
+
const zoom = this.getZoom();
|
|
2532
|
+
const size = this.getSize();
|
|
2533
|
+
const origin = project(this.getCenter(), zoom);
|
|
2534
|
+
const point = project(at, zoom);
|
|
2535
|
+
return {
|
|
2536
|
+
x: point.x - origin.x + size.width / 2,
|
|
2537
|
+
y: point.y - origin.y + size.height / 2,
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
unproject(point) {
|
|
2541
|
+
const zoom = this.getZoom();
|
|
2542
|
+
const size = this.getSize();
|
|
2543
|
+
const origin = project(this.getCenter(), zoom);
|
|
2544
|
+
return unproject({ x: origin.x + point.x - size.width / 2, y: origin.y + point.y - size.height / 2 }, zoom);
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Wymienia zestaw znaczników różnicowo — znaczniki o niezmienionym `id` zostają na miejscu,
|
|
2548
|
+
* inaczej każdy przelicz gasiłby animacje i migotał.
|
|
2549
|
+
*/
|
|
2550
|
+
setMarkers(next) {
|
|
2551
|
+
if (!this.map || !this.markerLib)
|
|
2552
|
+
return;
|
|
2553
|
+
const { AdvancedMarkerElement } = this.markerLib;
|
|
2554
|
+
const seen = new Set();
|
|
2555
|
+
for (const m of next) {
|
|
2556
|
+
seen.add(m.id);
|
|
2557
|
+
const existing = this.markers.get(m.id);
|
|
2558
|
+
if (existing) {
|
|
2559
|
+
existing.marker.position = { lat: m.at.lat, lng: m.at.lng };
|
|
2560
|
+
if (m.zIndex !== undefined)
|
|
2561
|
+
existing.marker.zIndex = m.zIndex;
|
|
2562
|
+
this.fillMarker(existing.inner, m);
|
|
2563
|
+
continue;
|
|
2564
|
+
}
|
|
2565
|
+
const { anchor, inner } = this.buildMarkerContent(m);
|
|
2566
|
+
const marker = new AdvancedMarkerElement({
|
|
2567
|
+
map: this.map,
|
|
2568
|
+
position: { lat: m.at.lat, lng: m.at.lng },
|
|
2569
|
+
content: anchor,
|
|
2570
|
+
zIndex: m.zIndex ?? 0,
|
|
2571
|
+
});
|
|
2572
|
+
/*
|
|
2573
|
+
* Kliknięcie łapiemy listenerem DOM na własnej treści, a nie zdarzeniem `gmp-click`
|
|
2574
|
+
* z `gmpClickable: true`. Powód jest ten sam, dla którego adapter Leafleta woła
|
|
2575
|
+
* `L.DomEvent.stopPropagation`: bez zatrzymania propagacji to samo kliknięcie doszłoby
|
|
2576
|
+
* do mapy i `onMapClick()` zamknąłby popup w tej samej klatce, w której go otwieramy.
|
|
2577
|
+
* Przy okazji odpada zależność od tego, jak `gmp-click` zachowuje się w danej wersji API.
|
|
2578
|
+
* Dostępność bez zmian — etykietę niesie `aria-label` w HTML-u ikony.
|
|
2579
|
+
*/
|
|
2580
|
+
const onClick = (event) => {
|
|
2581
|
+
event.stopPropagation();
|
|
2582
|
+
this.emit('markerclick', m.id, this.fromDomEvent(event, m.at));
|
|
2583
|
+
};
|
|
2584
|
+
inner.addEventListener('click', onClick);
|
|
2585
|
+
this.markers.set(m.id, {
|
|
2586
|
+
marker,
|
|
2587
|
+
inner,
|
|
2588
|
+
dispose: () => inner.removeEventListener('click', onClick),
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
for (const [id, entry] of this.markers) {
|
|
2592
|
+
if (seen.has(id))
|
|
2593
|
+
continue;
|
|
2594
|
+
entry.dispose();
|
|
2595
|
+
entry.marker.map = null;
|
|
2596
|
+
this.markers.delete(id);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
setUserPosition(position, html) {
|
|
2600
|
+
if (!this.map || !this.markerLib)
|
|
2601
|
+
return;
|
|
2602
|
+
const { AdvancedMarkerElement } = this.markerLib;
|
|
2603
|
+
if (!position) {
|
|
2604
|
+
if (this.userMarker)
|
|
2605
|
+
this.userMarker.map = null;
|
|
2606
|
+
this.userMarker = null;
|
|
2607
|
+
this.accuracyEl = null;
|
|
2608
|
+
this.accuracyAt = null;
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
const at = { lat: position.at.lat, lng: position.at.lng };
|
|
2612
|
+
this.accuracyMeters = position.accuracy;
|
|
2613
|
+
this.accuracyAt = position.at;
|
|
2614
|
+
if (this.userMarker) {
|
|
2615
|
+
this.userMarker.position = at;
|
|
2616
|
+
this.userMarker.content = this.buildUserContent(html);
|
|
2617
|
+
}
|
|
2618
|
+
else {
|
|
2619
|
+
this.userMarker = new AdvancedMarkerElement({
|
|
2620
|
+
map: this.map,
|
|
2621
|
+
position: at,
|
|
2622
|
+
content: this.buildUserContent(html),
|
|
2623
|
+
// Nad znacznikami lokalizacji — własne położenie ma być zawsze widoczne.
|
|
2624
|
+
zIndex: 1000,
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
this.resizeAccuracy();
|
|
2628
|
+
}
|
|
2629
|
+
/** Google dostarcza własne kafle i własną atrybucję — nie ma czego podmieniać. */
|
|
2630
|
+
setTileSource() {
|
|
2631
|
+
// celowo puste
|
|
2632
|
+
}
|
|
2633
|
+
setMaxBounds(bounds) {
|
|
2634
|
+
this.map?.setOptions({
|
|
2635
|
+
restriction: bounds ? { latLngBounds: this.toLatLngBounds(bounds), strictBounds: true } : null,
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
/** Google sam reaguje na zmianę rozmiaru kontenera — nie ma odpowiednika `invalidateSize`. */
|
|
2639
|
+
invalidateSize() {
|
|
2640
|
+
// celowo puste
|
|
2641
|
+
}
|
|
2642
|
+
on(event, handler) {
|
|
2643
|
+
(this.handlers[event] ??= []).push(handler);
|
|
2644
|
+
}
|
|
2645
|
+
// ── prywatne ────────────────────────────────────────────────────────────────
|
|
2646
|
+
emit(event, ...args) {
|
|
2647
|
+
for (const handler of this.handlers[event] ?? []) {
|
|
2648
|
+
handler(...args);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
/** Rejestruje listener Google i zapamiętuje go do sprzątania w `destroy()`. */
|
|
2652
|
+
on_(target, event, handler) {
|
|
2653
|
+
this.listeners.push(target.addListener(event, handler));
|
|
2654
|
+
}
|
|
2655
|
+
/**
|
|
2656
|
+
* Opakowanie znacznika. `AdvancedMarkerElement` kotwiczy treść **środkiem dolnej krawędzi**,
|
|
2657
|
+
* a kontrakt daje kotwicę w pikselach od lewego górnego rogu ikony. Element zewnętrzny ma
|
|
2658
|
+
* więc zerowe wymiary — jego „środek dolnej krawędzi" wypada dokładnie we współrzędnych —
|
|
2659
|
+
* a wewnętrzny przesuwa ikonę o żądaną kotwicę.
|
|
2660
|
+
*/
|
|
2661
|
+
buildMarkerContent(m) {
|
|
2662
|
+
const anchor = document.createElement('div');
|
|
2663
|
+
anchor.style.cssText = 'position:relative;width:0;height:0';
|
|
2664
|
+
const inner = document.createElement('div');
|
|
2665
|
+
inner.className = 'kpt-map__icon';
|
|
2666
|
+
inner.style.position = 'absolute';
|
|
2667
|
+
anchor.append(inner);
|
|
2668
|
+
this.fillMarker(inner, m);
|
|
2669
|
+
return { anchor, inner };
|
|
2670
|
+
}
|
|
2671
|
+
fillMarker(inner, m) {
|
|
2672
|
+
inner.style.left = `${-m.anchor.x}px`;
|
|
2673
|
+
inner.style.top = `${-m.anchor.y}px`;
|
|
2674
|
+
inner.style.width = `${m.size.width}px`;
|
|
2675
|
+
inner.style.height = `${m.size.height}px`;
|
|
2676
|
+
// HTML pochodzi z `marker-html.ts` — z rdzenia biblioteki, nie od użytkownika.
|
|
2677
|
+
inner.innerHTML = m.html;
|
|
2678
|
+
}
|
|
2679
|
+
/**
|
|
2680
|
+
* Znacznik użytkownika i krąg dokładności w jednym elemencie.
|
|
2681
|
+
*
|
|
2682
|
+
* Krąg jest `<div>`-em z klasą `kpt-map__accuracy`, a nie `google.maps.Circle`: Circle
|
|
2683
|
+
* przyjmuje kolory w opcjach, a kontrakt repo wymaga stylowania tokenami `var(--kpt-*)`.
|
|
2684
|
+
* Średnicę liczy `metersPerPixel()` z rdzenia, więc jest wspólna dla obu silników.
|
|
2685
|
+
*/
|
|
2686
|
+
buildUserContent(html) {
|
|
2687
|
+
const anchor = document.createElement('div');
|
|
2688
|
+
anchor.style.cssText = 'position:relative;width:0;height:0';
|
|
2689
|
+
const circle = document.createElement('div');
|
|
2690
|
+
circle.className = 'kpt-map__accuracy';
|
|
2691
|
+
circle.style.cssText = 'position:absolute;border-radius:50%;pointer-events:none';
|
|
2692
|
+
anchor.append(circle);
|
|
2693
|
+
this.accuracyEl = circle;
|
|
2694
|
+
const inner = document.createElement('div');
|
|
2695
|
+
inner.className = 'kpt-map__icon';
|
|
2696
|
+
inner.style.cssText = 'position:absolute;left:-12px;top:-12px;width:24px;height:24px';
|
|
2697
|
+
inner.innerHTML = html;
|
|
2698
|
+
anchor.append(inner);
|
|
2699
|
+
this.resizeAccuracy();
|
|
2700
|
+
return anchor;
|
|
2701
|
+
}
|
|
2702
|
+
resizeAccuracy() {
|
|
2703
|
+
const el = this.accuracyEl;
|
|
2704
|
+
const at = this.accuracyAt;
|
|
2705
|
+
if (!el || !at || !this.map)
|
|
2706
|
+
return;
|
|
2707
|
+
const diameter = (2 * this.accuracyMeters) / metersPerPixel(at.lat, this.getZoom());
|
|
2708
|
+
el.style.width = `${diameter}px`;
|
|
2709
|
+
el.style.height = `${diameter}px`;
|
|
2710
|
+
el.style.left = `${-diameter / 2}px`;
|
|
2711
|
+
el.style.top = `${-diameter / 2}px`;
|
|
2712
|
+
}
|
|
2713
|
+
toPointerEvent(e) {
|
|
2714
|
+
const at = { lat: e.latLng.lat(), lng: e.latLng.lng() };
|
|
2715
|
+
return { at, point: this.toContainerPoint(e.domEvent), originalEvent: e.domEvent };
|
|
2716
|
+
}
|
|
2717
|
+
fromDomEvent(event, at) {
|
|
2718
|
+
return { at, point: this.toContainerPoint(event), originalEvent: event };
|
|
2719
|
+
}
|
|
2720
|
+
/** Google podaje zdarzenie DOM bez pozycji w kontenerze — liczymy ją z prostokąta hosta. */
|
|
2721
|
+
toContainerPoint(event) {
|
|
2722
|
+
const rect = this.host?.getBoundingClientRect();
|
|
2723
|
+
if (!event || !rect)
|
|
2724
|
+
return { x: 0, y: 0 };
|
|
2725
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
2726
|
+
}
|
|
2727
|
+
toLatLngBounds(b) {
|
|
2728
|
+
return new this.maps.LatLngBounds({ lat: b.south, lng: b.west }, { lat: b.north, lng: b.east });
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
const MODULE_LOAD_ERROR = 'kpt-map: nie udało się załadować biblioteki `leaflet`. ' +
|
|
2733
|
+
'To zależność opcjonalna — zainstaluj ją w aplikacji: `pnpm add leaflet` ' +
|
|
2734
|
+
'(oraz dołącz `leaflet/dist/leaflet.css`).';
|
|
2735
|
+
let modulePromise = null;
|
|
2736
|
+
/**
|
|
2737
|
+
* Ładuje Leaflet raz na aplikację. Leaflet 1.9 jest paczką UMD/CJS bez pola `exports`,
|
|
2738
|
+
* więc pod bundlerem trafia raz jako `default`, a raz jako sam namespace — stąd interop.
|
|
2739
|
+
*/
|
|
2740
|
+
async function loadLeaflet() {
|
|
2741
|
+
modulePromise ??= import('leaflet')
|
|
2742
|
+
.then((mod) => mod?.default ?? mod)
|
|
2743
|
+
.catch((cause) => {
|
|
2744
|
+
modulePromise = null; // pozwól spróbować ponownie po doinstalowaniu paczki
|
|
2745
|
+
throw new Error(MODULE_LOAD_ERROR, { cause });
|
|
2746
|
+
});
|
|
2747
|
+
return modulePromise;
|
|
2748
|
+
}
|
|
2749
|
+
/** Fabryka silnika — każda mapa dostaje własną instancję. */
|
|
2750
|
+
function createLeafletEngine(options = {}) {
|
|
2751
|
+
return new LeafletEngine(options);
|
|
2752
|
+
}
|
|
2753
|
+
class LeafletEngine {
|
|
2754
|
+
constructor(options) {
|
|
2755
|
+
this.options = options;
|
|
2756
|
+
this.kind = 'leaflet';
|
|
2757
|
+
this.L = null;
|
|
2758
|
+
this.map = null;
|
|
2759
|
+
this.tiles = null;
|
|
2760
|
+
this.userMarker = null;
|
|
2761
|
+
this.userCircle = null;
|
|
2762
|
+
/** Znaczniki po `id` — bez tego każdy przelicz przerysowywałby całą mapę. */
|
|
2763
|
+
this.markers = new Map();
|
|
2764
|
+
this.handlers = {};
|
|
2765
|
+
}
|
|
2766
|
+
async init(host, opts) {
|
|
2767
|
+
const L = (this.L = await loadLeaflet());
|
|
2768
|
+
this.map = new L.Map(host, {
|
|
2769
|
+
center: [opts.center.lat, opts.center.lng],
|
|
2770
|
+
zoom: opts.zoom,
|
|
2771
|
+
minZoom: opts.minZoom ?? this.options.minZoom ?? 2,
|
|
2772
|
+
maxZoom: opts.maxZoom ?? this.options.maxZoom ?? 19,
|
|
2773
|
+
// Kontrolki rysuje warstwa Angulara — domyślnych nie da się otokenizować
|
|
2774
|
+
// bez walki z niewarstwowanym `leaflet.css`.
|
|
2775
|
+
zoomControl: false,
|
|
2776
|
+
attributionControl: true,
|
|
2777
|
+
inertia: this.options.inertia ?? true,
|
|
2778
|
+
scrollWheelZoom: this.options.scrollWheelZoom ?? true,
|
|
2779
|
+
maxBounds: opts.maxBounds ? this.toLatLngBounds(opts.maxBounds) : undefined,
|
|
2780
|
+
// Bez tego mapa przy `maxBounds` daje się odciągnąć i sprężynuje.
|
|
2781
|
+
maxBoundsViscosity: opts.maxBounds ? 1 : 0,
|
|
2782
|
+
});
|
|
2783
|
+
this.tiles = new L.TileLayer(opts.tileUrl, {
|
|
2784
|
+
attribution: opts.attribution,
|
|
2785
|
+
maxZoom: opts.maxZoom ?? this.options.maxZoom ?? 19,
|
|
2786
|
+
}).addTo(this.map);
|
|
2787
|
+
this.map.on('move', () => this.emit('move'));
|
|
2788
|
+
this.map.on('moveend', () => this.emit('moveend'));
|
|
2789
|
+
this.map.on('zoomend', () => this.emit('zoomend'));
|
|
2790
|
+
this.map.on('click', (e) => this.emit('click', this.toPointerEvent(e)));
|
|
2791
|
+
this.map.on('contextmenu', (e) => {
|
|
2792
|
+
// Menu przeglądarki zasłoniłoby własne „Dodaj lokalizację".
|
|
2793
|
+
L.DomEvent.preventDefault(e.originalEvent);
|
|
2794
|
+
this.emit('contextmenu', this.toPointerEvent(e));
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
destroy() {
|
|
2798
|
+
this.markers.clear();
|
|
2799
|
+
this.userMarker = null;
|
|
2800
|
+
this.userCircle = null;
|
|
2801
|
+
this.tiles = null;
|
|
2802
|
+
this.map?.remove();
|
|
2803
|
+
this.map = null;
|
|
2804
|
+
}
|
|
2805
|
+
setView(center, zoom, animate = true) {
|
|
2806
|
+
this.map?.setView([center.lat, center.lng], zoom, { animate });
|
|
2807
|
+
}
|
|
2808
|
+
fitBounds(bounds, paddingPx = 0) {
|
|
2809
|
+
this.map?.fitBounds(this.toLatLngBounds(bounds), {
|
|
2810
|
+
padding: [paddingPx, paddingPx],
|
|
2811
|
+
maxZoom: this.options.maxZoom ?? 19,
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
panBy(offset) {
|
|
2815
|
+
this.map?.panBy([offset.x, offset.y], { animate: true });
|
|
2816
|
+
}
|
|
2817
|
+
getCenter() {
|
|
2818
|
+
const c = this.map.getCenter();
|
|
2819
|
+
return { lat: c.lat, lng: c.lng };
|
|
2820
|
+
}
|
|
2821
|
+
getZoom() {
|
|
2822
|
+
return this.map.getZoom();
|
|
2823
|
+
}
|
|
2824
|
+
getBounds() {
|
|
2825
|
+
const b = this.map.getBounds();
|
|
2826
|
+
return { south: b.getSouth(), west: b.getWest(), north: b.getNorth(), east: b.getEast() };
|
|
2827
|
+
}
|
|
2828
|
+
getSize() {
|
|
2829
|
+
const s = this.map.getSize();
|
|
2830
|
+
return { width: s.x, height: s.y };
|
|
2831
|
+
}
|
|
2832
|
+
project(at) {
|
|
2833
|
+
const p = this.map.latLngToContainerPoint([at.lat, at.lng]);
|
|
2834
|
+
return { x: p.x, y: p.y };
|
|
2835
|
+
}
|
|
2836
|
+
unproject(point) {
|
|
2837
|
+
const ll = this.map.containerPointToLatLng([point.x, point.y]);
|
|
2838
|
+
return { lat: ll.lat, lng: ll.lng };
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Wymienia zestaw znaczników różnicowo. Znaczniki, których `id` się nie zmieniło,
|
|
2842
|
+
* zostają na miejscu — inaczej każdy przelicz gasiłby animacje i migotał.
|
|
2843
|
+
*/
|
|
2844
|
+
setMarkers(next) {
|
|
2845
|
+
if (!this.map || !this.L)
|
|
2846
|
+
return;
|
|
2847
|
+
const L = this.L;
|
|
2848
|
+
const seen = new Set();
|
|
2849
|
+
for (const m of next) {
|
|
2850
|
+
seen.add(m.id);
|
|
2851
|
+
const existing = this.markers.get(m.id);
|
|
2852
|
+
const icon = L.divIcon({
|
|
2853
|
+
html: m.html,
|
|
2854
|
+
className: 'kpt-map__icon',
|
|
2855
|
+
iconSize: [m.size.width, m.size.height],
|
|
2856
|
+
iconAnchor: [m.anchor.x, m.anchor.y],
|
|
2857
|
+
});
|
|
2858
|
+
if (existing) {
|
|
2859
|
+
existing.setLatLng([m.at.lat, m.at.lng]);
|
|
2860
|
+
existing.setIcon(icon);
|
|
2861
|
+
if (m.zIndex !== undefined)
|
|
2862
|
+
existing.setZIndexOffset(m.zIndex);
|
|
2863
|
+
continue;
|
|
2864
|
+
}
|
|
2865
|
+
const marker = new L.Marker([m.at.lat, m.at.lng], {
|
|
2866
|
+
icon,
|
|
2867
|
+
// Znacznik niesie własny `aria-label` w HTML-u ikony; pusty `alt`
|
|
2868
|
+
// powstrzymuje Leaflet przed dorzuceniem drugiej etykiety.
|
|
2869
|
+
alt: '',
|
|
2870
|
+
keyboard: false,
|
|
2871
|
+
zIndexOffset: m.zIndex ?? 0,
|
|
2872
|
+
});
|
|
2873
|
+
marker.on('click', (e) => {
|
|
2874
|
+
L.DomEvent.stopPropagation(e);
|
|
2875
|
+
this.emit('markerclick', m.id, this.toPointerEvent(e));
|
|
2876
|
+
});
|
|
2877
|
+
marker.addTo(this.map);
|
|
2878
|
+
this.markers.set(m.id, marker);
|
|
2879
|
+
}
|
|
2880
|
+
for (const [id, marker] of this.markers) {
|
|
2881
|
+
if (seen.has(id))
|
|
2882
|
+
continue;
|
|
2883
|
+
marker.remove();
|
|
2884
|
+
this.markers.delete(id);
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
setUserPosition(position, html) {
|
|
2888
|
+
if (!this.map || !this.L)
|
|
2889
|
+
return;
|
|
2890
|
+
const L = this.L;
|
|
2891
|
+
if (!position) {
|
|
2892
|
+
this.userMarker?.remove();
|
|
2893
|
+
this.userCircle?.remove();
|
|
2894
|
+
this.userMarker = null;
|
|
2895
|
+
this.userCircle = null;
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
const at = [position.at.lat, position.at.lng];
|
|
2899
|
+
const icon = L.divIcon({ html, className: 'kpt-map__icon', iconSize: [24, 24], iconAnchor: [12, 12] });
|
|
2900
|
+
if (this.userMarker) {
|
|
2901
|
+
this.userMarker.setLatLng(at);
|
|
2902
|
+
this.userMarker.setIcon(icon);
|
|
2903
|
+
}
|
|
2904
|
+
else {
|
|
2905
|
+
// Nad znacznikami lokalizacji — własne położenie ma być zawsze widoczne.
|
|
2906
|
+
this.userMarker = new L.Marker(at, { icon, alt: '', keyboard: false, zIndexOffset: 1000 }).addTo(this.map);
|
|
2907
|
+
}
|
|
2908
|
+
// Promień w metrach; L.Circle sam przelicza go na piksele przy każdym zoomie.
|
|
2909
|
+
if (this.userCircle) {
|
|
2910
|
+
this.userCircle.setLatLng(at);
|
|
2911
|
+
this.userCircle.setRadius(position.accuracy);
|
|
2912
|
+
}
|
|
2913
|
+
else {
|
|
2914
|
+
this.userCircle = new L.Circle(at, {
|
|
2915
|
+
radius: position.accuracy,
|
|
2916
|
+
className: 'kpt-map__accuracy',
|
|
2917
|
+
interactive: false,
|
|
2918
|
+
// Kolory idą z klasy CSS (tokeny), nie z opcji Leafleta.
|
|
2919
|
+
stroke: true,
|
|
2920
|
+
weight: 1,
|
|
2921
|
+
}).addTo(this.map);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
setTileSource(tileUrl, attribution) {
|
|
2925
|
+
if (!this.map || !this.L)
|
|
2926
|
+
return;
|
|
2927
|
+
this.tiles?.remove();
|
|
2928
|
+
this.tiles = new this.L.TileLayer(tileUrl, {
|
|
2929
|
+
attribution,
|
|
2930
|
+
maxZoom: this.options.maxZoom ?? 19,
|
|
2931
|
+
}).addTo(this.map);
|
|
2932
|
+
// Kafle wchodzą pod znaczniki — bez tego nowa warstwa przykryłaby je.
|
|
2933
|
+
this.tiles.bringToBack();
|
|
2934
|
+
}
|
|
2935
|
+
setMaxBounds(bounds) {
|
|
2936
|
+
if (!this.map)
|
|
2937
|
+
return;
|
|
2938
|
+
this.map.setMaxBounds(bounds ? this.toLatLngBounds(bounds) : null);
|
|
2939
|
+
this.map.options.maxBoundsViscosity = bounds ? 1 : 0;
|
|
2940
|
+
}
|
|
2941
|
+
invalidateSize() {
|
|
2942
|
+
// `animate: false` — to reakcja na zmianę layoutu, nie gest użytkownika.
|
|
2943
|
+
this.map?.invalidateSize({ animate: false });
|
|
2944
|
+
}
|
|
2945
|
+
on(event, handler) {
|
|
2946
|
+
(this.handlers[event] ??= []).push(handler);
|
|
2947
|
+
}
|
|
2948
|
+
emit(event, ...args) {
|
|
2949
|
+
for (const handler of this.handlers[event] ?? []) {
|
|
2950
|
+
handler(...args);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
toPointerEvent(e) {
|
|
2954
|
+
return {
|
|
2955
|
+
at: { lat: e.latlng.lat, lng: e.latlng.lng },
|
|
2956
|
+
point: { x: e.containerPoint.x, y: e.containerPoint.y },
|
|
2957
|
+
originalEvent: e.originalEvent,
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
toLatLngBounds(b) {
|
|
2961
|
+
return new this.L.LatLngBounds([b.south, b.west], [b.north, b.east]);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
/**
|
|
2966
|
+
* Fabryka silnika mapy. Fabryka, a nie gotowa instancja — każda mapa na stronie
|
|
2967
|
+
* potrzebuje własnego obiektu, a provider jest jeden.
|
|
2968
|
+
*/
|
|
2969
|
+
const KPT_MAP_ENGINE = new InjectionToken('KPT_MAP_ENGINE');
|
|
2970
|
+
/**
|
|
2971
|
+
* Silnik Leaflet + OpenStreetMap. Wymaga zainstalowanej paczki `leaflet`
|
|
2972
|
+
* (zależność opcjonalna) i dołączonego `leaflet/dist/leaflet.css`.
|
|
2973
|
+
*
|
|
2974
|
+
* @example
|
|
2975
|
+
* // main.ts
|
|
2976
|
+
* bootstrapApplication(App, { providers: [provideKptLeafletMap()] });
|
|
2977
|
+
*/
|
|
2978
|
+
function provideKptLeafletMap(options = {}) {
|
|
2979
|
+
return [
|
|
2980
|
+
{ provide: KPT_MAP_ENGINE, useValue: () => createLeafletEngine(options) },
|
|
2981
|
+
// Nadpisywalne — wystarczy podać własny provider PO tym.
|
|
2982
|
+
{ provide: KPT_MAP_GEOLOCATION, useClass: KptBrowserGeolocation },
|
|
2983
|
+
];
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Silnik Google Maps. Nie wymaga żadnej paczki npm — API dogrywa się skryptem przy pierwszej
|
|
2987
|
+
* mapie — ale wymaga klucza, konta rozliczeniowego i **`mapId`** z konsoli Google
|
|
2988
|
+
* (*Map management → Create Map ID*): bez niego znaczniki się nie pojawią.
|
|
2989
|
+
*
|
|
2990
|
+
* Różnice wobec Leafleta: `[tileUrl]` i `[attribution]` są bez znaczenia (kafle i atrybucję
|
|
2991
|
+
* dostarcza Google i nie wolno ich zasłonić), wczytanie mapy liczy się do SKU Dynamic Maps
|
|
2992
|
+
* (10 000 miesięcznie za darmo), a zoom bywa ułamkowy.
|
|
2993
|
+
*
|
|
2994
|
+
* **To jedyny silnik, z którym wolno użyć geokodera Google.** Service Specific Terms §6.2
|
|
2995
|
+
* i §14.2 zabraniają pokazywać wyniki Geocoding API i Places API razem z mapą nie-Google,
|
|
2996
|
+
* a więc także z domyślnym Leafletem tej biblioteki.
|
|
2997
|
+
*
|
|
2998
|
+
* @example
|
|
2999
|
+
* // main.ts
|
|
3000
|
+
* providers: [provideKptGoogleMap({ apiKey: env.googleMapsKey, mapId: env.googleMapId })]
|
|
3001
|
+
*/
|
|
3002
|
+
function provideKptGoogleMap(options) {
|
|
3003
|
+
return [
|
|
3004
|
+
{ provide: KPT_MAP_ENGINE, useValue: () => createGoogleEngine(options) },
|
|
3005
|
+
{ provide: KPT_MAP_GEOLOCATION, useClass: KptBrowserGeolocation },
|
|
3006
|
+
];
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
const _c0$1 = ["canvas"];
|
|
3010
|
+
const _c1 = ["popupEl"];
|
|
3011
|
+
const _c2 = (a0, a1, a2, a3, a4) => ({ $implicit: a0, data: a1, category: a2, distance: a3, close: a4 });
|
|
3012
|
+
const _c3 = (a0, a1, a2) => ({ $implicit: a0, at: a1, category: a2 });
|
|
3013
|
+
const _forTrack0$1 = ($index, $item) => $item.id;
|
|
3014
|
+
function KptMap_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
3015
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
3016
|
+
i0.ɵɵelementStart(0, "button", 11);
|
|
3017
|
+
i0.ɵɵlistener("click", function KptMap_Conditional_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.centreOnUser()); });
|
|
3018
|
+
i0.ɵɵelement(1, "span", 12);
|
|
3019
|
+
i0.ɵɵelementEnd();
|
|
3020
|
+
} if (rf & 2) {
|
|
3021
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3022
|
+
i0.ɵɵproperty("title", ctx_r2.locationBlocked() ? ctx_r2.i18n.t("map.locationDenied") : ctx_r2.i18n.t("map.locateMe"))("disabled", !ctx_r2.userPosition());
|
|
3023
|
+
i0.ɵɵattribute("aria-label", ctx_r2.locationBlocked() ? ctx_r2.i18n.t("map.locationDenied") : ctx_r2.i18n.t("map.locateMe"));
|
|
3024
|
+
} }
|
|
3025
|
+
function KptMap_Conditional_11_Template(rf, ctx) { if (rf & 1) {
|
|
3026
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
3027
|
+
i0.ɵɵelementStart(0, "div", 8)(1, "kpt-map-search", 13);
|
|
3028
|
+
i0.ɵɵlistener("selected", function KptMap_Conditional_11_Template_kpt_map_search_selected_1_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onSearchSelect($event)); })("cleared", function KptMap_Conditional_11_Template_kpt_map_search_cleared_1_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.clearSearchPin()); });
|
|
3029
|
+
i0.ɵɵelementEnd()();
|
|
3030
|
+
} if (rf & 2) {
|
|
3031
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3032
|
+
i0.ɵɵadvance();
|
|
3033
|
+
i0.ɵɵproperty("geocoder", ctx_r2.activeGeocoder())("placeholder", ctx_r2.searchPlaceholder())("minLength", ctx_r2.searchOptions().minLength ?? 3)("debounce", ctx_r2.searchOptions().debounce ?? 300)("limit", ctx_r2.searchOptions().limit ?? 5)("viewbox", ctx_r2.searchViewbox())("countryCodes", ctx_r2.searchCountryCodes());
|
|
3034
|
+
} }
|
|
3035
|
+
function KptMap_Conditional_12_Conditional_0_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
3036
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
3037
|
+
i0.ɵɵelementStart(0, "button", 19);
|
|
3038
|
+
i0.ɵɵlistener("click", function KptMap_Conditional_12_Conditional_0_For_4_Template_button_click_0_listener() { const category_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.startCreate(category_r6)); });
|
|
3039
|
+
i0.ɵɵelement(1, "span", 20);
|
|
3040
|
+
i0.ɵɵtext(2);
|
|
3041
|
+
i0.ɵɵelementEnd();
|
|
3042
|
+
} if (rf & 2) {
|
|
3043
|
+
const category_r6 = ctx.$implicit;
|
|
3044
|
+
i0.ɵɵadvance(2);
|
|
3045
|
+
i0.ɵɵtextInterpolate1(" ", category_r6.label, " ");
|
|
3046
|
+
} }
|
|
3047
|
+
function KptMap_Conditional_12_Conditional_0_ForEmpty_5_Template(rf, ctx) { if (rf & 1) {
|
|
3048
|
+
i0.ɵɵelementStart(0, "p", 18);
|
|
3049
|
+
i0.ɵɵtext(1);
|
|
3050
|
+
i0.ɵɵelementEnd();
|
|
3051
|
+
} if (rf & 2) {
|
|
3052
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
3053
|
+
i0.ɵɵadvance();
|
|
3054
|
+
i0.ɵɵtextInterpolate(ctx_r2.i18n.t("map.empty"));
|
|
3055
|
+
} }
|
|
3056
|
+
function KptMap_Conditional_12_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
3057
|
+
i0.ɵɵelementStart(0, "div", 15)(1, "p", 16);
|
|
3058
|
+
i0.ɵɵtext(2);
|
|
3059
|
+
i0.ɵɵelementEnd();
|
|
3060
|
+
i0.ɵɵrepeaterCreate(3, KptMap_Conditional_12_Conditional_0_For_4_Template, 3, 1, "button", 17, _forTrack0$1, false, KptMap_Conditional_12_Conditional_0_ForEmpty_5_Template, 2, 1, "p", 18);
|
|
3061
|
+
i0.ɵɵelementEnd();
|
|
3062
|
+
} if (rf & 2) {
|
|
3063
|
+
const position_r7 = ctx;
|
|
3064
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
3065
|
+
i0.ɵɵstyleProp("--%NS%_kpt-map-x", position_r7.x, "px")("--%NS%_kpt-map-y", position_r7.y, "px");
|
|
3066
|
+
i0.ɵɵattribute("aria-label", ctx_r2.i18n.t("map.addLocation"));
|
|
3067
|
+
i0.ɵɵadvance(2);
|
|
3068
|
+
i0.ɵɵtextInterpolate(ctx_r2.i18n.t("map.chooseCategory"));
|
|
3069
|
+
i0.ɵɵadvance();
|
|
3070
|
+
i0.ɵɵrepeater(ctx_r2.creatableCategories());
|
|
3071
|
+
} }
|
|
3072
|
+
function KptMap_Conditional_12_Template(rf, ctx) { if (rf & 1) {
|
|
3073
|
+
i0.ɵɵconditionalCreate(0, KptMap_Conditional_12_Conditional_0_Template, 6, 7, "div", 14);
|
|
3074
|
+
} if (rf & 2) {
|
|
3075
|
+
let tmp_2_0;
|
|
3076
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3077
|
+
i0.ɵɵconditional((tmp_2_0 = ctx_r2.contextPosition()) ? 0 : -1, tmp_2_0);
|
|
3078
|
+
} }
|
|
3079
|
+
function KptMap_Conditional_13_Conditional_0_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
3080
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
3081
|
+
i0.ɵɵelementStart(0, "button", 26);
|
|
3082
|
+
i0.ɵɵlistener("click", function KptMap_Conditional_13_Conditional_0_Conditional_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.closePopup()); });
|
|
3083
|
+
i0.ɵɵelementStart(1, "span", 6);
|
|
3084
|
+
i0.ɵɵtext(2, "\u00D7");
|
|
3085
|
+
i0.ɵɵelementEnd()();
|
|
3086
|
+
} if (rf & 2) {
|
|
3087
|
+
const ctx_r2 = i0.ɵɵnextContext(3);
|
|
3088
|
+
i0.ɵɵattribute("aria-label", ctx_r2.i18n.t("common.close"));
|
|
3089
|
+
} }
|
|
3090
|
+
function KptMap_Conditional_13_Conditional_0_Conditional_3_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
3091
|
+
const _r9 = i0.ɵɵgetCurrentView();
|
|
3092
|
+
i0.ɵɵelementStart(0, "button", 30);
|
|
3093
|
+
i0.ɵɵlistener("click", function KptMap_Conditional_13_Conditional_0_Conditional_3_For_4_Template_button_click_0_listener() { const item_r10 = i0.ɵɵrestoreView(_r9).$implicit; const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.selectFromGroup(item_r10)); });
|
|
3094
|
+
i0.ɵɵtext(1);
|
|
3095
|
+
i0.ɵɵelementEnd();
|
|
3096
|
+
} if (rf & 2) {
|
|
3097
|
+
const item_r10 = ctx.$implicit;
|
|
3098
|
+
const state_r11 = i0.ɵɵnextContext(3);
|
|
3099
|
+
i0.ɵɵattribute("aria-selected", item_r10.id === state_r11.location.id);
|
|
3100
|
+
i0.ɵɵadvance();
|
|
3101
|
+
i0.ɵɵtextInterpolate1(" ", item_r10.title, " ");
|
|
3102
|
+
} }
|
|
3103
|
+
function KptMap_Conditional_13_Conditional_0_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
3104
|
+
i0.ɵɵelementStart(0, "p", 27);
|
|
3105
|
+
i0.ɵɵtext(1);
|
|
3106
|
+
i0.ɵɵelementEnd();
|
|
3107
|
+
i0.ɵɵelementStart(2, "div", 28);
|
|
3108
|
+
i0.ɵɵrepeaterCreate(3, KptMap_Conditional_13_Conditional_0_Conditional_3_For_4_Template, 2, 2, "button", 29, _forTrack0$1);
|
|
3109
|
+
i0.ɵɵelementEnd();
|
|
3110
|
+
} if (rf & 2) {
|
|
3111
|
+
const state_r11 = i0.ɵɵnextContext(2);
|
|
3112
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3113
|
+
i0.ɵɵadvance();
|
|
3114
|
+
i0.ɵɵtextInterpolate(ctx_r2.i18n.t("map.clusterItems"));
|
|
3115
|
+
i0.ɵɵadvance(2);
|
|
3116
|
+
i0.ɵɵrepeater(state_r11.group.items);
|
|
3117
|
+
} }
|
|
3118
|
+
function KptMap_Conditional_13_Conditional_0_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
3119
|
+
i0.ɵɵelementContainer(0, 25);
|
|
3120
|
+
} if (rf & 2) {
|
|
3121
|
+
const state_r11 = i0.ɵɵnextContext(2);
|
|
3122
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3123
|
+
i0.ɵɵproperty("ngTemplateOutlet", ctx.templateRef)("ngTemplateOutletContext", i0.ɵɵpureFunction5(2, _c2, state_r11.location, state_r11.location.data, ctx_r2.popupCategory(), ctx_r2.popupDistance(), ctx_r2.closePopupFn));
|
|
3124
|
+
} }
|
|
3125
|
+
function KptMap_Conditional_13_Conditional_0_Conditional_6_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
3126
|
+
i0.ɵɵelementStart(0, "p", 32);
|
|
3127
|
+
i0.ɵɵtext(1);
|
|
3128
|
+
i0.ɵɵelementEnd();
|
|
3129
|
+
} if (rf & 2) {
|
|
3130
|
+
i0.ɵɵadvance();
|
|
3131
|
+
i0.ɵɵtextInterpolate(ctx.label);
|
|
3132
|
+
} }
|
|
3133
|
+
function KptMap_Conditional_13_Conditional_0_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
3134
|
+
i0.ɵɵelementStart(0, "strong", 31);
|
|
3135
|
+
i0.ɵɵtext(1);
|
|
3136
|
+
i0.ɵɵelementEnd();
|
|
3137
|
+
i0.ɵɵconditionalCreate(2, KptMap_Conditional_13_Conditional_0_Conditional_6_Conditional_2_Template, 2, 1, "p", 32);
|
|
3138
|
+
} if (rf & 2) {
|
|
3139
|
+
let tmp_8_0;
|
|
3140
|
+
const state_r11 = i0.ɵɵnextContext(2);
|
|
3141
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3142
|
+
i0.ɵɵadvance();
|
|
3143
|
+
i0.ɵɵtextInterpolate(state_r11.location.title);
|
|
3144
|
+
i0.ɵɵadvance();
|
|
3145
|
+
i0.ɵɵconditional((tmp_8_0 = ctx_r2.popupCategory()) ? 2 : -1, tmp_8_0);
|
|
3146
|
+
} }
|
|
3147
|
+
function KptMap_Conditional_13_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
3148
|
+
i0.ɵɵelementStart(0, "div", 22, 1);
|
|
3149
|
+
i0.ɵɵconditionalCreate(2, KptMap_Conditional_13_Conditional_0_Conditional_2_Template, 3, 1, "button", 23);
|
|
3150
|
+
i0.ɵɵconditionalCreate(3, KptMap_Conditional_13_Conditional_0_Conditional_3_Template, 5, 1);
|
|
3151
|
+
i0.ɵɵelementStart(4, "div", 24);
|
|
3152
|
+
i0.ɵɵconditionalCreate(5, KptMap_Conditional_13_Conditional_0_Conditional_5_Template, 1, 8, "ng-container", 25)(6, KptMap_Conditional_13_Conditional_0_Conditional_6_Template, 3, 2);
|
|
3153
|
+
i0.ɵɵelementEnd()();
|
|
3154
|
+
} if (rf & 2) {
|
|
3155
|
+
let tmp_12_0;
|
|
3156
|
+
const position_r12 = ctx;
|
|
3157
|
+
const state_r11 = i0.ɵɵnextContext();
|
|
3158
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3159
|
+
i0.ɵɵstyleProp("--%NS%_kpt-map-x", position_r12.x, "px")("--%NS%_kpt-map-y", position_r12.y, "px");
|
|
3160
|
+
i0.ɵɵattribute("data-layout", ctx_r2.popupLayout())("aria-label", state_r11.location.title);
|
|
3161
|
+
i0.ɵɵadvance(2);
|
|
3162
|
+
i0.ɵɵconditional(ctx_r2.popupLayout() === "plain" ? 2 : -1);
|
|
3163
|
+
i0.ɵɵadvance();
|
|
3164
|
+
i0.ɵɵconditional(state_r11.group ? 3 : -1);
|
|
3165
|
+
i0.ɵɵadvance(2);
|
|
3166
|
+
i0.ɵɵconditional((tmp_12_0 = ctx_r2.popupTemplate()) ? 5 : 6, tmp_12_0);
|
|
3167
|
+
} }
|
|
3168
|
+
function KptMap_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
3169
|
+
i0.ɵɵconditionalCreate(0, KptMap_Conditional_13_Conditional_0_Template, 7, 9, "div", 21);
|
|
3170
|
+
} if (rf & 2) {
|
|
3171
|
+
let tmp_3_0;
|
|
3172
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3173
|
+
i0.ɵɵconditional((tmp_3_0 = ctx_r2.popupPosition()) ? 0 : -1, tmp_3_0);
|
|
3174
|
+
} }
|
|
3175
|
+
function KptMap_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
3176
|
+
const _r13 = i0.ɵɵgetCurrentView();
|
|
3177
|
+
i0.ɵɵelementStart(0, "kpt-map-legend", 33);
|
|
3178
|
+
i0.ɵɵtwoWayListener("hiddenChange", function KptMap_Conditional_14_Template_kpt_map_legend_hiddenChange_0_listener($event) { i0.ɵɵrestoreView(_r13); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.hiddenCategories, $event) || (ctx_r2.hiddenCategories = $event); return i0.ɵɵresetView($event); });
|
|
3179
|
+
i0.ɵɵelementEnd();
|
|
3180
|
+
} if (rf & 2) {
|
|
3181
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3182
|
+
i0.ɵɵproperty("categories", ctx_r2.categories());
|
|
3183
|
+
i0.ɵɵtwoWayProperty("hidden", ctx_r2.hiddenCategories);
|
|
3184
|
+
i0.ɵɵproperty("counts", ctx_r2.counts());
|
|
3185
|
+
} }
|
|
3186
|
+
function KptMap_Conditional_16_Conditional_0_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
3187
|
+
i0.ɵɵelementContainer(0, 25);
|
|
3188
|
+
} if (rf & 2) {
|
|
3189
|
+
const tpl_r14 = i0.ɵɵnextContext();
|
|
3190
|
+
const target_r15 = i0.ɵɵnextContext();
|
|
3191
|
+
i0.ɵɵproperty("ngTemplateOutlet", tpl_r14.templateRef)("ngTemplateOutletContext", i0.ɵɵpureFunction3(2, _c3, ctx, target_r15.at, target_r15.category));
|
|
3192
|
+
} }
|
|
3193
|
+
function KptMap_Conditional_16_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
3194
|
+
i0.ɵɵconditionalCreate(0, KptMap_Conditional_16_Conditional_0_Conditional_0_Template, 1, 6, "ng-container", 25);
|
|
3195
|
+
} if (rf & 2) {
|
|
3196
|
+
let tmp_5_0;
|
|
3197
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
3198
|
+
i0.ɵɵconditional((tmp_5_0 = ctx_r2.createFormApi()) ? 0 : -1, tmp_5_0);
|
|
3199
|
+
} }
|
|
3200
|
+
function KptMap_Conditional_16_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
3201
|
+
const _r16 = i0.ɵɵgetCurrentView();
|
|
3202
|
+
i0.ɵɵelementStart(0, "kpt-map-create-dialog", 35);
|
|
3203
|
+
i0.ɵɵlistener("save", function KptMap_Conditional_16_Conditional_1_Template_kpt_map_create_dialog_save_0_listener($event) { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.emitCreate($event)); })("cancelled", function KptMap_Conditional_16_Conditional_1_Template_kpt_map_create_dialog_cancelled_0_listener() { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.closeCreate()); });
|
|
3204
|
+
i0.ɵɵelementEnd();
|
|
3205
|
+
} if (rf & 2) {
|
|
3206
|
+
const target_r15 = i0.ɵɵnextContext();
|
|
3207
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3208
|
+
i0.ɵɵproperty("geocoder", ctx_r2.activeGeocoder())("category", target_r15.category)("at", target_r15.at);
|
|
3209
|
+
} }
|
|
3210
|
+
function KptMap_Conditional_16_Template(rf, ctx) { if (rf & 1) {
|
|
3211
|
+
i0.ɵɵconditionalCreate(0, KptMap_Conditional_16_Conditional_0_Template, 1, 1)(1, KptMap_Conditional_16_Conditional_1_Template, 1, 3, "kpt-map-create-dialog", 34);
|
|
3212
|
+
} if (rf & 2) {
|
|
3213
|
+
let tmp_3_0;
|
|
3214
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
3215
|
+
i0.ɵɵconditional((tmp_3_0 = ctx_r2.createFormTemplate()) ? 0 : 1, tmp_3_0);
|
|
3216
|
+
} }
|
|
3217
|
+
/** Rozmiary ikon w pikselach — silnik potrzebuje ich do zakotwiczenia znacznika. */
|
|
3218
|
+
const MARKER_SIZE = { width: 32, height: 42 };
|
|
3219
|
+
const DOT_SIZE = { width: 24, height: 24 };
|
|
3220
|
+
const CLUSTER_SIZE = { width: 44, height: 44 };
|
|
3221
|
+
/** Ile miejsca zostawić wokół zasięgu przy `fitBounds`. */
|
|
3222
|
+
const FIT_PADDING = 48;
|
|
3223
|
+
/**
|
|
3224
|
+
* Identyfikator tymczasowej pinezki wyszukiwarki. Nie należy do `entries()`, więc
|
|
3225
|
+
* `onMarkerClick()` nie znajdzie dla niego wpisu i cicho wyjdzie — kliknięcie w wynik
|
|
3226
|
+
* wyszukiwania niczego nie otwiera.
|
|
3227
|
+
*/
|
|
3228
|
+
const SEARCH_MARKER_ID = 'kpt-map:search';
|
|
3229
|
+
/** Brak silnika to najczęstsza pomyłka przy pierwszym użyciu — komunikat niesie instrukcję. */
|
|
3230
|
+
const NO_ENGINE_ERROR = 'kpt-map: brak silnika mapy. Zarejestruj go w aplikacji — ' +
|
|
3231
|
+
'`provideKptLeafletMap()` albo `provideKptGoogleMap({ apiKey, mapId })` — ' +
|
|
3232
|
+
'albo podaj fabrykę wprost przez `[engine]`.';
|
|
3233
|
+
/**
|
|
3234
|
+
* Mapa z konfigurowalnymi znacznikami: kategorie z kolorem i ikoną, grupowanie z licznikiem,
|
|
3235
|
+
* popup szczegółów o szablonie zależnym od kategorii, legenda i dodawanie lokalizacji
|
|
3236
|
+
* prawym przyciskiem myszy.
|
|
3237
|
+
*
|
|
3238
|
+
* Silnik (domyślnie Leaflet + OpenStreetMap) dostarcza `provideKptLeafletMap()`. Wymaga
|
|
3239
|
+
* zainstalowanej paczki `leaflet` (zależność opcjonalna) i dołączonego `leaflet/dist/leaflet.css`.
|
|
3240
|
+
*
|
|
3241
|
+
* @example
|
|
3242
|
+
* <kpt-map [locations]="locations()" [categories]="categories" allowCreate showUserLocation>
|
|
3243
|
+
* <ng-template kptMapPopup category="closure" let-loc let-data="data">
|
|
3244
|
+
* <strong>{{ loc.title }}</strong>
|
|
3245
|
+
* <p>{{ data.address }}</p>
|
|
3246
|
+
* </ng-template>
|
|
3247
|
+
* </kpt-map>
|
|
3248
|
+
*/
|
|
3249
|
+
class KptMap {
|
|
3250
|
+
constructor() {
|
|
3251
|
+
this.locations = input.required(/* @ts-ignore */
|
|
3252
|
+
...(ngDevMode ? [{ debugName: "locations" }] : /* istanbul ignore next */ []));
|
|
3253
|
+
this.categories = input.required(/* @ts-ignore */
|
|
3254
|
+
...(ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []));
|
|
3255
|
+
/** Kod kraju z presetów (`PL`, `DE`, `EU`…). Nieznany kod cofa się do Polski. */
|
|
3256
|
+
this.country = input('PL', /* @ts-ignore */
|
|
3257
|
+
...(ngDevMode ? [{ debugName: "country" }] : /* istanbul ignore next */ []));
|
|
3258
|
+
/** Jawny widok startowy; nadpisuje `country`. */
|
|
3259
|
+
this.view = input(null, /* @ts-ignore */
|
|
3260
|
+
...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
|
|
3261
|
+
/** Blokuje przesuwanie poza zasięg kraju (albo `bounds`, gdy podane). */
|
|
3262
|
+
this.restrictToCountry = input(false, { ...(ngDevMode ? { debugName: "restrictToCountry" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3263
|
+
/** Jawny zasięg startowy; nadpisuje `country` i `view`. */
|
|
3264
|
+
this.bounds = input(null, /* @ts-ignore */
|
|
3265
|
+
...(ngDevMode ? [{ debugName: "bounds" }] : /* istanbul ignore next */ []));
|
|
3266
|
+
this.cluster = input(true, { ...(ngDevMode ? { debugName: "cluster" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3267
|
+
this.clusterOptions = input({}, /* @ts-ignore */
|
|
3268
|
+
...(ngDevMode ? [{ debugName: "clusterOptions" }] : /* istanbul ignore next */ []));
|
|
3269
|
+
/** `dominant` barwi grupę kolorem najliczniejszej kategorii. */
|
|
3270
|
+
this.clusterColorBy = input('none', /* @ts-ignore */
|
|
3271
|
+
...(ngDevMode ? [{ debugName: "clusterColorBy" }] : /* istanbul ignore next */ []));
|
|
3272
|
+
/** Renderuje legendę pod mapą. Wyłącz, jeśli chcesz ją umieścić gdzie indziej. */
|
|
3273
|
+
this.legend = input(true, { ...(ngDevMode ? { debugName: "legend" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3274
|
+
/** Identyfikatory ukrytych kategorii (dwukierunkowe) — legenda je przełącza. */
|
|
3275
|
+
this.hiddenCategories = model([], /* @ts-ignore */
|
|
3276
|
+
...(ngDevMode ? [{ debugName: "hiddenCategories" }] : /* istanbul ignore next */ []));
|
|
3277
|
+
/** Pokazuje liczniki przy pozycjach legendy. */
|
|
3278
|
+
this.legendCounts = input(true, { ...(ngDevMode ? { debugName: "legendCounts" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3279
|
+
this.showUserLocation = input(false, { ...(ngDevMode ? { debugName: "showUserLocation" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3280
|
+
/** Przy każdej nowej pozycji centruje mapę na użytkowniku. */
|
|
3281
|
+
this.followUser = input(false, { ...(ngDevMode ? { debugName: "followUser" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3282
|
+
/** Włącza menu „Dodaj lokalizację" pod prawym przyciskiem myszy. */
|
|
3283
|
+
this.allowCreate = input(false, { ...(ngDevMode ? { debugName: "allowCreate" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3284
|
+
/**
|
|
3285
|
+
* Pokazuje wyszukiwarkę adresów w rogu mapy. Wymaga portu `KPT_MAP_GEOCODER`
|
|
3286
|
+
* **z metodą `search()`** — bez niego pole się nie renderuje, bo biblioteka nie
|
|
3287
|
+
* odpytuje cudzego serwera bez zgody aplikacji.
|
|
3288
|
+
*/
|
|
3289
|
+
this.search = input(false, { ...(ngDevMode ? { debugName: "search" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3290
|
+
/** Podpowiedź w pustym polu wyszukiwania. Pusta oddaje głos słownikowi. */
|
|
3291
|
+
this.searchPlaceholder = input('', /* @ts-ignore */
|
|
3292
|
+
...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
3293
|
+
/** Strojenie wyszukiwarki. Domyślnie `3 / 300 ms / 5`. */
|
|
3294
|
+
this.searchOptions = input({}, /* @ts-ignore */
|
|
3295
|
+
...(ngDevMode ? [{ debugName: "searchOptions" }] : /* istanbul ignore next */ []));
|
|
3296
|
+
/**
|
|
3297
|
+
* Zoom, na jaki mapa wchodzi po wybraniu wyniku bez własnego zasięgu (pojedynczy adres).
|
|
3298
|
+
* Wynik z zasięgiem — miasto, region — dostaje `fitBounds` zamiast tego.
|
|
3299
|
+
*/
|
|
3300
|
+
this.searchZoom = input(16, { ...(ngDevMode ? { debugName: "searchZoom" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
3301
|
+
/** Zostawia tymczasową pinezkę w znalezionym punkcie. */
|
|
3302
|
+
this.searchMarker = input(true, { ...(ngDevMode ? { debugName: "searchMarker" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3303
|
+
/**
|
|
3304
|
+
* Wygląd popupu. `plain` to wąska ramka na treść tekstową (domyślna),
|
|
3305
|
+
* `card` oddaje całą szerokość i wysokość karcie miejsca: bez paddingu,
|
|
3306
|
+
* bez wbudowanego krzyżyka, z przewijaniem, gdy treść nie mieści się w mapie.
|
|
3307
|
+
*/
|
|
3308
|
+
this.popupLayout = input('plain', /* @ts-ignore */
|
|
3309
|
+
...(ngDevMode ? [{ debugName: "popupLayout" }] : /* istanbul ignore next */ []));
|
|
3310
|
+
/**
|
|
3311
|
+
* Punkt odniesienia dystansu podawanego szablonowi popupu jako `distance`.
|
|
3312
|
+
* `null` znaczy „pozycja użytkownika z `KPT_MAP_GEOLOCATION`", więc domyślnie
|
|
3313
|
+
* jest to odległość od miejsca, w którym stoi patrzący. Aplikacja może podstawić
|
|
3314
|
+
* inny punkt — wyszukany adres, siedzibę firmy, stałą pozycję w demie.
|
|
3315
|
+
*/
|
|
3316
|
+
this.distanceFrom = input(null, /* @ts-ignore */
|
|
3317
|
+
...(ngDevMode ? [{ debugName: "distanceFrom" }] : /* istanbul ignore next */ []));
|
|
3318
|
+
this.height = input('480px', /* @ts-ignore */
|
|
3319
|
+
...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
3320
|
+
/** Bez znaczenia dla silnika Google — kafle i atrybucję dostarcza tam Google. */
|
|
3321
|
+
this.tileUrl = input(null, /* @ts-ignore */
|
|
3322
|
+
...(ngDevMode ? [{ debugName: "tileUrl" }] : /* istanbul ignore next */ []));
|
|
3323
|
+
this.attribution = input(null, /* @ts-ignore */
|
|
3324
|
+
...(ngDevMode ? [{ debugName: "attribution" }] : /* istanbul ignore next */ []));
|
|
3325
|
+
/**
|
|
3326
|
+
* Silnik **tej** mapy. Podany wygrywa nad `KPT_MAP_ENGINE` z DI, więc dwie mapy o różnych
|
|
3327
|
+
* silnikach mieszczą się na jednej stronie bez komponentów-opakowań. Bez niego wszystko
|
|
3328
|
+
* działa jak dotąd — silnik przychodzi z DI.
|
|
3329
|
+
*
|
|
3330
|
+
* Fabryka, a nie gotowa instancja: każda mapa potrzebuje własnego obiektu. Czytany raz,
|
|
3331
|
+
* przy inicjalizacji — podmiana po starcie nie przeładowuje mapy.
|
|
3332
|
+
*
|
|
3333
|
+
* @example
|
|
3334
|
+
* readonly google: KptMapEngineFactory = () => createGoogleEngine({ apiKey, mapId });
|
|
3335
|
+
*/
|
|
3336
|
+
this.engine = input(null, /* @ts-ignore */
|
|
3337
|
+
...(ngDevMode ? [{ debugName: "engine" }] : /* istanbul ignore next */ []));
|
|
3338
|
+
/**
|
|
3339
|
+
* Geokoder **tej** mapy. Podany wygrywa nad `KPT_MAP_GEOCODER` z DI — tak samo jak
|
|
3340
|
+
* `[engine]` nad `KPT_MAP_ENGINE` i z tego samego powodu: Service Specific Terms wiążą
|
|
3341
|
+
* geokoder Google z **mapą** Google, a nie z aplikacją. Na stronie z dwoma silnikami
|
|
3342
|
+
* globalny provider nie umiałby tej reguły dotrzymać; wejście umie.
|
|
3343
|
+
*
|
|
3344
|
+
* Trafia i do wyszukiwarki, i do formularza „dodaj lokalizację" — jeden port na oba kierunki.
|
|
3345
|
+
*
|
|
3346
|
+
* @example
|
|
3347
|
+
* <kpt-map [engine]="google" [geocoder]="googleGeocoder" … />
|
|
3348
|
+
*/
|
|
3349
|
+
this.geocoder = input(null, /* @ts-ignore */
|
|
3350
|
+
...(ngDevMode ? [{ debugName: "geocoder" }] : /* istanbul ignore next */ []));
|
|
3351
|
+
this.locationClick = output();
|
|
3352
|
+
this.clusterClick = output();
|
|
3353
|
+
this.locationCreate = output();
|
|
3354
|
+
/** Wybrano podpowiedź wyszukiwarki. Mapa sama przenosi na nią widok. */
|
|
3355
|
+
this.searchSelect = output();
|
|
3356
|
+
this.viewChange = output();
|
|
3357
|
+
this.userPositionChange = output();
|
|
3358
|
+
this.userLocationError = output();
|
|
3359
|
+
this.i18n = inject(KptI18n);
|
|
3360
|
+
this.icons = inject(KptIconRegistry);
|
|
3361
|
+
/** Opcjonalny, bo silnik może przyjść wejściem `[engine]` zamiast z DI. */
|
|
3362
|
+
this.engineFactory = inject(KPT_MAP_ENGINE, { optional: true });
|
|
3363
|
+
this.geolocation = inject(KPT_MAP_GEOLOCATION, { optional: true });
|
|
3364
|
+
/** Opcjonalny — wejście `[geocoder]` i tak ma nad nim pierwszeństwo. */
|
|
3365
|
+
this.injectedGeocoder = inject(KPT_MAP_GEOCODER, { optional: true });
|
|
3366
|
+
this.destroyRef = inject(DestroyRef);
|
|
3367
|
+
this.injector = inject(Injector);
|
|
3368
|
+
/** Rozstrzygnięty port geokodera: wejście przed DI. Szablon podaje go dzieciom. */
|
|
3369
|
+
this.activeGeocoder = computed(() => this.geocoder() ?? this.injectedGeocoder, /* @ts-ignore */
|
|
3370
|
+
...(ngDevMode ? [{ debugName: "activeGeocoder" }] : /* istanbul ignore next */ []));
|
|
3371
|
+
this.canvas = viewChild.required('canvas', /* @ts-ignore */
|
|
3372
|
+
...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
|
|
3373
|
+
this.popupEl = viewChild('popupEl', /* @ts-ignore */
|
|
3374
|
+
...(ngDevMode ? [{ debugName: "popupEl" }] : /* istanbul ignore next */ []));
|
|
3375
|
+
this.popupTemplates = contentChildren(KptMapPopupTemplate, /* @ts-ignore */
|
|
3376
|
+
...(ngDevMode ? [{ debugName: "popupTemplates" }] : /* istanbul ignore next */ []));
|
|
3377
|
+
this.createFormTemplate = contentChild(KptMapCreateFormTemplate, /* @ts-ignore */
|
|
3378
|
+
...(ngDevMode ? [{ debugName: "createFormTemplate" }] : /* istanbul ignore next */ []));
|
|
3379
|
+
this.mapEngine = null;
|
|
3380
|
+
this.engineKind = signal(null, /* @ts-ignore */
|
|
3381
|
+
...(ngDevMode ? [{ debugName: "engineKind" }] : /* istanbul ignore next */ []));
|
|
3382
|
+
this.ready = signal(false, /* @ts-ignore */
|
|
3383
|
+
...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
3384
|
+
/** Zoom trzymamy w sygnale, bo od niego zależy wynik grupowania. */
|
|
3385
|
+
this.zoom = signal(6, /* @ts-ignore */
|
|
3386
|
+
...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
3387
|
+
/** Rośnie przy każdym ruchu mapy — wymusza przeliczenie pozycji popupu. */
|
|
3388
|
+
this.viewportTick = signal(0, /* @ts-ignore */
|
|
3389
|
+
...(ngDevMode ? [{ debugName: "viewportTick" }] : /* istanbul ignore next */ []));
|
|
3390
|
+
this.popup = signal(null, /* @ts-ignore */
|
|
3391
|
+
...(ngDevMode ? [{ debugName: "popup" }] : /* istanbul ignore next */ []));
|
|
3392
|
+
this.contextTarget = signal(null, /* @ts-ignore */
|
|
3393
|
+
...(ngDevMode ? [{ debugName: "contextTarget" }] : /* istanbul ignore next */ []));
|
|
3394
|
+
this.createFor = signal(null, /* @ts-ignore */
|
|
3395
|
+
...(ngDevMode ? [{ debugName: "createFor" }] : /* istanbul ignore next */ []));
|
|
3396
|
+
this.createOpen = signal(false, /* @ts-ignore */
|
|
3397
|
+
...(ngDevMode ? [{ debugName: "createOpen" }] : /* istanbul ignore next */ []));
|
|
3398
|
+
this.userPosition = signal(null, /* @ts-ignore */
|
|
3399
|
+
...(ngDevMode ? [{ debugName: "userPosition" }] : /* istanbul ignore next */ []));
|
|
3400
|
+
/** Ostatni wybrany wynik wyszukiwania — źródło tymczasowej pinezki. */
|
|
3401
|
+
this.searchPin = signal(null, /* @ts-ignore */
|
|
3402
|
+
...(ngDevMode ? [{ debugName: "searchPin" }] : /* istanbul ignore next */ []));
|
|
3403
|
+
this.locationBlocked = signal(false, /* @ts-ignore */
|
|
3404
|
+
...(ngDevMode ? [{ debugName: "locationBlocked" }] : /* istanbul ignore next */ []));
|
|
3405
|
+
this.categoryById = computed(() => new Map(this.categories().map((c) => [c.id, c])), /* @ts-ignore */
|
|
3406
|
+
...(ngDevMode ? [{ debugName: "categoryById" }] : /* istanbul ignore next */ []));
|
|
3407
|
+
/** Lokalizacje po odfiltrowaniu kategorii ukrytych w legendzie. */
|
|
3408
|
+
this.visibleLocations = computed(() => {
|
|
3409
|
+
const hidden = new Set(this.hiddenCategories());
|
|
3410
|
+
return hidden.size === 0
|
|
3411
|
+
? this.locations()
|
|
3412
|
+
: this.locations().filter((l) => !hidden.has(l.categoryId));
|
|
3413
|
+
}, /* @ts-ignore */
|
|
3414
|
+
...(ngDevMode ? [{ debugName: "visibleLocations" }] : /* istanbul ignore next */ []));
|
|
3415
|
+
this.counts = computed(() => this.legendCounts() ? countByCategory(this.locations()) : null, /* @ts-ignore */
|
|
3416
|
+
...(ngDevMode ? [{ debugName: "counts" }] : /* istanbul ignore next */ []));
|
|
3417
|
+
/** Wynik grupowania dla aktualnego zoomu — grupy i punkty pojedyncze. */
|
|
3418
|
+
this.entries = computed(() => {
|
|
3419
|
+
const locations = this.visibleLocations();
|
|
3420
|
+
if (!this.cluster())
|
|
3421
|
+
return [...locations];
|
|
3422
|
+
return clusterLocations(locations, {
|
|
3423
|
+
...this.clusterOptions(),
|
|
3424
|
+
zoom: this.zoom(),
|
|
3425
|
+
categoryOrder: this.categories().map((c) => c.id),
|
|
3426
|
+
});
|
|
3427
|
+
}, /* @ts-ignore */
|
|
3428
|
+
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
3429
|
+
/** Znaczniki gotowe dla silnika. HTML powstaje w rdzeniu, poza Angularem. */
|
|
3430
|
+
this.markers = computed(() => {
|
|
3431
|
+
const byId = this.categoryById();
|
|
3432
|
+
const selectedId = this.popup()?.location.id ?? null;
|
|
3433
|
+
const colorMode = this.clusterColorBy();
|
|
3434
|
+
const rendered = this.entries().map((entry) => {
|
|
3435
|
+
if (isCluster(entry)) {
|
|
3436
|
+
const category = colorMode === 'dominant' ? byId.get(entry.dominantCategoryId) : undefined;
|
|
3437
|
+
return {
|
|
3438
|
+
id: entry.id,
|
|
3439
|
+
at: entry.at,
|
|
3440
|
+
html: renderClusterHtml({
|
|
3441
|
+
count: entry.count,
|
|
3442
|
+
color: category?.color ?? 'primary',
|
|
3443
|
+
label: this.i18n.t('map.cluster', { count: entry.count }),
|
|
3444
|
+
}),
|
|
3445
|
+
size: CLUSTER_SIZE,
|
|
3446
|
+
anchor: { x: CLUSTER_SIZE.width / 2, y: CLUSTER_SIZE.height / 2 },
|
|
3447
|
+
// Grupy nad pojedynczymi punktami — licznik ma być czytelny.
|
|
3448
|
+
zIndex: 500,
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3451
|
+
const category = byId.get(entry.categoryId);
|
|
3452
|
+
const shape = category?.shape ?? 'pin';
|
|
3453
|
+
const size = shape === 'pin' ? MARKER_SIZE : DOT_SIZE;
|
|
3454
|
+
return {
|
|
3455
|
+
id: entry.id,
|
|
3456
|
+
at: entry.at,
|
|
3457
|
+
html: renderMarkerHtml({
|
|
3458
|
+
shape,
|
|
3459
|
+
color: category?.color ?? 'muted',
|
|
3460
|
+
iconSvgInner: category?.icon ? this.icons.get(category.icon) : undefined,
|
|
3461
|
+
label: entry.title,
|
|
3462
|
+
selected: entry.id === selectedId,
|
|
3463
|
+
}),
|
|
3464
|
+
size,
|
|
3465
|
+
// Pinezka kotwiczy się czubkiem, kropka i kwadrat środkiem.
|
|
3466
|
+
anchor: shape === 'pin' ? { x: size.width / 2, y: size.height } : { x: size.width / 2, y: size.height / 2 },
|
|
3467
|
+
zIndex: entry.id === selectedId ? 400 : 0,
|
|
3468
|
+
};
|
|
3469
|
+
});
|
|
3470
|
+
// Pinezka wyniku wyszukiwania idzie tą samą drogą co reszta znaczników, więc przesuwa
|
|
3471
|
+
// się z mapą klatka po klatce i nie wymaga niczego od silnika.
|
|
3472
|
+
const pin = this.searchMarker() ? this.searchPin() : null;
|
|
3473
|
+
if (pin) {
|
|
3474
|
+
rendered.push({
|
|
3475
|
+
id: SEARCH_MARKER_ID,
|
|
3476
|
+
at: pin.at,
|
|
3477
|
+
html: renderMarkerHtml({
|
|
3478
|
+
shape: 'pin',
|
|
3479
|
+
color: 'info',
|
|
3480
|
+
iconSvgInner: this.icons.get('map-pin'),
|
|
3481
|
+
label: pin.label,
|
|
3482
|
+
}),
|
|
3483
|
+
size: MARKER_SIZE,
|
|
3484
|
+
anchor: { x: MARKER_SIZE.width / 2, y: MARKER_SIZE.height },
|
|
3485
|
+
// Nad grupami (500) — to na nią użytkownik właśnie kazał mapie polecieć.
|
|
3486
|
+
zIndex: 600,
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
return rendered;
|
|
3490
|
+
}, /* @ts-ignore */
|
|
3491
|
+
...(ngDevMode ? [{ debugName: "markers" }] : /* istanbul ignore next */ []));
|
|
3492
|
+
/** Szablon popupu dla kategorii; bez dopasowania — szablon zapasowy (bez `category`). */
|
|
3493
|
+
this.popupTemplateFor = computed(() => {
|
|
3494
|
+
const exact = new Map();
|
|
3495
|
+
let fallback = null;
|
|
3496
|
+
for (const tpl of this.popupTemplates()) {
|
|
3497
|
+
if (tpl.category())
|
|
3498
|
+
exact.set(tpl.category(), tpl);
|
|
3499
|
+
else
|
|
3500
|
+
fallback ??= tpl;
|
|
3501
|
+
}
|
|
3502
|
+
return (categoryId) => exact.get(categoryId) ?? fallback;
|
|
3503
|
+
}, /* @ts-ignore */
|
|
3504
|
+
...(ngDevMode ? [{ debugName: "popupTemplateFor" }] : /* istanbul ignore next */ []));
|
|
3505
|
+
this.popupTemplate = computed(() => {
|
|
3506
|
+
const state = this.popup();
|
|
3507
|
+
return state ? this.popupTemplateFor()(state.location.categoryId) : null;
|
|
3508
|
+
}, /* @ts-ignore */
|
|
3509
|
+
...(ngDevMode ? [{ debugName: "popupTemplate" }] : /* istanbul ignore next */ []));
|
|
3510
|
+
this.popupCategory = computed(() => {
|
|
3511
|
+
const state = this.popup();
|
|
3512
|
+
return state ? this.categoryById().get(state.location.categoryId) : undefined;
|
|
3513
|
+
}, /* @ts-ignore */
|
|
3514
|
+
...(ngDevMode ? [{ debugName: "popupCategory" }] : /* istanbul ignore next */ []));
|
|
3515
|
+
/**
|
|
3516
|
+
* Odległość otwartego punktu od `distanceFrom` (albo od użytkownika) w metrach.
|
|
3517
|
+
* `null`, gdy nie ma od czego liczyć — szablon chowa wtedy wiersz dystansu,
|
|
3518
|
+
* zamiast pokazywać zero.
|
|
3519
|
+
*/
|
|
3520
|
+
this.popupDistance = computed(() => {
|
|
3521
|
+
const state = this.popup();
|
|
3522
|
+
if (!state)
|
|
3523
|
+
return null;
|
|
3524
|
+
const origin = this.distanceFrom() ?? this.userPosition()?.at ?? null;
|
|
3525
|
+
return origin ? haversine(origin, state.location.at) : null;
|
|
3526
|
+
}, /* @ts-ignore */
|
|
3527
|
+
...(ngDevMode ? [{ debugName: "popupDistance" }] : /* istanbul ignore next */ []));
|
|
3528
|
+
/**
|
|
3529
|
+
* Pozycja popupu w pikselach kontenera. Zależy od `viewportTick`, więc przelicza się
|
|
3530
|
+
* przy każdym ruchu mapy — jeden sygnał zamiast nasłuchu w szablonie.
|
|
3531
|
+
*/
|
|
3532
|
+
this.popupPosition = computed(() => {
|
|
3533
|
+
this.viewportTick();
|
|
3534
|
+
const state = this.popup();
|
|
3535
|
+
if (!state || !this.mapEngine || !this.ready())
|
|
3536
|
+
return null;
|
|
3537
|
+
return this.mapEngine.project(state.location.at);
|
|
3538
|
+
}, /* @ts-ignore */
|
|
3539
|
+
...(ngDevMode ? [{ debugName: "popupPosition" }] : /* istanbul ignore next */ []));
|
|
3540
|
+
this.contextPosition = computed(() => {
|
|
3541
|
+
this.viewportTick();
|
|
3542
|
+
const target = this.contextTarget();
|
|
3543
|
+
if (!target || !this.mapEngine || !this.ready())
|
|
3544
|
+
return null;
|
|
3545
|
+
return this.mapEngine.project(target.at);
|
|
3546
|
+
}, /* @ts-ignore */
|
|
3547
|
+
...(ngDevMode ? [{ debugName: "contextPosition" }] : /* istanbul ignore next */ []));
|
|
3548
|
+
/** Kategorie oferowane w menu „dodaj" — ukryte w legendzie odpadają. */
|
|
3549
|
+
this.creatableCategories = computed(() => {
|
|
3550
|
+
const hidden = new Set(this.hiddenCategories());
|
|
3551
|
+
return this.categories().filter((c) => !hidden.has(c.id) && (c.fields?.length || this.createFormTemplate()));
|
|
3552
|
+
}, /* @ts-ignore */
|
|
3553
|
+
...(ngDevMode ? [{ debugName: "creatableCategories" }] : /* istanbul ignore next */ []));
|
|
3554
|
+
/** Czy jest czym szukać — port bez `search()` nie zasługuje na pole, które nic nie robi. */
|
|
3555
|
+
this.searchEnabled = computed(() => this.search() && !!this.activeGeocoder()?.search, /* @ts-ignore */
|
|
3556
|
+
...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
|
|
3557
|
+
/**
|
|
3558
|
+
* Prostokąt preferowanych wyników. Bierzemy zasięg jawny, a bez niego preset kraju —
|
|
3559
|
+
* „Kwiatowa" ma trafiać w tę pod nosem, nie w tę z drugiego końca świata. To sam bias:
|
|
3560
|
+
* adresu z zagranicy dalej da się poszukać.
|
|
3561
|
+
*/
|
|
3562
|
+
this.searchViewbox = computed(() => this.bounds() ?? resolveCountryView(this.country()).bounds, /* @ts-ignore */
|
|
3563
|
+
...(ngDevMode ? [{ debugName: "searchViewbox" }] : /* istanbul ignore next */ []));
|
|
3564
|
+
/**
|
|
3565
|
+
* Twarde zawężenie do krajów. Jawne `searchOptions.countryCodes` wygrywa; bez niego
|
|
3566
|
+
* odcinamy zagranicę dopiero przy `restrictToCountry`, bo tam mapa i tak nie pozwoli
|
|
3567
|
+
* przesunąć widoku poza kraj — znaleziony adres byłby nie do pokazania.
|
|
3568
|
+
*/
|
|
3569
|
+
this.searchCountryCodes = computed(() => {
|
|
3570
|
+
const explicit = this.searchOptions().countryCodes;
|
|
3571
|
+
if (explicit)
|
|
3572
|
+
return explicit;
|
|
3573
|
+
return this.restrictToCountry() ? [this.country()] : null;
|
|
3574
|
+
}, /* @ts-ignore */
|
|
3575
|
+
...(ngDevMode ? [{ debugName: "searchCountryCodes" }] : /* istanbul ignore next */ []));
|
|
3576
|
+
this.createFormApi = computed(() => {
|
|
3577
|
+
const target = this.createFor();
|
|
3578
|
+
if (!target)
|
|
3579
|
+
return null;
|
|
3580
|
+
return {
|
|
3581
|
+
at: target.at,
|
|
3582
|
+
category: target.category,
|
|
3583
|
+
close: () => this.closeCreate(),
|
|
3584
|
+
submit: (value) => this.emitCreate(value),
|
|
3585
|
+
};
|
|
3586
|
+
}, /* @ts-ignore */
|
|
3587
|
+
...(ngDevMode ? [{ debugName: "createFormApi" }] : /* istanbul ignore next */ []));
|
|
3588
|
+
/** Gotowa referencja dla kontekstu szablonu — szablon nie ma jak zrobić `bind`. */
|
|
3589
|
+
this.closePopupFn = () => this.closePopup();
|
|
3590
|
+
/** Zasięg wszystkich widocznych lokalizacji — `null`, gdy nie ma żadnej. */
|
|
3591
|
+
this.dataBounds = computed(() => boundsOf(this.visibleLocations().map((l) => l.at)), /* @ts-ignore */
|
|
3592
|
+
...(ngDevMode ? [{ debugName: "dataBounds" }] : /* istanbul ignore next */ []));
|
|
3593
|
+
afterNextRender(() => void this.initEngine());
|
|
3594
|
+
this.destroyRef.onDestroy(() => this.mapEngine?.destroy());
|
|
3595
|
+
// Zmiana źródła kafli w locie — bez przeładowania mapy.
|
|
3596
|
+
effect(() => {
|
|
3597
|
+
const url = this.tileUrl() ?? KPT_MAP_DEFAULT_TILE_URL;
|
|
3598
|
+
const attribution = this.attribution() ?? KPT_MAP_DEFAULT_ATTRIBUTION;
|
|
3599
|
+
if (untracked(this.ready))
|
|
3600
|
+
this.mapEngine?.setTileSource(url, attribution);
|
|
3601
|
+
});
|
|
3602
|
+
// Wypychanie znaczników do silnika — jedyne miejsce, które je rysuje.
|
|
3603
|
+
effect(() => {
|
|
3604
|
+
const markers = this.markers();
|
|
3605
|
+
if (this.ready())
|
|
3606
|
+
this.mapEngine?.setMarkers(markers);
|
|
3607
|
+
});
|
|
3608
|
+
effect(() => {
|
|
3609
|
+
const bounds = this.restrictBounds();
|
|
3610
|
+
if (untracked(this.ready))
|
|
3611
|
+
this.mapEngine?.setMaxBounds(bounds);
|
|
3612
|
+
});
|
|
3613
|
+
effect(() => {
|
|
3614
|
+
const position = this.userPosition();
|
|
3615
|
+
if (!this.ready())
|
|
3616
|
+
return;
|
|
3617
|
+
this.mapEngine?.setUserPosition(position, renderUserMarkerHtml({
|
|
3618
|
+
label: this.i18n.t('map.myLocation'),
|
|
3619
|
+
iconSvgInner: this.icons.get('user'),
|
|
3620
|
+
}));
|
|
3621
|
+
});
|
|
3622
|
+
// `<dialog>` zamyka się też Esc-em i backdropem — cel czyścimy przy każdym zamknięciu.
|
|
3623
|
+
effect(() => {
|
|
3624
|
+
if (!this.createOpen())
|
|
3625
|
+
untracked(() => this.createFor.set(null));
|
|
3626
|
+
});
|
|
3627
|
+
this.watchGeolocation();
|
|
3628
|
+
}
|
|
3629
|
+
restrictBounds() {
|
|
3630
|
+
if (!this.restrictToCountry())
|
|
3631
|
+
return null;
|
|
3632
|
+
return this.bounds() ?? resolveCountryView(this.country()).bounds;
|
|
3633
|
+
}
|
|
3634
|
+
async initEngine() {
|
|
3635
|
+
// Wejście wygrywa nad DI: dwie mapy o różnych silnikach na jednej stronie.
|
|
3636
|
+
const factory = this.engine() ?? this.engineFactory;
|
|
3637
|
+
if (!factory)
|
|
3638
|
+
throw new Error(NO_ENGINE_ERROR);
|
|
3639
|
+
const engine = (this.mapEngine = factory());
|
|
3640
|
+
this.engineKind.set(engine.kind);
|
|
3641
|
+
const preset = resolveCountryView(this.country());
|
|
3642
|
+
const view = this.view();
|
|
3643
|
+
const startBounds = this.bounds();
|
|
3644
|
+
try {
|
|
3645
|
+
await engine.init(this.canvas().nativeElement, {
|
|
3646
|
+
center: view?.center ?? preset.center,
|
|
3647
|
+
zoom: view?.zoom ?? preset.zoom,
|
|
3648
|
+
maxBounds: this.restrictBounds(),
|
|
3649
|
+
tileUrl: this.tileUrl() ?? KPT_MAP_DEFAULT_TILE_URL,
|
|
3650
|
+
attribution: this.attribution() ?? KPT_MAP_DEFAULT_ATTRIBUTION,
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
catch (error) {
|
|
3654
|
+
// Brak paczki `leaflet` to najczęstsza przyczyna — komunikat niesie instrukcję.
|
|
3655
|
+
this.mapEngine = null;
|
|
3656
|
+
throw error;
|
|
3657
|
+
}
|
|
3658
|
+
// Jawny zasięg wygrywa nad środkiem i zoomem: dopiero teraz znamy rozmiar kontenera.
|
|
3659
|
+
if (startBounds && !view)
|
|
3660
|
+
engine.fitBounds(startBounds, FIT_PADDING);
|
|
3661
|
+
// Leaflet emituje `move` co klatkę przeciągania. Bez popupu i menu nikt tej pozycji
|
|
3662
|
+
// nie czyta, a każdy zapis sygnału planowałby przebieg detekcji zmian — stąd bramka.
|
|
3663
|
+
engine.on('move', () => {
|
|
3664
|
+
if (untracked(this.popup) || untracked(this.contextTarget)) {
|
|
3665
|
+
this.viewportTick.update((t) => t + 1);
|
|
3666
|
+
}
|
|
3667
|
+
});
|
|
3668
|
+
engine.on('moveend', () => this.emitViewChange());
|
|
3669
|
+
engine.on('zoomend', () => this.zoom.set(engine.getZoom()));
|
|
3670
|
+
engine.on('click', () => this.onMapClick());
|
|
3671
|
+
engine.on('contextmenu', (event) => this.onContextMenu(event));
|
|
3672
|
+
engine.on('markerclick', (id, event) => this.onMarkerClick(id, event));
|
|
3673
|
+
this.observeResize();
|
|
3674
|
+
this.zoom.set(engine.getZoom());
|
|
3675
|
+
this.ready.set(true);
|
|
3676
|
+
this.emitViewChange();
|
|
3677
|
+
}
|
|
3678
|
+
/** Bez tego Leaflet zostawia szare pola po każdej zmianie układu strony. */
|
|
3679
|
+
observeResize() {
|
|
3680
|
+
if (typeof ResizeObserver === 'undefined')
|
|
3681
|
+
return;
|
|
3682
|
+
const observer = new ResizeObserver(() => this.mapEngine?.invalidateSize());
|
|
3683
|
+
observer.observe(this.canvas().nativeElement);
|
|
3684
|
+
this.destroyRef.onDestroy(() => observer.disconnect());
|
|
3685
|
+
}
|
|
3686
|
+
watchGeolocation() {
|
|
3687
|
+
effect((onCleanup) => {
|
|
3688
|
+
if (!this.showUserLocation() || !this.geolocation) {
|
|
3689
|
+
this.userPosition.set(null);
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
this.locationBlocked.set(false);
|
|
3693
|
+
const subscription = this.geolocation.watch().subscribe({
|
|
3694
|
+
next: (position) => {
|
|
3695
|
+
this.userPosition.set(position);
|
|
3696
|
+
this.userPositionChange.emit(position);
|
|
3697
|
+
if (position && untracked(this.followUser)) {
|
|
3698
|
+
this.mapEngine?.setView(position.at, Math.max(untracked(this.zoom), 13));
|
|
3699
|
+
}
|
|
3700
|
+
},
|
|
3701
|
+
error: (error) => {
|
|
3702
|
+
// Odmowa zgody wyłącza kontrolkę, ale nie wywraca komponentu.
|
|
3703
|
+
this.userPosition.set(null);
|
|
3704
|
+
this.locationBlocked.set(true);
|
|
3705
|
+
if (error instanceof KptMapGeolocationFailure)
|
|
3706
|
+
this.userLocationError.emit(error.detail);
|
|
3707
|
+
},
|
|
3708
|
+
});
|
|
3709
|
+
onCleanup(() => subscription.unsubscribe());
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3712
|
+
emitViewChange() {
|
|
3713
|
+
if (!this.mapEngine)
|
|
3714
|
+
return;
|
|
3715
|
+
this.zoom.set(this.mapEngine.getZoom());
|
|
3716
|
+
this.viewChange.emit({
|
|
3717
|
+
center: this.mapEngine.getCenter(),
|
|
3718
|
+
zoom: this.mapEngine.getZoom(),
|
|
3719
|
+
bounds: this.mapEngine.getBounds(),
|
|
3720
|
+
});
|
|
3721
|
+
}
|
|
3722
|
+
onMarkerClick(id, _event) {
|
|
3723
|
+
this.contextTarget.set(null);
|
|
3724
|
+
const entry = this.entries().find((e) => e.id === id);
|
|
3725
|
+
if (!entry)
|
|
3726
|
+
return;
|
|
3727
|
+
if (isCluster(entry)) {
|
|
3728
|
+
this.clusterClick.emit(entry);
|
|
3729
|
+
// Grupa punktów o tej samej współrzędnej nie da się rozbić zoomem — pokaż listę.
|
|
3730
|
+
if (isSplittable(entry)) {
|
|
3731
|
+
this.mapEngine?.fitBounds(entry.bounds, FIT_PADDING);
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
this.popup.set({ location: entry.items[0], group: entry });
|
|
3735
|
+
this.autoPanToPopup();
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
this.popup.set({ location: entry, group: null });
|
|
3739
|
+
this.autoPanToPopup();
|
|
3740
|
+
this.locationClick.emit(entry);
|
|
3741
|
+
}
|
|
3742
|
+
onContextMenu(event) {
|
|
3743
|
+
if (!this.allowCreate())
|
|
3744
|
+
return;
|
|
3745
|
+
this.popup.set(null);
|
|
3746
|
+
this.contextTarget.set({ at: event.at, point: event.point });
|
|
3747
|
+
}
|
|
3748
|
+
dismissOverlays() {
|
|
3749
|
+
this.popup.set(null);
|
|
3750
|
+
this.contextTarget.set(null);
|
|
3751
|
+
}
|
|
3752
|
+
/** Kliknięcie w mapę zamyka nakładki i zdejmuje pinezkę wyniku wyszukiwania. */
|
|
3753
|
+
onMapClick() {
|
|
3754
|
+
this.dismissOverlays();
|
|
3755
|
+
this.searchPin.set(null);
|
|
3756
|
+
}
|
|
3757
|
+
clearSearchPin() {
|
|
3758
|
+
this.searchPin.set(null);
|
|
3759
|
+
}
|
|
3760
|
+
closePopup() {
|
|
3761
|
+
this.popup.set(null);
|
|
3762
|
+
}
|
|
3763
|
+
/** Zapas na strzałkę i cień popupu przy sprawdzaniu, czy mieści się w kontenerze. */
|
|
3764
|
+
static { this.POPUP_MARGIN = 16; }
|
|
3765
|
+
/**
|
|
3766
|
+
* Przesuwa mapę tak, żeby świeżo otwarty popup zmieścił się w kontenerze.
|
|
3767
|
+
*
|
|
3768
|
+
* Wymiarów popupu nie znamy przed renderem, więc mierzymy go po dograniu ramki.
|
|
3769
|
+
* Bez tego znacznik przy górnej krawędzi otwiera popup poza widokiem.
|
|
3770
|
+
*/
|
|
3771
|
+
autoPanToPopup() {
|
|
3772
|
+
const engine = this.mapEngine;
|
|
3773
|
+
if (!engine)
|
|
3774
|
+
return;
|
|
3775
|
+
afterNextRender(() => {
|
|
3776
|
+
const el = this.popupEl()?.nativeElement;
|
|
3777
|
+
const state = this.popup();
|
|
3778
|
+
if (!el || !state)
|
|
3779
|
+
return;
|
|
3780
|
+
const anchor = engine.project(state.location.at);
|
|
3781
|
+
const size = engine.getSize();
|
|
3782
|
+
const margin = KptMap.POPUP_MARGIN;
|
|
3783
|
+
const rect = el.getBoundingClientRect();
|
|
3784
|
+
// Popup kotwiczy się nad znacznikiem i jest wyśrodkowany w poziomie.
|
|
3785
|
+
const top = anchor.y - rect.height - margin;
|
|
3786
|
+
const left = anchor.x - rect.width / 2;
|
|
3787
|
+
const right = anchor.x + rect.width / 2;
|
|
3788
|
+
let dx = 0;
|
|
3789
|
+
let dy = 0;
|
|
3790
|
+
if (top < margin)
|
|
3791
|
+
dy = top - margin;
|
|
3792
|
+
if (left < margin)
|
|
3793
|
+
dx = left - margin;
|
|
3794
|
+
else if (right > size.width - margin)
|
|
3795
|
+
dx = right - (size.width - margin);
|
|
3796
|
+
if (dx !== 0 || dy !== 0)
|
|
3797
|
+
engine.panBy({ x: dx, y: dy });
|
|
3798
|
+
}, { injector: this.injector });
|
|
3799
|
+
}
|
|
3800
|
+
/** Pozycja z grupy nierozdzielalnej — przełącza popup bez ruszania mapy. */
|
|
3801
|
+
selectFromGroup(location) {
|
|
3802
|
+
const state = this.popup();
|
|
3803
|
+
this.popup.set({ location, group: state?.group ?? null });
|
|
3804
|
+
this.locationClick.emit(location);
|
|
3805
|
+
}
|
|
3806
|
+
startCreate(category) {
|
|
3807
|
+
const target = this.contextTarget();
|
|
3808
|
+
if (!target)
|
|
3809
|
+
return;
|
|
3810
|
+
this.contextTarget.set(null);
|
|
3811
|
+
this.createFor.set({ at: target.at, category });
|
|
3812
|
+
this.createOpen.set(true);
|
|
3813
|
+
}
|
|
3814
|
+
closeCreate() {
|
|
3815
|
+
this.createOpen.set(false);
|
|
3816
|
+
this.createFor.set(null);
|
|
3817
|
+
}
|
|
3818
|
+
emitCreate(value) {
|
|
3819
|
+
const target = this.createFor();
|
|
3820
|
+
if (!target)
|
|
3821
|
+
return;
|
|
3822
|
+
// Komponent nie dopisuje nic do `locations` — źródłem prawdy jest aplikacja.
|
|
3823
|
+
this.locationCreate.emit({ at: target.at, categoryId: target.category.id, value });
|
|
3824
|
+
this.closeCreate();
|
|
3825
|
+
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Przenosi widok na wybrany wynik. Zasięg (miasto, region) wpasowujemy w kadr,
|
|
3828
|
+
* a pojedynczy punkt dostaje `[searchZoom]` — inaczej każdy adres lądowałby
|
|
3829
|
+
* w innym przybliżeniu, zależnym od tego, jak dostawca obrysował budynek.
|
|
3830
|
+
*/
|
|
3831
|
+
onSearchSelect(result) {
|
|
3832
|
+
this.dismissOverlays();
|
|
3833
|
+
this.searchPin.set(result);
|
|
3834
|
+
if (result.bounds)
|
|
3835
|
+
this.mapEngine?.fitBounds(result.bounds, FIT_PADDING);
|
|
3836
|
+
else
|
|
3837
|
+
this.mapEngine?.setView(result.at, this.searchZoom());
|
|
3838
|
+
this.searchSelect.emit(result);
|
|
3839
|
+
}
|
|
3840
|
+
zoomBy(delta) {
|
|
3841
|
+
if (!this.mapEngine)
|
|
3842
|
+
return;
|
|
3843
|
+
this.mapEngine.setView(this.mapEngine.getCenter(), this.mapEngine.getZoom() + delta);
|
|
3844
|
+
}
|
|
3845
|
+
centreOnUser() {
|
|
3846
|
+
const position = this.userPosition();
|
|
3847
|
+
if (!position || !this.mapEngine)
|
|
3848
|
+
return;
|
|
3849
|
+
this.mapEngine.setView(position.at, Math.max(this.mapEngine.getZoom(), 13));
|
|
3850
|
+
}
|
|
3851
|
+
/** Wraca do widoku startowego — po zabłądzeniu na drugim końcu świata. */
|
|
3852
|
+
resetView() {
|
|
3853
|
+
if (!this.mapEngine)
|
|
3854
|
+
return;
|
|
3855
|
+
const explicit = this.bounds();
|
|
3856
|
+
if (explicit) {
|
|
3857
|
+
this.mapEngine.fitBounds(explicit, FIT_PADDING);
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3860
|
+
const view = this.view();
|
|
3861
|
+
const preset = resolveCountryView(this.country());
|
|
3862
|
+
this.mapEngine.setView(view?.center ?? preset.center, view?.zoom ?? preset.zoom);
|
|
3863
|
+
}
|
|
3864
|
+
fitToData() {
|
|
3865
|
+
const bounds = this.dataBounds();
|
|
3866
|
+
if (bounds)
|
|
3867
|
+
this.mapEngine?.fitBounds(bounds, FIT_PADDING);
|
|
3868
|
+
}
|
|
3869
|
+
static { this.ɵfac = function KptMap_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMap)(); }; }
|
|
3870
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: KptMap, selectors: [["kpt-map"]], contentQueries: function KptMap_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) {
|
|
3871
|
+
i0.ɵɵcontentQuerySignal(dirIndex, ctx.popupTemplates, KptMapPopupTemplate, 4)(dirIndex, ctx.createFormTemplate, KptMapCreateFormTemplate, 5);
|
|
3872
|
+
} if (rf & 2) {
|
|
3873
|
+
i0.ɵɵqueryAdvance(2);
|
|
3874
|
+
} }, viewQuery: function KptMap_Query(rf, ctx) { if (rf & 1) {
|
|
3875
|
+
i0.ɵɵviewQuerySignal(ctx.canvas, _c0$1, 5)(ctx.popupEl, _c1, 5);
|
|
3876
|
+
} if (rf & 2) {
|
|
3877
|
+
i0.ɵɵqueryAdvance(2);
|
|
3878
|
+
} }, hostAttrs: [1, "kpt-map"], hostVars: 3, hostBindings: function KptMap_HostBindings(rf, ctx) { if (rf & 2) {
|
|
3879
|
+
i0.ɵɵattribute("data-engine", ctx.engineKind());
|
|
3880
|
+
i0.ɵɵstyleProp("--%NS%kpt-map-height", ctx.height());
|
|
3881
|
+
} }, inputs: { locations: [1, "locations"], categories: [1, "categories"], country: [1, "country"], view: [1, "view"], restrictToCountry: [1, "restrictToCountry"], bounds: [1, "bounds"], cluster: [1, "cluster"], clusterOptions: [1, "clusterOptions"], clusterColorBy: [1, "clusterColorBy"], legend: [1, "legend"], hiddenCategories: [1, "hiddenCategories"], legendCounts: [1, "legendCounts"], showUserLocation: [1, "showUserLocation"], followUser: [1, "followUser"], allowCreate: [1, "allowCreate"], search: [1, "search"], searchPlaceholder: [1, "searchPlaceholder"], searchOptions: [1, "searchOptions"], searchZoom: [1, "searchZoom"], searchMarker: [1, "searchMarker"], popupLayout: [1, "popupLayout"], distanceFrom: [1, "distanceFrom"], height: [1, "height"], tileUrl: [1, "tileUrl"], attribution: [1, "attribution"], engine: [1, "engine"], geocoder: [1, "geocoder"] }, outputs: { hiddenCategories: "hiddenCategoriesChange", locationClick: "locationClick", clusterClick: "clusterClick", locationCreate: "locationCreate", searchSelect: "searchSelect", viewChange: "viewChange", userPositionChange: "userPositionChange", userLocationError: "userLocationError" }, decls: 17, vars: 15, consts: [["canvas", ""], ["popupEl", ""], [1, "kpt-map__viewport"], [1, "kpt-map__canvas"], ["role", "group", 1, "kpt-map__controls"], ["type", "button", 1, "kpt-map__control", 3, "click", "title", "disabled"], ["aria-hidden", "true"], ["type", "button", 1, "kpt-map__control", "kpt-map__control--locate", 3, "title", "disabled"], [1, "kpt-map__search"], [1, "kpt-map__legend", 3, "categories", "hidden", "counts"], [3, "openChange", "open", "header"], ["type", "button", 1, "kpt-map__control", "kpt-map__control--locate", 3, "click", "title", "disabled"], ["aria-hidden", "true", 1, "kpt-map__crosshair"], [3, "selected", "cleared", "geocoder", "placeholder", "minLength", "debounce", "limit", "viewbox", "countryCodes"], ["role", "menu", 1, "kpt-map__context", 3, "--%NS%_kpt-map-x", "--%NS%_kpt-map-y"], ["role", "menu", 1, "kpt-map__context"], [1, "kpt-map__context-title"], ["type", "button", "role", "menuitem", 1, "kpt-map__context-item"], [1, "kpt-map__context-empty"], ["type", "button", "role", "menuitem", 1, "kpt-map__context-item", 3, "click"], ["aria-hidden", "true", 1, "kpt-map__context-dot"], ["role", "dialog", 1, "kpt-map__popup", 3, "--%NS%_kpt-map-x", "--%NS%_kpt-map-y"], ["role", "dialog", 1, "kpt-map__popup"], ["type", "button", 1, "kpt-map__popup-close"], [1, "kpt-map__popup-body"], [3, "ngTemplateOutlet", "ngTemplateOutletContext"], ["type", "button", 1, "kpt-map__popup-close", 3, "click"], [1, "kpt-map__popup-group"], ["role", "tablist", 1, "kpt-map__popup-tabs"], ["type", "button", "role", "tab", 1, "kpt-map__popup-tab"], ["type", "button", "role", "tab", 1, "kpt-map__popup-tab", 3, "click"], [1, "kpt-map__popup-title"], [1, "kpt-map__popup-category"], [1, "kpt-map__legend", 3, "hiddenChange", "categories", "hidden", "counts"], [3, "geocoder", "category", "at"], [3, "save", "cancelled", "geocoder", "category", "at"]], template: function KptMap_Template(rf, ctx) { if (rf & 1) {
|
|
3882
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
3883
|
+
i0.ɵɵelementStart(0, "div", 2);
|
|
3884
|
+
i0.ɵɵelement(1, "div", 3, 0);
|
|
3885
|
+
i0.ɵɵelementStart(3, "div", 4)(4, "button", 5);
|
|
3886
|
+
i0.ɵɵlistener("click", function KptMap_Template_button_click_4_listener() { return ctx.zoomBy(1); });
|
|
3887
|
+
i0.ɵɵelementStart(5, "span", 6);
|
|
3888
|
+
i0.ɵɵtext(6, "+");
|
|
3889
|
+
i0.ɵɵelementEnd()();
|
|
3890
|
+
i0.ɵɵelementStart(7, "button", 5);
|
|
3891
|
+
i0.ɵɵlistener("click", function KptMap_Template_button_click_7_listener() { return ctx.zoomBy(-1); });
|
|
3892
|
+
i0.ɵɵelementStart(8, "span", 6);
|
|
3893
|
+
i0.ɵɵtext(9, "\u2212");
|
|
3894
|
+
i0.ɵɵelementEnd()();
|
|
3895
|
+
i0.ɵɵconditionalCreate(10, KptMap_Conditional_10_Template, 2, 3, "button", 7);
|
|
3896
|
+
i0.ɵɵelementEnd();
|
|
3897
|
+
i0.ɵɵconditionalCreate(11, KptMap_Conditional_11_Template, 2, 7, "div", 8);
|
|
3898
|
+
i0.ɵɵconditionalCreate(12, KptMap_Conditional_12_Template, 1, 1);
|
|
3899
|
+
i0.ɵɵconditionalCreate(13, KptMap_Conditional_13_Template, 1, 1);
|
|
3900
|
+
i0.ɵɵelementEnd();
|
|
3901
|
+
i0.ɵɵconditionalCreate(14, KptMap_Conditional_14_Template, 1, 3, "kpt-map-legend", 9);
|
|
3902
|
+
i0.ɵɵelementStart(15, "kpt-dialog", 10);
|
|
3903
|
+
i0.ɵɵtwoWayListener("openChange", function KptMap_Template_kpt_dialog_openChange_15_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.createOpen, $event) || (ctx.createOpen = $event); return i0.ɵɵresetView($event); });
|
|
3904
|
+
i0.ɵɵconditionalCreate(16, KptMap_Conditional_16_Template, 2, 1);
|
|
3905
|
+
i0.ɵɵelementEnd();
|
|
3906
|
+
} if (rf & 2) {
|
|
3907
|
+
let tmp_11_0;
|
|
3908
|
+
let tmp_15_0;
|
|
3909
|
+
i0.ɵɵadvance(3);
|
|
3910
|
+
i0.ɵɵattribute("aria-label", ctx.i18n.t("map.legend"));
|
|
3911
|
+
i0.ɵɵadvance();
|
|
3912
|
+
i0.ɵɵproperty("title", ctx.i18n.t("map.zoomIn"))("disabled", !ctx.ready());
|
|
3913
|
+
i0.ɵɵattribute("aria-label", ctx.i18n.t("map.zoomIn"));
|
|
3914
|
+
i0.ɵɵadvance(3);
|
|
3915
|
+
i0.ɵɵproperty("title", ctx.i18n.t("map.zoomOut"))("disabled", !ctx.ready());
|
|
3916
|
+
i0.ɵɵattribute("aria-label", ctx.i18n.t("map.zoomOut"));
|
|
3917
|
+
i0.ɵɵadvance(3);
|
|
3918
|
+
i0.ɵɵconditional(ctx.showUserLocation() ? 10 : -1);
|
|
3919
|
+
i0.ɵɵadvance();
|
|
3920
|
+
i0.ɵɵconditional(ctx.searchEnabled() ? 11 : -1);
|
|
3921
|
+
i0.ɵɵadvance();
|
|
3922
|
+
i0.ɵɵconditional(ctx.allowCreate() && ctx.contextTarget() ? 12 : -1);
|
|
3923
|
+
i0.ɵɵadvance();
|
|
3924
|
+
i0.ɵɵconditional((tmp_11_0 = ctx.popup()) ? 13 : -1, tmp_11_0);
|
|
3925
|
+
i0.ɵɵadvance();
|
|
3926
|
+
i0.ɵɵconditional(ctx.legend() ? 14 : -1);
|
|
3927
|
+
i0.ɵɵadvance();
|
|
3928
|
+
i0.ɵɵtwoWayProperty("open", ctx.createOpen);
|
|
3929
|
+
i0.ɵɵproperty("header", ctx.i18n.t("map.addLocation"));
|
|
3930
|
+
i0.ɵɵadvance();
|
|
3931
|
+
i0.ɵɵconditional((tmp_15_0 = ctx.createFor()) ? 16 : -1, tmp_15_0);
|
|
3932
|
+
} }, dependencies: [NgTemplateOutlet, KptDialog, KptMapLegend, KptMapSearch, KptMapCreateDialog], styles: ["@charset \"UTF-8\";@layer kpt.components{.kpt-map{--%NS%kpt-map-height: 480px;display:flex;flex-direction:column;gap:var(--%NS%kpt-space-3);width:100%;isolation:isolate}.kpt-map__viewport{position:relative;height:var(--%NS%kpt-map-height);overflow:hidden;background:var(--%NS%kpt-map-bg, var(--%NS%kpt-color-surface-sunken));border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-map-border, var(--%NS%kpt-color-border));border-radius:var(--%NS%kpt-map-radius, var(--%NS%kpt-radius-lg))}.kpt-map__canvas{width:100%;height:100%}.kpt-map__controls{position:absolute;top:var(--%NS%kpt-space-3);left:var(--%NS%kpt-space-3);z-index:2;display:flex;flex-direction:column;gap:var(--%NS%kpt-space-1)}.kpt-map__control{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;font-size:var(--%NS%kpt-font-size-lg);line-height:1;color:var(--%NS%kpt-color-on-surface);cursor:pointer;background:var(--%NS%kpt-color-surface-raised);border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-md);box-shadow:var(--%NS%kpt-elevation-1)}.kpt-map__control:hover:not(:disabled){background:var(--%NS%kpt-color-surface-hover)}.kpt-map__control:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:2px}.kpt-map__control:disabled{color:var(--%NS%kpt-color-on-surface-muted);cursor:not-allowed;opacity:.6}.kpt-map__search{position:absolute;top:var(--%NS%kpt-space-3);right:var(--%NS%kpt-space-3);z-index:5;max-width:calc(100% - 2rem - var(--%NS%kpt-space-3) * 3)}.kpt-map__crosshair{position:relative;width:.875rem;height:.875rem;border:var(--%NS%kpt-border-width-medium) solid currentcolor;border-radius:var(--%NS%kpt-radius-full)}.kpt-map__crosshair:after{position:absolute;inset:50% auto auto 50%;width:.25rem;height:.25rem;content:\"\";background:currentcolor;border-radius:var(--%NS%kpt-radius-full);translate:-50% -50%}.kpt-map__marker{position:relative;display:block;width:100%;height:100%;color:var(--%NS%_kpt-map-marker-fg, var(--%NS%kpt-color-text-inverse))}.kpt-map__marker-shape{display:block;width:100%;height:100%;fill:var(--%NS%_kpt-map-marker-bg, var(--%NS%kpt-color-muted));stroke:var(--%NS%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(--%NS%_kpt-map-marker-bg, var(--%NS%kpt-color-muted));border:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-surface-raised);box-shadow:var(--%NS%kpt-elevation-1)}.kpt-map__marker--dot{border-radius:var(--%NS%kpt-radius-full)}.kpt-map__marker--square{border-radius:var(--%NS%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(--%NS%kpt-color-focus-ring);stroke-width:2.5}.kpt-map__cluster{display:grid;place-items:center;width:100%;height:100%;font-weight:var(--%NS%kpt-font-weight-semibold);font-variant-numeric:tabular-nums;color:var(--%NS%_kpt-map-cluster-fg, var(--%NS%kpt-color-primary-contrast));background:var(--%NS%_kpt-map-cluster-bg, var(--%NS%kpt-map-cluster-bg, var(--%NS%kpt-color-primary)));border:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-surface-raised);border-radius:var(--%NS%kpt-radius-full);box-shadow:var(--%NS%kpt-elevation-2)}.kpt-map__cluster--sm{font-size:var(--%NS%kpt-font-size-xs);scale:.78}.kpt-map__cluster--md{font-size:var(--%NS%kpt-font-size-sm);scale:.9}.kpt-map__cluster--lg{font-size:var(--%NS%kpt-font-size-sm);scale:1}.kpt-map__user-marker{display:grid;place-items:center;width:100%;height:100%;font-size:.875rem;color:var(--%NS%kpt-color-info-contrast);background:var(--%NS%kpt-color-info);border:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-surface-raised);border-radius:var(--%NS%kpt-radius-full);box-shadow:var(--%NS%kpt-elevation-2)}.kpt-map__user-marker svg{width:1em;height:1em}.kpt-map__popup{position:absolute;top:var(--%NS%_kpt-map-y, 0);left:var(--%NS%_kpt-map-x, 0);z-index:3;max-width:min(var(--%NS%kpt-map-popup-width, 20rem),100% - var(--%NS%kpt-space-6));padding:var(--%NS%kpt-space-4);color:var(--%NS%kpt-color-on-surface);background:var(--%NS%kpt-map-popup-bg, var(--%NS%kpt-color-surface-raised));border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-lg);box-shadow:var(--%NS%kpt-map-popup-shadow, var(--%NS%kpt-elevation-3));translate:-50% calc(-100% - var(--%NS%kpt-space-3))}.kpt-map__popup:after{position:absolute;top:100%;left:50%;width:.75rem;height:.75rem;content:\"\";background:inherit;border-right:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-bottom:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);rotate:45deg;translate:-50% -50%}.kpt-map__popup[data-layout=card]{max-width:min(var(--%NS%kpt-map-popup-width-card, 26rem),100% - var(--%NS%kpt-space-6));width:min(var(--%NS%kpt-map-popup-width-card, 26rem),100% - var(--%NS%kpt-space-6));max-height:min(var(--%NS%kpt-map-popup-max-height, 32rem),100% - var(--%NS%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(--%NS%kpt-space-4)}.kpt-map__popup[data-layout=card] .kpt-map__popup-group{margin-top:var(--%NS%kpt-space-3)}.kpt-map__popup-close{position:absolute;top:var(--%NS%kpt-space-2);right:var(--%NS%kpt-space-2);display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;font-size:var(--%NS%kpt-font-size-lg);line-height:1;color:var(--%NS%kpt-color-on-surface-muted);cursor:pointer;background:transparent;border:none;border-radius:var(--%NS%kpt-radius-full)}.kpt-map__popup-close:hover{background:var(--%NS%kpt-color-surface-hover)}.kpt-map__popup-close:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}.kpt-map__popup-body{padding-right:var(--%NS%kpt-space-5);font-size:var(--%NS%kpt-font-size-sm)}.kpt-map__popup-body p{margin:0 0 var(--%NS%kpt-space-1)}.kpt-map__popup-title{display:block;margin-bottom:var(--%NS%kpt-space-1);font-size:var(--%NS%kpt-font-size-base)}.kpt-map__popup-category{color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map__popup-group{margin:0 var(--%NS%kpt-space-5) var(--%NS%kpt-space-2) 0;font-size:var(--%NS%kpt-font-size-xs);color:var(--%NS%kpt-color-on-surface-muted);text-transform:uppercase}.kpt-map__popup-tabs{display:flex;flex-wrap:wrap;gap:var(--%NS%kpt-space-1);margin-bottom:var(--%NS%kpt-space-2)}.kpt-map__popup-tab{padding:var(--%NS%kpt-space-1) var(--%NS%kpt-space-2);font:inherit;font-size:var(--%NS%kpt-font-size-xs);color:var(--%NS%kpt-color-on-surface);cursor:pointer;background:var(--%NS%kpt-color-surface-variant);border:var(--%NS%kpt-border-width-thin) solid transparent;border-radius:var(--%NS%kpt-radius-sm)}.kpt-map__popup-tab[aria-selected=true]{background:var(--%NS%kpt-color-surface-selected);border-color:var(--%NS%kpt-color-border-strong)}.kpt-map__popup-tab:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:1px}.kpt-map__context{position:absolute;top:var(--%NS%_kpt-map-y, 0);left:var(--%NS%_kpt-map-x, 0);z-index:4;min-width:12rem;padding:var(--%NS%kpt-space-2);background:var(--%NS%kpt-color-surface-raised);border:var(--%NS%kpt-border-width-thin) solid var(--%NS%kpt-color-border);border-radius:var(--%NS%kpt-radius-md);box-shadow:var(--%NS%kpt-elevation-3)}.kpt-map__context-title{margin:0 0 var(--%NS%kpt-space-1);padding:0 var(--%NS%kpt-space-2);font-size:var(--%NS%kpt-font-size-xs);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map__context-item{display:flex;gap:var(--%NS%kpt-space-2);align-items:center;width:100%;padding:var(--%NS%kpt-space-2);font:inherit;font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface);text-align:start;cursor:pointer;background:transparent;border:none;border-radius:var(--%NS%kpt-radius-sm)}.kpt-map__context-item:hover{background:var(--%NS%kpt-color-surface-hover)}.kpt-map__context-item:focus-visible{outline:var(--%NS%kpt-border-width-medium) solid var(--%NS%kpt-color-focus-ring);outline-offset:-2px}.kpt-map__context-empty{margin:0;padding:var(--%NS%kpt-space-2);font-size:var(--%NS%kpt-font-size-sm);color:var(--%NS%kpt-color-on-surface-muted)}.kpt-map__context-dot{width:.5rem;height:.5rem;background:var(--%NS%kpt-color-border-strong);border-radius:var(--%NS%kpt-radius-full)}.kpt-map[data-engine=google] .kpt-map__accuracy{background:color-mix(in oklch,var(--%NS%kpt-color-info) 15%,transparent);border:var(--%NS%kpt-border-width-thin) solid color-mix(in oklch,var(--%NS%kpt-color-info) 50%,transparent)}}.kpt-map .leaflet-container{font-family:var(--%NS%kpt-font-family-sans);font-size:var(--%NS%kpt-font-size-sm);background:var(--%NS%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(--%NS%kpt-space-1);font-size:var(--%NS%kpt-font-size-xs);color:var(--%NS%kpt-color-on-surface-muted);background:color-mix(in oklch,var(--%NS%kpt-color-surface-raised) 85%,transparent)}.kpt-map .leaflet-control-attribution a{color:var(--%NS%kpt-color-primary)}.kpt-map .kpt-map__accuracy{fill:var(--%NS%kpt-color-info);fill-opacity:.15;stroke:var(--%NS%kpt-color-info);stroke-opacity:.5}.kpt-map .leaflet-popup-content-wrapper,.kpt-map .leaflet-popup-tip{display:none}\n"], encapsulation: 2 }); }
|
|
3933
|
+
}
|
|
3934
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMap, [{
|
|
3935
|
+
type: Component,
|
|
3936
|
+
args: [{ selector: 'kpt-map', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, KptDialog, KptMapLegend, KptMapSearch, KptMapCreateDialog], host: {
|
|
3937
|
+
class: 'kpt-map',
|
|
3938
|
+
'[style.--kpt-map-height]': 'height()',
|
|
3939
|
+
'[attr.data-engine]': 'engineKind()',
|
|
3940
|
+
}, 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"] }]
|
|
3941
|
+
}], () => [], { 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 }] }] }); })();
|
|
3942
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMap, { className: "KptMap", filePath: "lib/map.component.ts", lineNumber: 119 }); })();
|
|
3943
|
+
|
|
3944
|
+
/*
|
|
3945
|
+
* Formatowanie liczb dla warstwy prezentacji mapy — pure TS.
|
|
3946
|
+
* Wszystko przez `Intl`, więc separator dziesiętny idzie z locale,
|
|
3947
|
+
* a nie z zaszytej na sztywno kropki.
|
|
3948
|
+
*/
|
|
3949
|
+
/**
|
|
3950
|
+
* Dystans po ludzku: poniżej kilometra w metrach zaokrąglonych do dziesiątek,
|
|
3951
|
+
* wyżej w kilometrach z jednym miejscem po przecinku (`6,2 km` w `pl-PL`).
|
|
3952
|
+
*
|
|
3953
|
+
* Wartość ujemna albo nieskończona daje `null` — wołający chowa wtedy wiersz,
|
|
3954
|
+
* zamiast malować „NaN km".
|
|
3955
|
+
*/
|
|
3956
|
+
function formatDistance(metres, locale) {
|
|
3957
|
+
if (metres == null || !Number.isFinite(metres) || metres < 0)
|
|
3958
|
+
return null;
|
|
3959
|
+
if (metres < 1000) {
|
|
3960
|
+
const rounded = metres < 100 ? Math.round(metres) : Math.round(metres / 10) * 10;
|
|
3961
|
+
return `${new Intl.NumberFormat(locale).format(rounded)} m`;
|
|
3962
|
+
}
|
|
3963
|
+
const km = metres / 1000;
|
|
3964
|
+
// Powyżej 100 km ułamek nie niesie już informacji.
|
|
3965
|
+
const digits = km >= 100 ? 0 : 1;
|
|
3966
|
+
const value = new Intl.NumberFormat(locale, {
|
|
3967
|
+
minimumFractionDigits: digits,
|
|
3968
|
+
maximumFractionDigits: digits,
|
|
3969
|
+
}).format(km);
|
|
3970
|
+
return `${value} km`;
|
|
3971
|
+
}
|
|
3972
|
+
/** Współrzędne do wiersza karty — pięć miejsc po przecinku to ok. metr. */
|
|
3973
|
+
function formatLatLng(at, digits = 5) {
|
|
3974
|
+
return `${at.lat.toFixed(digits)}, ${at.lng.toFixed(digits)}`;
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
const _c0 = () => [];
|
|
3978
|
+
const _forTrack0 = ($index, $item) => $item.label;
|
|
3979
|
+
const _forTrack1 = ($index, $item) => $item.kind;
|
|
3980
|
+
function KptMapPoiCard_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
3981
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
3982
|
+
i0.ɵɵelementStart(0, "kpt-icon-button", 18);
|
|
3983
|
+
i0.ɵɵlistener("click", function KptMapPoiCard_Conditional_3_Template_kpt_icon_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onClose()); });
|
|
3984
|
+
i0.ɵɵelement(1, "kpt-icon", 19);
|
|
3985
|
+
i0.ɵɵelementEnd();
|
|
3986
|
+
} if (rf & 2) {
|
|
3987
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
3988
|
+
i0.ɵɵproperty("ariaLabel", ctx_r1.i18n.t("common.close"));
|
|
3989
|
+
} }
|
|
3990
|
+
function KptMapPoiCard_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
3991
|
+
i0.ɵɵelementStart(0, "strong", 4);
|
|
3992
|
+
i0.ɵɵtext(1);
|
|
3993
|
+
i0.ɵɵelementEnd();
|
|
3994
|
+
} if (rf & 2) {
|
|
3995
|
+
i0.ɵɵadvance();
|
|
3996
|
+
i0.ɵɵtextInterpolate(ctx);
|
|
3997
|
+
} }
|
|
3998
|
+
function KptMapPoiCard_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
3999
|
+
i0.ɵɵelementStart(0, "span", 5);
|
|
4000
|
+
i0.ɵɵtext(1);
|
|
4001
|
+
i0.ɵɵelementEnd();
|
|
4002
|
+
} if (rf & 2) {
|
|
4003
|
+
i0.ɵɵadvance();
|
|
4004
|
+
i0.ɵɵtextInterpolate(ctx);
|
|
4005
|
+
} }
|
|
4006
|
+
function KptMapPoiCard_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
4007
|
+
i0.ɵɵelement(0, "img", 8);
|
|
4008
|
+
} if (rf & 2) {
|
|
4009
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4010
|
+
i0.ɵɵproperty("src", ctx, i0.ɵɵsanitizeUrl)("alt", ctx_r1.poi().photoAlt ?? ctx_r1.poi().title);
|
|
4011
|
+
} }
|
|
4012
|
+
function KptMapPoiCard_Conditional_10_Template(rf, ctx) { if (rf & 1) {
|
|
4013
|
+
i0.ɵɵelementStart(0, "div", 9);
|
|
4014
|
+
i0.ɵɵelement(1, "kpt-icon", 20);
|
|
4015
|
+
i0.ɵɵelementEnd();
|
|
4016
|
+
} if (rf & 2) {
|
|
4017
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4018
|
+
i0.ɵɵattribute("aria-label", ctx_r1.i18n.t("map.poi.noPhoto"));
|
|
4019
|
+
} }
|
|
4020
|
+
function KptMapPoiCard_Conditional_12_Template(rf, ctx) { if (rf & 1) {
|
|
4021
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
4022
|
+
i0.ɵɵelementStart(0, "kpt-button", 21);
|
|
4023
|
+
i0.ɵɵlistener("click", function KptMapPoiCard_Conditional_12_Template_kpt_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onPrimary()); });
|
|
4024
|
+
i0.ɵɵtext(1);
|
|
4025
|
+
i0.ɵɵelementEnd();
|
|
4026
|
+
} if (rf & 2) {
|
|
4027
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4028
|
+
i0.ɵɵadvance();
|
|
4029
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.primaryLabel(), " ");
|
|
4030
|
+
} }
|
|
4031
|
+
function KptMapPoiCard_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
4032
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
4033
|
+
i0.ɵɵelementStart(0, "kpt-icon-button", 22);
|
|
4034
|
+
i0.ɵɵlistener("click", function KptMapPoiCard_Conditional_13_Template_kpt_icon_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.share()); });
|
|
4035
|
+
i0.ɵɵelement(1, "kpt-icon", 23);
|
|
4036
|
+
i0.ɵɵelementEnd();
|
|
4037
|
+
} if (rf & 2) {
|
|
4038
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4039
|
+
i0.ɵɵproperty("ariaLabel", ctx_r1.i18n.t("map.poi.share"));
|
|
4040
|
+
} }
|
|
4041
|
+
function KptMapPoiCard_Conditional_14_Template(rf, ctx) { if (rf & 1) {
|
|
4042
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
4043
|
+
i0.ɵɵelementStart(0, "kpt-icon-button", 24);
|
|
4044
|
+
i0.ɵɵlistener("click", function KptMapPoiCard_Conditional_14_Template_kpt_icon_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.toggleFavourite()); });
|
|
4045
|
+
i0.ɵɵelement(1, "kpt-icon", 25);
|
|
4046
|
+
i0.ɵɵelementEnd();
|
|
4047
|
+
} if (rf & 2) {
|
|
4048
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4049
|
+
i0.ɵɵproperty("ariaLabel", ctx_r1.favouriteLabel());
|
|
4050
|
+
i0.ɵɵattribute("aria-pressed", ctx_r1.favourite())("data-on", ctx_r1.favourite() ? "" : null);
|
|
4051
|
+
} }
|
|
4052
|
+
function KptMapPoiCard_Conditional_15_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
4053
|
+
i0.ɵɵelement(0, "kpt-icon", 27);
|
|
4054
|
+
} if (rf & 2) {
|
|
4055
|
+
const category_r6 = i0.ɵɵnextContext();
|
|
4056
|
+
i0.ɵɵproperty("name", category_r6.icon);
|
|
4057
|
+
} }
|
|
4058
|
+
function KptMapPoiCard_Conditional_15_Template(rf, ctx) { if (rf & 1) {
|
|
4059
|
+
i0.ɵɵelementStart(0, "p", 26);
|
|
4060
|
+
i0.ɵɵconditionalCreate(1, KptMapPoiCard_Conditional_15_Conditional_1_Template, 1, 1, "kpt-icon", 27);
|
|
4061
|
+
i0.ɵɵtext(2);
|
|
4062
|
+
i0.ɵɵelementEnd();
|
|
4063
|
+
} if (rf & 2) {
|
|
4064
|
+
const category_r6 = ctx;
|
|
4065
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4066
|
+
i0.ɵɵstyleProp("--%NS%_kpt-poi-color", ctx_r1.categoryColor());
|
|
4067
|
+
i0.ɵɵadvance();
|
|
4068
|
+
i0.ɵɵconditional(category_r6.icon ? 1 : -1);
|
|
4069
|
+
i0.ɵɵadvance();
|
|
4070
|
+
i0.ɵɵtextInterpolate1(" ", category_r6.label, " ");
|
|
4071
|
+
} }
|
|
4072
|
+
function KptMapPoiCard_Conditional_16_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
4073
|
+
i0.ɵɵelementStart(0, "span", 31);
|
|
4074
|
+
i0.ɵɵtext(1);
|
|
4075
|
+
i0.ɵɵelementEnd();
|
|
4076
|
+
} if (rf & 2) {
|
|
4077
|
+
i0.ɵɵadvance();
|
|
4078
|
+
i0.ɵɵtextInterpolate1("\u00B7 ", ctx);
|
|
4079
|
+
} }
|
|
4080
|
+
function KptMapPoiCard_Conditional_16_Conditional_7_For_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
4081
|
+
i0.ɵɵelementStart(0, "span", 36);
|
|
4082
|
+
i0.ɵɵtext(1);
|
|
4083
|
+
i0.ɵɵelementEnd();
|
|
4084
|
+
} if (rf & 2) {
|
|
4085
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
4086
|
+
i0.ɵɵadvance();
|
|
4087
|
+
i0.ɵɵtextInterpolate(ctx_r1.i18n.t("map.poi.closedDay"));
|
|
4088
|
+
} }
|
|
4089
|
+
function KptMapPoiCard_Conditional_16_Conditional_7_For_2_Conditional_4_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
4090
|
+
i0.ɵɵelementStart(0, "span", 37);
|
|
4091
|
+
i0.ɵɵtext(1);
|
|
4092
|
+
i0.ɵɵelementEnd();
|
|
4093
|
+
} if (rf & 2) {
|
|
4094
|
+
const range_r8 = ctx.$implicit;
|
|
4095
|
+
i0.ɵɵadvance();
|
|
4096
|
+
i0.ɵɵtextInterpolate(range_r8);
|
|
4097
|
+
} }
|
|
4098
|
+
function KptMapPoiCard_Conditional_16_Conditional_7_For_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
4099
|
+
i0.ɵɵrepeaterCreate(0, KptMapPoiCard_Conditional_16_Conditional_7_For_2_Conditional_4_For_1_Template, 2, 1, "span", 37, i0.ɵɵrepeaterTrackByIdentity);
|
|
4100
|
+
} if (rf & 2) {
|
|
4101
|
+
const group_r9 = i0.ɵɵnextContext().$implicit;
|
|
4102
|
+
i0.ɵɵrepeater(group_r9.ranges);
|
|
4103
|
+
} }
|
|
4104
|
+
function KptMapPoiCard_Conditional_16_Conditional_7_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
4105
|
+
i0.ɵɵelementStart(0, "dt", 34);
|
|
4106
|
+
i0.ɵɵtext(1);
|
|
4107
|
+
i0.ɵɵelementEnd();
|
|
4108
|
+
i0.ɵɵelementStart(2, "dd", 35);
|
|
4109
|
+
i0.ɵɵconditionalCreate(3, KptMapPoiCard_Conditional_16_Conditional_7_For_2_Conditional_3_Template, 2, 1, "span", 36)(4, KptMapPoiCard_Conditional_16_Conditional_7_For_2_Conditional_4_Template, 2, 0);
|
|
4110
|
+
i0.ɵɵelementEnd();
|
|
4111
|
+
} if (rf & 2) {
|
|
4112
|
+
const group_r9 = ctx.$implicit;
|
|
4113
|
+
i0.ɵɵadvance();
|
|
4114
|
+
i0.ɵɵtextInterpolate(group_r9.label);
|
|
4115
|
+
i0.ɵɵadvance(2);
|
|
4116
|
+
i0.ɵɵconditional(group_r9.closed ? 3 : 4);
|
|
4117
|
+
} }
|
|
4118
|
+
function KptMapPoiCard_Conditional_16_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
4119
|
+
i0.ɵɵelementStart(0, "dl", 33);
|
|
4120
|
+
i0.ɵɵrepeaterCreate(1, KptMapPoiCard_Conditional_16_Conditional_7_For_2_Template, 5, 2, null, null, _forTrack0);
|
|
4121
|
+
i0.ɵɵelementEnd();
|
|
4122
|
+
} if (rf & 2) {
|
|
4123
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
4124
|
+
i0.ɵɵproperty("id", ctx_r1.hoursId);
|
|
4125
|
+
i0.ɵɵadvance();
|
|
4126
|
+
i0.ɵɵrepeater(ctx_r1.hoursGroups());
|
|
4127
|
+
} }
|
|
4128
|
+
function KptMapPoiCard_Conditional_16_Template(rf, ctx) { if (rf & 1) {
|
|
4129
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
4130
|
+
i0.ɵɵelementStart(0, "div", 15)(1, "button", 28);
|
|
4131
|
+
i0.ɵɵlistener("click", function KptMapPoiCard_Conditional_16_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.toggleHours()); });
|
|
4132
|
+
i0.ɵɵelement(2, "kpt-icon", 29);
|
|
4133
|
+
i0.ɵɵelementStart(3, "span", 30);
|
|
4134
|
+
i0.ɵɵtext(4);
|
|
4135
|
+
i0.ɵɵelementEnd();
|
|
4136
|
+
i0.ɵɵconditionalCreate(5, KptMapPoiCard_Conditional_16_Conditional_5_Template, 2, 1, "span", 31);
|
|
4137
|
+
i0.ɵɵelement(6, "kpt-icon", 32);
|
|
4138
|
+
i0.ɵɵelementEnd();
|
|
4139
|
+
i0.ɵɵconditionalCreate(7, KptMapPoiCard_Conditional_16_Conditional_7_Template, 3, 1, "dl", 33);
|
|
4140
|
+
i0.ɵɵelementEnd();
|
|
4141
|
+
} if (rf & 2) {
|
|
4142
|
+
let tmp_5_0;
|
|
4143
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4144
|
+
i0.ɵɵadvance();
|
|
4145
|
+
i0.ɵɵattribute("aria-expanded", ctx_r1.hoursExpanded())("aria-controls", ctx_r1.hoursId);
|
|
4146
|
+
i0.ɵɵadvance(2);
|
|
4147
|
+
i0.ɵɵattribute("data-open", ctx_r1.open() || ctx_r1.alwaysOpen());
|
|
4148
|
+
i0.ɵɵadvance();
|
|
4149
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.statusLabel(), " ");
|
|
4150
|
+
i0.ɵɵadvance();
|
|
4151
|
+
i0.ɵɵconditional((tmp_5_0 = ctx_r1.statusHint()) ? 5 : -1, tmp_5_0);
|
|
4152
|
+
i0.ɵɵadvance(2);
|
|
4153
|
+
i0.ɵɵconditional(ctx_r1.hoursExpanded() ? 7 : -1);
|
|
4154
|
+
} }
|
|
4155
|
+
function KptMapPoiCard_Conditional_17_For_2_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
4156
|
+
i0.ɵɵelementStart(0, "a", 41);
|
|
4157
|
+
i0.ɵɵtext(1);
|
|
4158
|
+
i0.ɵɵelementEnd();
|
|
4159
|
+
} if (rf & 2) {
|
|
4160
|
+
const row_r10 = i0.ɵɵnextContext().$implicit;
|
|
4161
|
+
i0.ɵɵproperty("href", row_r10.href, i0.ɵɵsanitizeUrl);
|
|
4162
|
+
i0.ɵɵattribute("target", row_r10.external ? "_blank" : null)("rel", row_r10.external ? "noopener" : null);
|
|
4163
|
+
i0.ɵɵadvance();
|
|
4164
|
+
i0.ɵɵtextInterpolate1(" ", row_r10.text, " ");
|
|
4165
|
+
} }
|
|
4166
|
+
function KptMapPoiCard_Conditional_17_For_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
4167
|
+
i0.ɵɵelementStart(0, "span", 42);
|
|
4168
|
+
i0.ɵɵtext(1);
|
|
4169
|
+
i0.ɵɵelementEnd();
|
|
4170
|
+
} if (rf & 2) {
|
|
4171
|
+
const row_r10 = i0.ɵɵnextContext().$implicit;
|
|
4172
|
+
i0.ɵɵadvance();
|
|
4173
|
+
i0.ɵɵtextInterpolate(row_r10.text);
|
|
4174
|
+
} }
|
|
4175
|
+
function KptMapPoiCard_Conditional_17_For_2_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
4176
|
+
i0.ɵɵelementStart(0, "a", 44);
|
|
4177
|
+
i0.ɵɵelement(1, "kpt-icon", 46);
|
|
4178
|
+
i0.ɵɵelementEnd();
|
|
4179
|
+
} if (rf & 2) {
|
|
4180
|
+
const row_r10 = i0.ɵɵnextContext().$implicit;
|
|
4181
|
+
i0.ɵɵproperty("href", row_r10.href, i0.ɵɵsanitizeUrl)("title", row_r10.actionLabel);
|
|
4182
|
+
i0.ɵɵattribute("target", row_r10.external ? "_blank" : null)("rel", row_r10.external ? "noopener" : null)("aria-label", row_r10.actionLabel);
|
|
4183
|
+
i0.ɵɵadvance();
|
|
4184
|
+
i0.ɵɵproperty("name", row_r10.actionIcon);
|
|
4185
|
+
} }
|
|
4186
|
+
function KptMapPoiCard_Conditional_17_For_2_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
4187
|
+
const _r11 = i0.ɵɵgetCurrentView();
|
|
4188
|
+
i0.ɵɵelementStart(0, "button", 47);
|
|
4189
|
+
i0.ɵɵlistener("click", function KptMapPoiCard_Conditional_17_For_2_Conditional_6_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r11); const row_r10 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.copy(row_r10)); });
|
|
4190
|
+
i0.ɵɵelement(1, "kpt-icon", 46);
|
|
4191
|
+
i0.ɵɵelementEnd();
|
|
4192
|
+
} if (rf & 2) {
|
|
4193
|
+
const row_r10 = i0.ɵɵnextContext().$implicit;
|
|
4194
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
4195
|
+
i0.ɵɵproperty("title", ctx_r1.i18n.t("map.poi.copy"));
|
|
4196
|
+
i0.ɵɵattribute("aria-label", ctx_r1.i18n.t("map.poi.copy"));
|
|
4197
|
+
i0.ɵɵadvance();
|
|
4198
|
+
i0.ɵɵproperty("name", ctx_r1.justCopied() === row_r10.kind ? "check" : "copy");
|
|
4199
|
+
} }
|
|
4200
|
+
function KptMapPoiCard_Conditional_17_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
4201
|
+
i0.ɵɵelementStart(0, "li", 38);
|
|
4202
|
+
i0.ɵɵelement(1, "kpt-icon", 40);
|
|
4203
|
+
i0.ɵɵconditionalCreate(2, KptMapPoiCard_Conditional_17_For_2_Conditional_2_Template, 2, 4, "a", 41)(3, KptMapPoiCard_Conditional_17_For_2_Conditional_3_Template, 2, 1, "span", 42);
|
|
4204
|
+
i0.ɵɵelementStart(4, "span", 43);
|
|
4205
|
+
i0.ɵɵconditionalCreate(5, KptMapPoiCard_Conditional_17_For_2_Conditional_5_Template, 2, 6, "a", 44);
|
|
4206
|
+
i0.ɵɵconditionalCreate(6, KptMapPoiCard_Conditional_17_For_2_Conditional_6_Template, 2, 3, "button", 45);
|
|
4207
|
+
i0.ɵɵelementEnd()();
|
|
4208
|
+
} if (rf & 2) {
|
|
4209
|
+
const row_r10 = ctx.$implicit;
|
|
4210
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
4211
|
+
i0.ɵɵadvance();
|
|
4212
|
+
i0.ɵɵproperty("name", row_r10.icon);
|
|
4213
|
+
i0.ɵɵadvance();
|
|
4214
|
+
i0.ɵɵconditional(row_r10.href ? 2 : 3);
|
|
4215
|
+
i0.ɵɵadvance(3);
|
|
4216
|
+
i0.ɵɵconditional(row_r10.href ? 5 : -1);
|
|
4217
|
+
i0.ɵɵadvance();
|
|
4218
|
+
i0.ɵɵconditional(ctx_r1.canCopy ? 6 : -1);
|
|
4219
|
+
} }
|
|
4220
|
+
function KptMapPoiCard_Conditional_17_For_4_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
4221
|
+
i0.ɵɵelement(0, "kpt-icon", 40);
|
|
4222
|
+
} if (rf & 2) {
|
|
4223
|
+
const fact_r12 = i0.ɵɵnextContext().$implicit;
|
|
4224
|
+
i0.ɵɵproperty("name", fact_r12.icon);
|
|
4225
|
+
} }
|
|
4226
|
+
function KptMapPoiCard_Conditional_17_For_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
4227
|
+
i0.ɵɵelement(0, "span", 48);
|
|
4228
|
+
} }
|
|
4229
|
+
function KptMapPoiCard_Conditional_17_For_4_Template(rf, ctx) { if (rf & 1) {
|
|
4230
|
+
i0.ɵɵelementStart(0, "li", 39);
|
|
4231
|
+
i0.ɵɵconditionalCreate(1, KptMapPoiCard_Conditional_17_For_4_Conditional_1_Template, 1, 1, "kpt-icon", 40)(2, KptMapPoiCard_Conditional_17_For_4_Conditional_2_Template, 1, 0, "span", 48);
|
|
4232
|
+
i0.ɵɵelementStart(3, "span", 42)(4, "span", 49);
|
|
4233
|
+
i0.ɵɵtext(5);
|
|
4234
|
+
i0.ɵɵelementEnd();
|
|
4235
|
+
i0.ɵɵtext(6);
|
|
4236
|
+
i0.ɵɵelementEnd()();
|
|
4237
|
+
} if (rf & 2) {
|
|
4238
|
+
const fact_r12 = ctx.$implicit;
|
|
4239
|
+
i0.ɵɵadvance();
|
|
4240
|
+
i0.ɵɵconditional(fact_r12.icon ? 1 : 2);
|
|
4241
|
+
i0.ɵɵadvance(4);
|
|
4242
|
+
i0.ɵɵtextInterpolate(fact_r12.label);
|
|
4243
|
+
i0.ɵɵadvance();
|
|
4244
|
+
i0.ɵɵtextInterpolate1(" ", fact_r12.value, " ");
|
|
4245
|
+
} }
|
|
4246
|
+
function KptMapPoiCard_Conditional_17_Template(rf, ctx) { if (rf & 1) {
|
|
4247
|
+
i0.ɵɵelementStart(0, "ul", 16);
|
|
4248
|
+
i0.ɵɵrepeaterCreate(1, KptMapPoiCard_Conditional_17_For_2_Template, 7, 4, "li", 38, _forTrack1);
|
|
4249
|
+
i0.ɵɵrepeaterCreate(3, KptMapPoiCard_Conditional_17_For_4_Template, 7, 3, "li", 39, _forTrack0);
|
|
4250
|
+
i0.ɵɵelementEnd();
|
|
4251
|
+
} if (rf & 2) {
|
|
4252
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4253
|
+
i0.ɵɵadvance();
|
|
4254
|
+
i0.ɵɵrepeater(ctx_r1.rows());
|
|
4255
|
+
i0.ɵɵadvance(2);
|
|
4256
|
+
i0.ɵɵrepeater(ctx_r1.poi().facts ?? i0.ɵɵpureFunction0(0, _c0));
|
|
4257
|
+
} }
|
|
4258
|
+
function KptMapPoiCard_Conditional_19_Template(rf, ctx) { if (rf & 1) {
|
|
4259
|
+
i0.ɵɵtext(0);
|
|
4260
|
+
} if (rf & 2) {
|
|
4261
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
4262
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.i18n.t("map.poi.copied"), " ");
|
|
4263
|
+
} }
|
|
4264
|
+
/** Licznik instancji — `aria-controls` musi wskazywać unikatowy identyfikator na stronie. */
|
|
4265
|
+
let poiCardSeq = 0;
|
|
4266
|
+
/** Adres bez schematu dostaje `https://` — inaczej przeglądarka potraktuje go jak ścieżkę względną. */
|
|
4267
|
+
function normalizeUrl(value) {
|
|
4268
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(value) ? value : `https://${value}`;
|
|
4269
|
+
}
|
|
4270
|
+
/** Wersja adresu do pokazania: bez schematu i bez końcowego ukośnika. */
|
|
4271
|
+
function prettyUrl(value) {
|
|
4272
|
+
return value.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '').replace(/\/$/, '');
|
|
4273
|
+
}
|
|
4274
|
+
/**
|
|
4275
|
+
* Karta miejsca do popupu mapy — dystans od użytkownika, zdjęcie, akcje, kategoria,
|
|
4276
|
+
* rozwijane godziny otwarcia i wiersze kontaktowe z kopiowaniem.
|
|
4277
|
+
*
|
|
4278
|
+
* Komponent jest sterowany danymi: bierze `KptMapPoi`, a nie model domenowy aplikacji.
|
|
4279
|
+
* Mapowanie `KptMapLocation.data` → `KptMapPoi` robi aplikacja, dzięki czemu karta nie
|
|
4280
|
+
* wie nic o tym, czy pokazuje ładowarkę, restaurację, czy punkt serwisowy.
|
|
4281
|
+
*
|
|
4282
|
+
* Sam popup `kpt-map` trzeba przełączyć na `[popupLayout]="'card'"` — inaczej karta
|
|
4283
|
+
* dostanie 20 rem szerokości i padding przeznaczony dla treści tekstowej.
|
|
4284
|
+
*
|
|
4285
|
+
* @example
|
|
4286
|
+
* <kpt-map [locations]="places()" [categories]="categories" popupLayout="card">
|
|
4287
|
+
* <ng-template kptMapPopup let-loc let-d="data" let-dist="distance" let-close="close">
|
|
4288
|
+
* <kpt-map-poi-card
|
|
4289
|
+
* [poi]="poiOf(loc, d)"
|
|
4290
|
+
* [distance]="dist"
|
|
4291
|
+
* [category]="categoryOf(loc)"
|
|
4292
|
+
* primaryLabel="Wyznacz trasę"
|
|
4293
|
+
* (closed)="close()"
|
|
4294
|
+
* />
|
|
4295
|
+
* </ng-template>
|
|
4296
|
+
* </kpt-map>
|
|
4297
|
+
*/
|
|
4298
|
+
class KptMapPoiCard {
|
|
4299
|
+
constructor() {
|
|
4300
|
+
this.poi = input.required(/* @ts-ignore */
|
|
4301
|
+
...(ngDevMode ? [{ debugName: "poi" }] : /* istanbul ignore next */ []));
|
|
4302
|
+
/** Odległość w metrach; `null` chowa wiersz dystansu. */
|
|
4303
|
+
this.distance = input(null, /* @ts-ignore */
|
|
4304
|
+
...(ngDevMode ? [{ debugName: "distance" }] : /* istanbul ignore next */ []));
|
|
4305
|
+
/** Źródło ikony, koloru i etykiety wiersza kategorii; `null` chowa ten wiersz. */
|
|
4306
|
+
this.category = input(null, /* @ts-ignore */
|
|
4307
|
+
...(ngDevMode ? [{ debugName: "category" }] : /* istanbul ignore next */ []));
|
|
4308
|
+
/** Etykieta przycisku głównego; pusta chowa przycisk. */
|
|
4309
|
+
this.primaryLabel = input('', /* @ts-ignore */
|
|
4310
|
+
...(ngDevMode ? [{ debugName: "primaryLabel" }] : /* istanbul ignore next */ []));
|
|
4311
|
+
this.showShare = input(true, { ...(ngDevMode ? { debugName: "showShare" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4312
|
+
this.showFavourite = input(true, { ...(ngDevMode ? { debugName: "showFavourite" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4313
|
+
this.favourite = model(false, /* @ts-ignore */
|
|
4314
|
+
...(ngDevMode ? [{ debugName: "favourite" }] : /* istanbul ignore next */ []));
|
|
4315
|
+
this.closable = input(true, { ...(ngDevMode ? { debugName: "closable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
4316
|
+
/** Rozwinąć godziny od razu. Domyślnie zwinięte, jak na karcie miejsca w mapach. */
|
|
4317
|
+
this.hoursExpanded = model(false, /* @ts-ignore */
|
|
4318
|
+
...(ngDevMode ? [{ debugName: "hoursExpanded" }] : /* istanbul ignore next */ []));
|
|
4319
|
+
this.primaryAction = output();
|
|
4320
|
+
this.closed = output();
|
|
4321
|
+
this.shared = output();
|
|
4322
|
+
this.i18n = inject(KptI18n);
|
|
4323
|
+
/** Identyfikator regionu godzin; kart na stronie bywa więcej niż jedna. */
|
|
4324
|
+
this.hoursId = `kpt-map-poi-hours-${++poiCardSeq}`;
|
|
4325
|
+
/** Rośnie przy każdym otwarciu karty i przy zmianie danych — odświeża status godzin. */
|
|
4326
|
+
this.now = signal(Date.now(), /* @ts-ignore */
|
|
4327
|
+
...(ngDevMode ? [{ debugName: "now" }] : /* istanbul ignore next */ []));
|
|
4328
|
+
/**
|
|
4329
|
+
* Ikona kategorii stoi wprost na powierzchni karty, bez własnego tła — stąd
|
|
4330
|
+
* `foregroundVarOf()`, a nie `colorVarOf()`: rola `muted` ma kolor tła, nie treści.
|
|
4331
|
+
*/
|
|
4332
|
+
this.categoryColor = computed(() => {
|
|
4333
|
+
const category = this.category();
|
|
4334
|
+
return category ? foregroundVarOf(category.color) : null;
|
|
4335
|
+
}, /* @ts-ignore */
|
|
4336
|
+
...(ngDevMode ? [{ debugName: "categoryColor" }] : /* istanbul ignore next */ []));
|
|
4337
|
+
this.distanceLabel = computed(() => formatDistance(this.distance(), this.i18n.locale()), /* @ts-ignore */
|
|
4338
|
+
...(ngDevMode ? [{ debugName: "distanceLabel" }] : /* istanbul ignore next */ []));
|
|
4339
|
+
/* ── godziny ─────────────────────────────────────────────────────────── */
|
|
4340
|
+
this.hasHours = computed(() => (this.poi().hours?.length ?? 0) > 0, /* @ts-ignore */
|
|
4341
|
+
...(ngDevMode ? [{ debugName: "hasHours" }] : /* istanbul ignore next */ []));
|
|
4342
|
+
this.alwaysOpen = computed(() => isAlwaysOpen(this.poi().hours), /* @ts-ignore */
|
|
4343
|
+
...(ngDevMode ? [{ debugName: "alwaysOpen" }] : /* istanbul ignore next */ []));
|
|
4344
|
+
this.open = computed(() => isOpenAt(this.poi().hours, new Date(this.now())), /* @ts-ignore */
|
|
4345
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
4346
|
+
this.hoursGroups = computed(() => groupWeek(this.poi().hours, this.i18n.locale()), /* @ts-ignore */
|
|
4347
|
+
...(ngDevMode ? [{ debugName: "hoursGroups" }] : /* istanbul ignore next */ []));
|
|
4348
|
+
this.statusLabel = computed(() => {
|
|
4349
|
+
if (this.alwaysOpen())
|
|
4350
|
+
return this.i18n.t('map.poi.alwaysOpen');
|
|
4351
|
+
return this.i18n.t(this.open() ? 'map.poi.openNow' : 'map.poi.closedNow');
|
|
4352
|
+
}, /* @ts-ignore */
|
|
4353
|
+
...(ngDevMode ? [{ debugName: "statusLabel" }] : /* istanbul ignore next */ []));
|
|
4354
|
+
/** Dopisek „otwarcie o 11:00" — nie ma go dla 24/7 ani gdy zmiana jest dalej niż doba. */
|
|
4355
|
+
this.statusHint = computed(() => {
|
|
4356
|
+
if (this.alwaysOpen() || !this.hasHours())
|
|
4357
|
+
return null;
|
|
4358
|
+
const at = new Date(this.now());
|
|
4359
|
+
const change = nextChange(this.poi().hours, at);
|
|
4360
|
+
if (!change)
|
|
4361
|
+
return null;
|
|
4362
|
+
if (change.at.getTime() - at.getTime() > 24 * 60 * 60 * 1000)
|
|
4363
|
+
return null;
|
|
4364
|
+
const time = new Intl.DateTimeFormat(this.i18n.locale(), {
|
|
4365
|
+
hour: '2-digit',
|
|
4366
|
+
minute: '2-digit',
|
|
4367
|
+
}).format(change.at);
|
|
4368
|
+
return this.i18n.t(change.open ? 'map.poi.opensAt' : 'map.poi.closesAt', { time });
|
|
4369
|
+
}, /* @ts-ignore */
|
|
4370
|
+
...(ngDevMode ? [{ debugName: "statusHint" }] : /* istanbul ignore next */ []));
|
|
4371
|
+
/* ── wiersze kontaktu ────────────────────────────────────────────────── */
|
|
4372
|
+
this.rows = computed(() => {
|
|
4373
|
+
const poi = this.poi();
|
|
4374
|
+
const rows = [];
|
|
4375
|
+
if (poi.phone) {
|
|
4376
|
+
rows.push({
|
|
4377
|
+
kind: 'phone',
|
|
4378
|
+
icon: 'phone',
|
|
4379
|
+
text: poi.phone,
|
|
4380
|
+
copyValue: poi.phone,
|
|
4381
|
+
href: `tel:${poi.phone.replace(/\s+/g, '')}`,
|
|
4382
|
+
external: false,
|
|
4383
|
+
actionIcon: 'phone',
|
|
4384
|
+
actionLabel: this.i18n.t('map.poi.call'),
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
if (poi.website) {
|
|
4388
|
+
const url = normalizeUrl(poi.website);
|
|
4389
|
+
rows.push({
|
|
4390
|
+
kind: 'website',
|
|
4391
|
+
icon: 'world',
|
|
4392
|
+
text: prettyUrl(poi.website),
|
|
4393
|
+
copyValue: url,
|
|
4394
|
+
href: url,
|
|
4395
|
+
external: true,
|
|
4396
|
+
actionIcon: 'external-link',
|
|
4397
|
+
actionLabel: this.i18n.t('map.poi.openWebsite'),
|
|
4398
|
+
});
|
|
4399
|
+
}
|
|
4400
|
+
if (poi.email) {
|
|
4401
|
+
rows.push({
|
|
4402
|
+
kind: 'email',
|
|
4403
|
+
icon: 'mail',
|
|
4404
|
+
text: poi.email,
|
|
4405
|
+
copyValue: poi.email,
|
|
4406
|
+
href: `mailto:${poi.email}`,
|
|
4407
|
+
external: false,
|
|
4408
|
+
actionIcon: 'mail',
|
|
4409
|
+
actionLabel: this.i18n.t('map.poi.sendEmail'),
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4412
|
+
if (poi.at) {
|
|
4413
|
+
const text = formatLatLng(poi.at);
|
|
4414
|
+
rows.push({
|
|
4415
|
+
kind: 'coords',
|
|
4416
|
+
icon: 'crosshair',
|
|
4417
|
+
text,
|
|
4418
|
+
copyValue: text,
|
|
4419
|
+
href: null,
|
|
4420
|
+
external: false,
|
|
4421
|
+
actionIcon: '',
|
|
4422
|
+
actionLabel: '',
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
return rows;
|
|
4426
|
+
}, /* @ts-ignore */
|
|
4427
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
4428
|
+
/* ── akcje ───────────────────────────────────────────────────────────── */
|
|
4429
|
+
/** Klucz wiersza, którego wartość właśnie skopiowano — przełącza ikonę na „check". */
|
|
4430
|
+
this.justCopied = signal(null, /* @ts-ignore */
|
|
4431
|
+
...(ngDevMode ? [{ debugName: "justCopied" }] : /* istanbul ignore next */ []));
|
|
4432
|
+
this.copyTimer = null;
|
|
4433
|
+
/** Bez API schowka przycisk kopiowania w ogóle się nie renderuje. */
|
|
4434
|
+
this.canCopy = typeof navigator !== 'undefined' && !!navigator.clipboard;
|
|
4435
|
+
this.canShare = typeof navigator !== 'undefined' && 'share' in navigator;
|
|
4436
|
+
}
|
|
4437
|
+
async copy(row) {
|
|
4438
|
+
try {
|
|
4439
|
+
await navigator.clipboard.writeText(row.copyValue);
|
|
4440
|
+
}
|
|
4441
|
+
catch {
|
|
4442
|
+
// Odmowa zgody albo brak bezpiecznego kontekstu — nie ma czego zgłaszać użytkownikowi.
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
this.justCopied.set(row.kind);
|
|
4446
|
+
if (this.copyTimer)
|
|
4447
|
+
clearTimeout(this.copyTimer);
|
|
4448
|
+
this.copyTimer = setTimeout(() => this.justCopied.set(null), 2000);
|
|
4449
|
+
}
|
|
4450
|
+
onPrimary() {
|
|
4451
|
+
this.primaryAction.emit(this.poi());
|
|
4452
|
+
}
|
|
4453
|
+
onClose() {
|
|
4454
|
+
this.closed.emit();
|
|
4455
|
+
}
|
|
4456
|
+
toggleFavourite() {
|
|
4457
|
+
this.favourite.update((v) => !v);
|
|
4458
|
+
}
|
|
4459
|
+
toggleHours() {
|
|
4460
|
+
// Odświeżamy zegar przy rozwinięciu — karta bywa otwarta dłużej niż minutę.
|
|
4461
|
+
this.now.set(Date.now());
|
|
4462
|
+
this.hoursExpanded.update((v) => !v);
|
|
4463
|
+
}
|
|
4464
|
+
/**
|
|
4465
|
+
* Udostępnianie: natywny arkusz systemowy, gdy przeglądarka go ma, inaczej schowek.
|
|
4466
|
+
* W obu przypadkach leci `shared`, więc aplikacja może dołożyć własną telemetrię.
|
|
4467
|
+
*/
|
|
4468
|
+
async share() {
|
|
4469
|
+
const poi = this.poi();
|
|
4470
|
+
const url = poi.website ? normalizeUrl(poi.website) : undefined;
|
|
4471
|
+
if (this.canShare) {
|
|
4472
|
+
try {
|
|
4473
|
+
await navigator.share({ title: poi.title, text: poi.address, url });
|
|
4474
|
+
}
|
|
4475
|
+
catch {
|
|
4476
|
+
// Anulowanie arkusza przez użytkownika nie jest błędem.
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
else if (this.canCopy) {
|
|
4481
|
+
try {
|
|
4482
|
+
await navigator.clipboard.writeText(url ?? poi.title);
|
|
4483
|
+
}
|
|
4484
|
+
catch {
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
this.shared.emit(poi);
|
|
4489
|
+
}
|
|
4490
|
+
favouriteLabel() {
|
|
4491
|
+
return this.i18n.t(this.favourite() ? 'map.poi.removeFavourite' : 'map.poi.addFavourite');
|
|
4492
|
+
}
|
|
4493
|
+
static { this.ɵfac = function KptMapPoiCard_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || KptMapPoiCard)(); }; }
|
|
4494
|
+
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) {
|
|
4495
|
+
i0.ɵɵelementStart(0, "header", 0)(1, "p", 1);
|
|
4496
|
+
i0.ɵɵtext(2);
|
|
4497
|
+
i0.ɵɵelementEnd();
|
|
4498
|
+
i0.ɵɵconditionalCreate(3, KptMapPoiCard_Conditional_3_Template, 2, 1, "kpt-icon-button", 2);
|
|
4499
|
+
i0.ɵɵelementEnd();
|
|
4500
|
+
i0.ɵɵelementStart(4, "p", 3);
|
|
4501
|
+
i0.ɵɵconditionalCreate(5, KptMapPoiCard_Conditional_5_Template, 2, 1, "strong", 4);
|
|
4502
|
+
i0.ɵɵconditionalCreate(6, KptMapPoiCard_Conditional_6_Template, 2, 1, "span", 5);
|
|
4503
|
+
i0.ɵɵelementEnd();
|
|
4504
|
+
i0.ɵɵelementStart(7, "div", 6)(8, "div", 7);
|
|
4505
|
+
i0.ɵɵconditionalCreate(9, KptMapPoiCard_Conditional_9_Template, 1, 2, "img", 8)(10, KptMapPoiCard_Conditional_10_Template, 2, 1, "div", 9);
|
|
4506
|
+
i0.ɵɵelementEnd();
|
|
4507
|
+
i0.ɵɵelementStart(11, "div", 10);
|
|
4508
|
+
i0.ɵɵconditionalCreate(12, KptMapPoiCard_Conditional_12_Template, 2, 1, "kpt-button", 11);
|
|
4509
|
+
i0.ɵɵconditionalCreate(13, KptMapPoiCard_Conditional_13_Template, 2, 1, "kpt-icon-button", 12);
|
|
4510
|
+
i0.ɵɵconditionalCreate(14, KptMapPoiCard_Conditional_14_Template, 2, 3, "kpt-icon-button", 13);
|
|
4511
|
+
i0.ɵɵelementEnd();
|
|
4512
|
+
i0.ɵɵconditionalCreate(15, KptMapPoiCard_Conditional_15_Template, 3, 4, "p", 14);
|
|
4513
|
+
i0.ɵɵconditionalCreate(16, KptMapPoiCard_Conditional_16_Template, 8, 6, "div", 15);
|
|
4514
|
+
i0.ɵɵconditionalCreate(17, KptMapPoiCard_Conditional_17_Template, 5, 1, "ul", 16);
|
|
4515
|
+
i0.ɵɵelementStart(18, "p", 17);
|
|
4516
|
+
i0.ɵɵconditionalCreate(19, KptMapPoiCard_Conditional_19_Template, 1, 1);
|
|
4517
|
+
i0.ɵɵelementEnd()();
|
|
4518
|
+
} if (rf & 2) {
|
|
4519
|
+
let tmp_2_0;
|
|
4520
|
+
let tmp_3_0;
|
|
4521
|
+
let tmp_4_0;
|
|
4522
|
+
let tmp_8_0;
|
|
4523
|
+
i0.ɵɵadvance(2);
|
|
4524
|
+
i0.ɵɵtextInterpolate(ctx.poi().title);
|
|
4525
|
+
i0.ɵɵadvance();
|
|
4526
|
+
i0.ɵɵconditional(ctx.closable() ? 3 : -1);
|
|
4527
|
+
i0.ɵɵadvance(2);
|
|
4528
|
+
i0.ɵɵconditional((tmp_2_0 = ctx.distanceLabel()) ? 5 : -1, tmp_2_0);
|
|
4529
|
+
i0.ɵɵadvance();
|
|
4530
|
+
i0.ɵɵconditional((tmp_3_0 = ctx.poi().address) ? 6 : -1, tmp_3_0);
|
|
4531
|
+
i0.ɵɵadvance(3);
|
|
4532
|
+
i0.ɵɵconditional((tmp_4_0 = ctx.poi().photo) ? 9 : 10, tmp_4_0);
|
|
4533
|
+
i0.ɵɵadvance(3);
|
|
4534
|
+
i0.ɵɵconditional(ctx.primaryLabel() ? 12 : -1);
|
|
4535
|
+
i0.ɵɵadvance();
|
|
4536
|
+
i0.ɵɵconditional(ctx.showShare() ? 13 : -1);
|
|
4537
|
+
i0.ɵɵadvance();
|
|
4538
|
+
i0.ɵɵconditional(ctx.showFavourite() ? 14 : -1);
|
|
4539
|
+
i0.ɵɵadvance();
|
|
4540
|
+
i0.ɵɵconditional((tmp_8_0 = ctx.category()) ? 15 : -1, tmp_8_0);
|
|
4541
|
+
i0.ɵɵadvance();
|
|
4542
|
+
i0.ɵɵconditional(ctx.hasHours() ? 16 : -1);
|
|
4543
|
+
i0.ɵɵadvance();
|
|
4544
|
+
i0.ɵɵconditional(ctx.rows().length || ctx.poi().facts?.length ? 17 : -1);
|
|
4545
|
+
i0.ɵɵadvance(2);
|
|
4546
|
+
i0.ɵɵconditional(ctx.justCopied() ? 19 : -1);
|
|
4547
|
+
} }, 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 }); }
|
|
4548
|
+
}
|
|
4549
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(KptMapPoiCard, [{
|
|
4550
|
+
type: Component,
|
|
4551
|
+
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"] }]
|
|
4552
|
+
}], 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"] }] }); })();
|
|
4553
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(KptMapPoiCard, { className: "KptMapPoiCard", filePath: "lib/map-poi-card.component.ts", lineNumber: 81 }); })();
|
|
4554
|
+
|
|
4555
|
+
/*
|
|
4556
|
+
* Public API — @konce-pt/angular/map
|
|
4557
|
+
* Mapa z konfigurowalnymi znacznikami (Leaflet + OpenStreetMap). Rdzeń obliczeń: pure TS.
|
|
4558
|
+
*
|
|
4559
|
+
* Wymaga zależności opcjonalnej `leaflet` i dołączenia `leaflet/dist/leaflet.css`
|
|
4560
|
+
* po stronie aplikacji.
|
|
4561
|
+
*/
|
|
4562
|
+
|
|
4563
|
+
/**
|
|
4564
|
+
* Generated bundle index. Do not edit.
|
|
4565
|
+
*/
|
|
4566
|
+
|
|
4567
|
+
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 };
|
|
4568
|
+
//# sourceMappingURL=konce-pt-angular-map.mjs.map
|