@konce-pt/angular 0.7.0 → 0.7.6

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 CHANGED
@@ -1,5 +1,385 @@
1
1
  # @konce-pt/angular
2
2
 
3
+ ## 0.7.6
4
+
5
+ ### Minor Changes
6
+
7
+ - 1e20c5e: `kpt-badge` — nowe wejście `size` (`'sm' | 'md' | 'lg'`, domyślnie `'md'`).
8
+
9
+ Wysokość (`1rem` / `1.25rem` / `1.5rem`) steruje resztą geometrii: krój to `calc(--_size * 0.6)`,
10
+ wcięcie boczne i średnica kropki skalują się razem z nią. `md` wychodzi identycznie jak dotąd
11
+ (0.75rem kroju, kropka 0.65rem), więc istniejące użycia nie zmieniają wyglądu.
12
+
13
+ `sm` jest po to, żeby wstawić odznakę w wiersz tekstu — przy etykiecie, w pozycji menu, w komórce
14
+ tabeli — bez rozpychania wiersza.
15
+
16
+ - 8dd8e41: `KptMenuItem` — wiersz nagłówkowy i odznaka na pozycji.
17
+
18
+ - `header: true` zamienia pozycję w nieklikalny blok tekstu: `avatar` (`{ src?, name?, icon? }`,
19
+ renderowany przez `kpt-avatar`), `label` w półgrubym kroju i `description` drugą linią. Pokrywa
20
+ oba typowe zastosowania nagłówka menu: dane zalogowanego użytkownika (nazwa + e-mail) i opis
21
+ funkcji menu. To `<div role="presentation">`, więc nie wchodzi do nawigacji klawiaturą i nie
22
+ narusza kontraktu dzieci `role="menu"`.
23
+ - `badge` (`string | number`) plus `badgeVariant` dokładają odznakę wyrównaną do prawej krawędzi
24
+ pozycji — licznik powiadomień, status. Renderuje ją `kpt-badge`, więc wartości powyżej `max`
25
+ skracają się do `99+`, a wartość wchodzi w nazwę dostępną pozycji.
26
+ - `description` działa też na zwykłych pozycjach — dwuwierszowa pozycja menu.
27
+
28
+ Pola honorują `kpt-menu`, `kpt-context-menu` i `kpt-split-button`. `kpt-menubar`, `kpt-megamenu`
29
+ i `kpt-speed-dial` dzielą typ, ale renderowania nie zmieniają. Wszystkie pola są opcjonalne —
30
+ istniejące `items` działają bez zmian.
31
+
32
+ - d67fde2: `kpt-sidenav` — slot stopki `kptSidenavFooter` przyklejonej do dołu panelu.
33
+
34
+ Element z tym atrybutem trafia poza obszar przewijany, więc zostaje na dole niezależnie od długości
35
+ nawigacji — blok zalogowanego użytkownika, przełącznik motywu czy numer wersji nie odjeżdżają razem
36
+ z listą pozycji. Stopka dostaje górną kreskę na pełną szerokość panelu i ten sam padding co nawigacja.
37
+ W szynie ikon (`sidenavRailBreakpoint`) tekst stopki chowa się tym samym atrybutem `kptNavLabel`
38
+ co etykiety pozycji.
39
+
40
+ Wewnątrz komponentu `padding`, `gap` i `overflow-y` przeniosły się z hosta do obszaru przewijanego —
41
+ panele bez stopki wyglądają identycznie jak dotąd. Zmiana dotyka tylko aplikacji, które celowały
42
+ w `kpt-sidenav > *` selektorem dziecka bezpośredniego.
43
+
44
+ ### Patch Changes
45
+
46
+ - Migotanie przycisków i paneli na pierwszej klatce — poprawki `kpt-button`, `kpt-drawer`
47
+ i `kpt-bottom-sheet`.
48
+
49
+ Wspólna przyczyna: reguły stylu warunkowane atrybutem `[attr.data-*]`, który Angular ustawia
50
+ dopiero w przebiegu aktualizacji. Przez jedną klatkę komponent stał więc w stanie bazowym
51
+ i dopiero potem animował się do właściwego.
52
+
53
+ - **Przyciski** — sprzężenie zwrotne hovera i aktywności niesie pseudoelement `::after`
54
+ (`opacity` 0 → 1) zamiast animowanego `background-color`. Warstwa startuje przezroczysta,
55
+ więc zmiana jej koloru na pierwszej klatce jest niewidoczna. `inset: 0` trzyma ją wewnątrz
56
+ ramki, dzięki czemu obrys wariantu `outline` zostaje widoczny pod kursorem. Ten sam wzorzec
57
+ dostają `kpt-icon-button`, `kpt-fab` i `kpt-split-button`.
58
+ - **`kpt-drawer`** — geometria pozycji domyślnej i przesunięcie poza ekran wróciły do reguły
59
+ bazowej, więc zamknięty panel nie pokazuje się na moment przed animacją.
60
+ - **`kpt-drawer` i `kpt-bottom-sheet`** — zamknięty panel wypada z kolejki Tab i z drzewa
61
+ dostępności przez `visibility: hidden`, nie przez `inert`. Jest poprawny już na pierwszej
62
+ klatce, bez czekania na JavaScript, a animacja wjazdu zostaje (czego `display: none`
63
+ by nie pozwolił).
64
+
65
+ Wszystkie trzy komponenty honorują teraz `prefers-reduced-motion: reduce`.
66
+
67
+ ## 0.7.5
68
+
69
+ ### Minor Changes
70
+
71
+ - Responsywny `kpt-app-shell`: nakładka, szyna ikon i panel zadokowany
72
+
73
+ `kpt-app-shell` sam dobiera układ do szerokości okna. Nowy tryb `auto` (od `sidenavBreakpoint`,
74
+ domyślnie `lg`, panel jest zadokowany; poniżej — nakładka ze scrimem, domyślnie zamknięta),
75
+ opcjonalna szyna ikon w paśmie do `sidenavRailBreakpoint`, `Escape` zamykające nakładkę,
76
+ `inert` na treści pod nią, poprawny kierunek wysuwu w RTL i wysokość `100dvh`.
77
+
78
+ O układzie decyduje wyłącznie CSS, więc nic nie migocze przed hydracją i całość działa na SSR.
79
+
80
+ **Zmiana zachowania:** domyślną wartością `sidenavMode` jest teraz `'auto'` zamiast `'side'`.
81
+ Jeśli aplikacja sama zarządza responsywnością szkieletu (własne `@media` i własny hamburger),
82
+ ustaw jawnie `sidenavMode="side"` — inaczej dostaniesz dwie warstwy tej samej logiki.
83
+ Tryby `'side'` i `'over'` działają dokładnie jak wcześniej.
84
+
85
+ Nowe API:
86
+
87
+ - `KptBreakpointObserver` — media query jako sygnały (`up`, `down`, `matches`), spięte
88
+ z tokenami `--kpt-breakpoint-*`; poza przeglądarką zwraca `false`.
89
+ - `KptShellNavToggle` (`[kptShellNavToggle]`) — hamburger, który sam trafia w stan właściwy
90
+ dla bieżącego układu i dokłada `aria-expanded` / `aria-controls` / etykietę z i18n.
91
+ - `kpt-app-shell`: `sidenavCompactOpen`, `sidenavBreakpoint`, `sidenavRailBreakpoint`,
92
+ `sidenavLabel`, `sidenavId`, `isOverlay()`, `isSidenavOpen()`,
93
+ `toggleSidenav()` / `openSidenav()` / `closeSidenav()`.
94
+ - `kpt-toolbar`: input `wrap` (zawijanie zawartości) oraz kompaktowa wysokość poniżej `sm`.
95
+ - `@konce-pt/grid`: klasy `kpt-display-*` z wariantami per breakpoint i skrótem `kpt-hide`.
96
+ - Tokeny: `--kpt-sidenav-rail-width` (4rem), `--kpt-toolbar-height-compact` (3rem).
97
+ - Słownik: namespace `appShell` (`toggleNavigation`, `navigation`) w EN i PL.
98
+
99
+ Poprawka: `kpt-drawer` używał fallbacku `--kpt-z-overlay: 1000` (wartość `z.dropdown`)
100
+ zamiast `1100`.
101
+
102
+ - Nowy komponent `KptMap` (kpt-map) w subpathu `@konce-pt/angular/map` — interaktywna mapa
103
+ z konfigurowalnymi znacznikami, na silniku Leaflet + OpenStreetMap.
104
+
105
+ - Kategorie obiektów z kolorem (rola semantyczna tokenów, nie literał) i ikoną z `KptIconRegistry`;
106
+ kształty `pin` / `dot` / `square`.
107
+ - Grupowanie znaczników z licznikiem — własny rdzeń pure TS (siatka w przestrzeni pikseli, O(n),
108
+ deterministyczna), wspólny dla obu silników i przenośny do portu React. Bez `leaflet.markercluster`.
109
+ - Popup szczegółów o szablonie zależnym od kategorii (`ng-template kptMapPopup`), renderowany
110
+ przez Angulara i pozycjonowany przez `engine.project()` — pełne komponenty `kpt-*` w treści.
111
+ Kontekst szablonu niesie też `distance` — odległość punktu w metrach od `[distanceFrom]`,
112
+ a bez niego od pozycji użytkownika.
113
+ - **Karta miejsca `KptMapPoiCard` (kpt-map-poi-card)**: dystans, zdjęcie 16:9, przycisk główny
114
+ z udostępnianiem i ulubionymi, wiersz kategorii, rozwijane godziny otwarcia ze statusem
115
+ otwarte/zamknięte i wiersze kontaktowe (telefon, www, e-mail, współrzędne) z kopiowaniem
116
+ do schowka. Sterowana danymi: bierze `KptMapPoi`, nie model domenowy aplikacji.
117
+ - `[popupLayout]="'card'"` oddaje popup w całości treści — bez paddingu, szerszy, przewijalny
118
+ i bez wbudowanego krzyżyka (daje go karta). Domyślne `plain` zostaje bez zmian.
119
+ - Nagłówek karty (tytuł, krzyżyk, dystans i adres) stoi nieruchomo; przewija się wyłącznie
120
+ reszta treści, w osobnym `.kpt-map-poi__scroll`. Przewijanie schodzi w tym celu z popupu
121
+ do karty — `popupLayout="card"` daje `.kpt-map__popup-body` `display: flex` i `overflow:
122
+ hidden`, a karcie pełną wysokość. Sam popup zostaje bez `overflow: hidden`, bo ucięłby
123
+ strzałkę `::after` wiszącą na `top: 100%`. Karta poza popupem, bez limitu wysokości,
124
+ nie przewija się wcale.
125
+ - Pasek „Anuluj / Zapisz" w formularzu dodawania jest przypięty do dołu (`position: sticky`),
126
+ więc nie ucieka na koniec długiej listy pól.
127
+ - Legenda jako filtr: `KptMapLegend` (kpt-map-legend) z `aria-pressed`, liczniki per kategoria,
128
+ ukrycie kategorii usuwa ją także z grup.
129
+ - Dodawanie lokalizacji prawym przyciskiem myszy: formularz budowany ze schemy `category.fields`
130
+ na Signal Forms albo własny `ng-template kptMapCreateForm`. Poza `text`/`textarea`/`number`/
131
+ `date`/`select`/`multiselect`/`checkbox` schema przyjmuje `tel`, `email` i `url` (z walidacją
132
+ formatu), `image` — wybór pliku czytany jako `data:` **albo** wklejony adres, oraz `hours` —
133
+ edytor tygodnia dający `KptMapOpeningHours`.
134
+ Plik zdjęcia zostaje w przeglądarce: biblioteka nigdzie go nie wysyła i nie ma wiedzy
135
+ o storage'u aplikacji. Limit 2 MB jest realny — data URL puchnie o ~⅓, a cała wartość
136
+ leci w `locationCreate`.
137
+ - Widok startowy z presetu kraju (`KPT_MAP_COUNTRIES`, domyślnie Polska) oraz opcjonalne
138
+ ograniczenie przesuwania do jego zasięgu.
139
+ - Znacznik własnego położenia z geolokalizacji wraz z kręgiem dokładności i kontrolką
140
+ „wyśrodkuj na mnie"; źródło pozycji przez port DI `KPT_MAP_GEOLOCATION`
141
+ (`provideKptMapGeolocationStub()` dla Storybooka i testów).
142
+ - Silnik za interfejsem `KptMapEngine` — `provideKptLeafletMap()` dziś, adapter Google Maps
143
+ bez zmian w API komponentu.
144
+ - Rdzeń pure TS w publicznym API: `clusterLocations`, `countryPreset`, projekcja Mercatora
145
+ (`project`/`unproject`/`boundsOf`/`haversine`/`metersPerPixel`/`zoomForBounds`),
146
+ generatory HTML znaczników, formatowanie (`formatDistance`, `formatLatLng`) oraz godziny
147
+ otwarcia (`isOpenAt`, `isAlwaysOpen`, `nextChange`, `groupWeek`, `weekdayName`, `emptyWeek`,
148
+ `parseTime`, `formatTime`). Testy: `packages/angular/map/src/core/*.test.ts`.
149
+ `isOpenAt()` liczy dwa dni — bieżący i poprzedni — więc przedział `22:00–01:00` obejmuje
150
+ wczesne godziny dnia następnego; `24:00` to koniec doby, a nie przejście przez północ.
151
+ `groupWeek()` skleja sąsiadujące dni o identycznym rozkładzie w `pon.–pt.`, ale nie zawija
152
+ tygodnia przez granicę niedziela–poniedziałek. Nazwy dni idą z `Intl`, nie ze słownika —
153
+ karta i edytor godzin działają dla dowolnego locale bez dopisywania tłumaczeń.
154
+ - Nowe tokeny komponentowe `--kpt-map-*` (w tym `popup-width`, `popup-width-card`,
155
+ `popup-max-height`, `poi-media-radius`, `poi-row-hover`) oraz klucze i18n `map.*`,
156
+ `map.poi.*` i `map.field.*` (EN + PL).
157
+ - Cztery ikony dopisane do **wbudowanego** zestawu `KPT_ICONS`: `mail`, `share`,
158
+ `external-link`, `crosshair`. Karta miejsca ich potrzebuje, a komponent biblioteczny nie może
159
+ wymagać `provideKptTablerIcons()`.
160
+ - `KptInput` przyjmuje `type="time"`.
161
+ - `KptDatepicker` dostaje wejście `ariaLabel` (nazwa dostępna tam, gdzie pola nie opisuje
162
+ `<label for>` — np. dwie godziny w jednym wierszu) i w trybie `time` rysuje w polu ikonę
163
+ zegara zamiast kalendarza. Godziny otwarcia wybiera się właśnie nim
164
+ (`selectionMode="time"`, `minuteStep=5`), a nie natywnym `<input type="time">`: tamten
165
+ otwiera systemowy dropdown, którego nie da się ostylować.
166
+ - **Naprawiony kolor ikony kategorii `muted` poza mapą.** Legenda malowała glif zaszytą
167
+ bielą na tle koloru roli, a `--kpt-color-muted` to neutral-100 — token tła, nie treści.
168
+ Kategoria `muted` miała więc w legendzie ikonę praktycznie niewidoczną, choć jej znacznik
169
+ na mapie był poprawny (ten bierze `contrastVarOf()`). Legenda używa teraz `contrastVarOf()`,
170
+ a nowe `foregroundVarOf()` w publicznym API daje kolor glifu stojącego wprost na powierzchni
171
+ (bez własnego tła) — z niego korzysta wiersz kategorii w karcie miejsca.
172
+ - `renderKptIconSvg()` wydzielone z `kpt-icon` do publicznego API — mapa buduje z niego
173
+ znaczniki poza Angularem, więc oba miejsca mają jedno źródło prawdy.
174
+
175
+ **Nowa zależność opcjonalna:** `leaflet` jako `peerDependencies` + `peerDependenciesMeta.optional`.
176
+ To pierwsza zależność runtime biblioteki. Ładowana dynamicznym `import()`, więc aplikacje bez mapy
177
+ jej nie pobierają. Użycie mapy wymaga `pnpm add leaflet` i dołączenia `leaflet/dist/leaflet.css`
178
+ (Angular CLI: `angular.json → styles[]`, nie `import` w `main.ts`).
179
+
180
+ **Uwaga o kaskadzie:** nadpisania `.leaflet-*` w `map.component.scss` stoją poza `@layer` — celowo
181
+ i jako jedyne w bibliotece. `leaflet.css` nie jest warstwowany, a CSS spoza warstw wygrywa z każdą
182
+ regułą w `@layer` niezależnie od specyficzności.
183
+
184
+ - Podpowiadanie adresu w formularzu „dodaj lokalizację": pole z `autofill` (`city`, `address`,
185
+ `street`, `houseNumber`, `postcode`, `state`, `county`, `country`, `countryCode`, `label`)
186
+ wypełnia się z geokodowania wstecznego współrzędnych kliknięcia. Źródło przez port DI
187
+ `KPT_MAP_GEOCODER`; gotowy adapter `provideKptNominatimGeocoder()` jest **opt-in** —
188
+ biblioteka nie odpytuje cudzego serwera bez zgody aplikacji. Podpowiedź wypełnia wyłącznie
189
+ pola puste, więc nigdy nie kasuje tego, co użytkownik wpisał.
190
+
191
+ **Uwaga o kaflach:** domyślne `tile.openstreetmap.org` to usługa best-effort bez SLA, z wymogiem
192
+ widocznej atrybucji i zakazem pre-fetchowania. Do produkcji podstaw własny serwer przez `[tileUrl]`.
193
+ To samo dotyczy Nominatim (jedno zapytanie na sekundę, zakaz masowego odpytywania) — adapter sam
194
+ trzyma odstęp, ale produkcja powinna mieć własną instancję albo dostawcę komercyjnego.
195
+
196
+ ***
197
+
198
+ Przy okazji naprawiony **utajony błąd `KptDialog`**: kontrolki na CDK Overlay (`kpt-select`,
199
+ `kpt-datepicker`, `kpt-autocomplete`, `kpt-menu`) otwierały się w środku okna modalnego
200
+ niewidoczne. `showModal()` wynosi `<dialog>` do warstwy szczytowej przeglądarki, ponad całe
201
+ drzewo dokumentu, a kontener CDK Overlay wisi na `<body>` — żaden `z-index` tego nie przebija.
202
+ `kpt-dialog` przenosi teraz kontener overlayów do wnętrza okna na czas jego otwarcia i oddaje
203
+ go na miejsce przy zamknięciu.
204
+
205
+ Błąd był w bibliotece od początku, ale nieuruchomiony: żaden przykład nie umieszczał kontrolki
206
+ overlayowej w oknie modalnym. Ujawnił go formularz „dodaj lokalizację" z pól `date` i `select`.
207
+
208
+ **Nieruchomy nagłówek okna.** `.kpt-dialog` jest teraz kolumną flex: nagłówek z krzyżykiem
209
+ i stopka `[kptDialogFooter]` stoją, a pasek przewijania biegnie wyłącznie wzdłuż
210
+ `.kpt-dialog__body`. Wcześniej okno przewijało się w całości, razem z nagłówkiem. Treść
211
+ dostaje `flex: 0 1 auto` + `min-height: 0`, celowo nie `flex: 1` — krótkie okno (`kpt-confirm`)
212
+ dalej kurczy się do treści, zamiast rozciągać na `max-height`. `display: flex` jest scopowane
213
+ `&[open]`: regułę `dialog:not([open]) { display: none }` daje arkusz przeglądarki, a każda
214
+ deklaracja autorska ją bije — także z `@layer`, więc bez scopingu zamknięte okna zostałyby
215
+ widoczne. `overflow: hidden` na oknie celowo nie ma: przycięłoby panele CDK Overlay
216
+ przeniesione do jego wnętrza.
217
+
218
+ `.kpt-map .leaflet-container` dostaje `z-index: 0`, żeby zamknąć skalę z-index Leafleta
219
+ (panes 200–700, kontrolki 800) we własnym kontekście układania — bez tego popup, kontrolki
220
+ zoomu i menu prawego przycisku są malowane pod kaflami.
221
+
222
+ ***
223
+
224
+ Naprawiony też **utajony błąd `KptSwitch`, `KptCheckbox` i `KptRadioGroup`**: ukryty `<input>`
225
+ tych kontrolek jest `position: absolute`, ale ich element główny nie był pozycjonowany. Blokiem
226
+ zawierającym stawał się więc najbliższy pozycjonowany przodek — w praktyce kontener aplikacji —
227
+ przez co input uciekał poza przewijany obszar i rozpychał dokument, dając **drugi pasek
228
+ przewijania** obok paska treści. Kontrolki zatrzymują teraz swój input u siebie
229
+ (`position: relative` na elemencie głównym).
230
+
231
+ Widoczne dopiero, gdy któraś z tych kontrolek trafi dostatecznie nisko w długiej, przewijanej
232
+ stronie — stąd ujawniło się na stronie mapy, która ma pięć przełączników.
233
+
234
+ - Geokoder per mapa — nowe wejście `[geocoder]` w `kpt-map`, symetryczne do `[engine]`.
235
+
236
+ - `[geocoder]` przyjmuje `KptMapGeocoderPort` i wygrywa nad `KPT_MAP_GEOCODER` z DI. Trafia do
237
+ obu konsumentów portu naraz: wyszukiwarki adresów i podpowiadania adresu w formularzu
238
+ „dodaj lokalizację". To samo wejście mają `kpt-map-search` i `kpt-map-create-dialog`, gdy stoją
239
+ poza mapą. Bez niego nic się nie zmienia — port przychodzi z DI, jak dotąd.
240
+ - **Po co.** Service Specific Terms §6.2 i §14.2 wiążą geokoder Google z **mapą** Google, a nie
241
+ z aplikacją. Odkąd `[engine]` pozwala postawić dwa silniki na jednej stronie, globalny provider
242
+ nie umiał tej reguły dotrzymać: musiałby złamać ją po jednej ze stron. Teraz mapa Leafleta
243
+ z geokoderem OSM i mapa Google z geokoderem Google mieszczą się obok siebie.
244
+ - W odróżnieniu od `[engine]` port czytany jest **reaktywnie**: podmiana zeruje listę podpowiedzi
245
+ i od następnej frazy pyta nowego dostawcę.
246
+ - `loadGoogleMapsApi(options)` wyeksportowany z `@konce-pt/angular/map` (plus typy
247
+ `KptGoogleLoadOptions` i `KptGoogleMapsApi`) — ten sam loader, którego używa silnik. Adapter
248
+ geokodera Google **musi** iść przez SDK, a nie przez REST: web service
249
+ `maps.googleapis.com/maps/api/geocode/json` odrzuca klucze ograniczone po `Referer`
250
+ („API keys with referer restrictions cannot be used with this API"), a to jedyna restrykcja
251
+ mająca sens dla klucza leżącego jawnie w bundlu przeglądarki. CORS na tym endpoincie działa,
252
+ więc odmowa przychodzi jako `200` z `REQUEST_DENIED` w treści — łatwo ją przeoczyć.
253
+ Samego adaptera biblioteka nadal nie dostarcza; przykład od początku do końca stoi
254
+ w playgroundzie.
255
+
256
+ - Drugi silnik mapy — Google Maps — i wybór silnika per instancja przez wejście `[engine]`.
257
+
258
+ - `provideKptGoogleMap({ apiKey, mapId, language?, region?, version?, minZoom?, maxZoom?,
259
+ scrollWheelZoom? })` obok `provideKptLeafletMap()`. **Zero nowych zależności npm**: Google Maps
260
+ JavaScript API dogrywa się skryptem przy pierwszej mapie, tak jak Leaflet ładuje się dynamicznym
261
+ `import()`. Paczka `@googlemaps/js-api-loader` dokładałaby wersję do pilnowania w zamian za te
262
+ same kilkanaście linijek.
263
+ - **`mapId` jest wymagany.** Bez niego nie działa `AdvancedMarkerElement`, a znaczniki tej
264
+ biblioteki to gotowy HTML z `marker-html.ts` — starsze `Marker` go nie przyjmie.
265
+ - Nowe wejście **`[engine]`** (`KptMapEngineFactory | null`) wygrywa nad `KPT_MAP_ENGINE` z DI.
266
+ Dwie mapy o różnych silnikach mieszczą się dzięki temu na jednej stronie bez komponentów-opakowań.
267
+ `KPT_MAP_ENGINE` staje się wstrzykiwany opcjonalnie, a brak silnika w obu źródłach daje czytelny
268
+ komunikat z instrukcją zamiast `NullInjectorError` z nazwą tokenu.
269
+ - Kontrakt `KptMapEngine` **nie zmienił się o ani jedną sygnaturę** — to był cel tego interfejsu
270
+ i teraz się zweryfikował. Grupowanie, popup, legenda, menu prawego przycisku, wyszukiwarka
271
+ i formularz dodawania działają w obu silnikach z tego samego kodu.
272
+ - Adapter Google liczy `project()`/`unproject()` **rdzeniem** (`core/geo.ts`), a nie
273
+ `map.getProjection()`: ten zwraca `null` do pierwszego `idle` i tak czy owak wymaga przeliczenia
274
+ na piksele kontenera. Wspólna matematyka znaczy, że popup ląduje piksel w piksel tak samo w obu
275
+ silnikach, zgadza się z grupowaniem i obsługuje zoom ułamkowy, którego Google używa przy gestach.
276
+ - Kliknięcie w znacznik łapie listener DOM ze `stopPropagation()`, a nie `gmp-click`
277
+ z `gmpClickable`. Bez zatrzymania propagacji to samo kliknięcie doszłoby do mapy i `onMapClick()`
278
+ zamknąłby popup w tej samej klatce, w której go otwieramy. Adapter Leafleta robi dokładnie to
279
+ samo przez `L.DomEvent.stopPropagation`; przy okazji odpada zależność od zachowania `gmp-click`
280
+ w danej wersji API.
281
+ - Krąg dokładności przy silniku Google jest elementem z tłem, a nie `google.maps.Circle`: Circle
282
+ przyjmuje kolory w opcjach, a kontrakt repo wymaga stylowania tokenami `var(--kpt-*)`. Średnicę
283
+ liczy `metersPerPixel()` z rdzenia i odświeża na `zoom_changed`. Silnik trafia na host jako
284
+ `[data-engine]`, więc różnicę widać w jednej regule SCSS.
285
+ - `[tileUrl]` i `[attribution]` są przy silniku Google **ignorowane** — kafle i atrybucję dostarcza
286
+ Google, a jej zasłonięcie albo usunięcie łamie warunki usługi. `setTileSource()` i
287
+ `invalidateSize()` to tam świadome no-opy.
288
+
289
+ **Dlaczego to w ogóle powstało.** Google Maps Platform Service Specific Terms §6.2 (Geocoding API)
290
+ i §14.2 (Places API) zabraniają używać ich wyników „in conjunction with a non-Google map".
291
+ Dopóki `kpt-map` umiał tylko Leaflet z kaflami OSM, każdy adapter Google pod `KPT_MAP_GEOCODER`
292
+ był niezgodny z umową. Silnik Google zdejmuje to u źródła. Sama biblioteka adaptera geokodera
293
+ Google **nie dostarcza** — jego miejsce jest w aplikacji, bo wolno go użyć tylko z jednym
294
+ z dwóch silników.
295
+
296
+ - Wyszukiwarka adresów w `kpt-map` — `[search]` (domyślnie wyłączona) pokazuje w rogu mapy pole
297
+ z podpowiedziami; wybór podpowiedzi przenosi widok na znalezione miejsce.
298
+
299
+ - Port `KPT_MAP_GEOCODER` dostaje **opcjonalną** metodę `search(query, options)` obok istniejącego
300
+ `reverse()`. Opcjonalną celowo: porty napisane w aplikacjach przed jej powstaniem kompilują się
301
+ bez zmian, a `kpt-map` bez niej po prostu nie renderuje pola. Nowe typy `KptMapSuggestion`
302
+ (`{ id, label, at?, bounds?, address? }`), `KptMapSearchResult` (to samo z wymaganym `at`)
303
+ i `KptMapSearchOptions` w publicznym API; `address` to ten sam `KptMapAddress`, co przy
304
+ geokodowaniu wstecznym — bez drugiego modelu adresu.
305
+ - `KptNominatimGeocoder` implementuje `search()` na `/search` i wchodzi w **tę samą kolejkę**
306
+ co `reverse()`: polityka Nominatim liczy jedno zapytanie na sekundę na całego klienta,
307
+ a nie na metodę.
308
+ - **Kolejka adaptera Nominatim liczy odstęp od zegara**, a nie stałym `delay(1000)` przed każdym
309
+ zapytaniem. Wcześniej pierwsze zapytanie po dowolnie długiej bezczynności też płaciło sekundę
310
+ — przy podpowiadaniu w trakcie pisania to różnica między „działa" a „zacina się". Zmiana
311
+ dotyczy także istniejącego `reverse()`; górna granica jednego zapytania na sekundę
312
+ zostaje nienaruszona.
313
+ - Nowe w rdzeniu (pure TS, publiczne API): `isFittableBounds(bounds, minSpan?)` i `KPT_MIN_FIT_SPAN`
314
+ (0,001°, ~100 m) — czy prostokąt warto wpasowywać w kadr, czy potraktować jak punkt.
315
+ Geokodery obrysowują pojedynczy adres prostokątem rzędu 0,0001°, więc `fitBounds` wjeżdżałby
316
+ na nim na górny limit zoomu, a dwa sąsiednie adresy lądowałyby w różnych przybliżeniach.
317
+ Próg wystarczy przekroczyć w jednej osi — długa, wąska ulica dalej zasługuje na `fitBounds`.
318
+ Testy: `packages/angular/map/src/core/geo.test.ts`.
319
+ - Nowe wejścia `kpt-map`: `search`, `searchPlaceholder`, `searchOptions`
320
+ (`minLength` = 3, `debounce` = 300 ms, `limit` = 5, `countryCodes`), `searchZoom` = 16,
321
+ `searchMarker` = true. Nowe wyjście `searchSelect`.
322
+ - Wynik z własnym zasięgiem (miasto, region) wchodzi w kadr przez `fitBounds`, a pojedynczy
323
+ punkt dostaje `[searchZoom]` — dzięki temu każdy adres ląduje w tym samym, przewidywalnym
324
+ przybliżeniu, niezależnie od tego, jak dostawca obrysował budynek.
325
+ - Zapytania są nachylone ku presetowi `[country]` (albo `[bounds]`) przez `viewbox`. To sam
326
+ bias — przestawia kolejność wyników i niczego nie odcina; adres z zagranicy dalej da się
327
+ znaleźć. Twarde `countrycodes` dokłada się dopiero przy `restrictToCountry` (mapa i tak nie
328
+ pozwoli przesunąć widoku poza kraj) albo jawnie przez `searchOptions.countryCodes`.
329
+ - Tymczasowa pinezka wyniku idzie tą samą drogą co reszta znaczników — przez
330
+ `engine.setMarkers()` — więc przesuwa się z mapą klatka po klatce i **nie wymaga niczego
331
+ od silnika**. Nie należy do wyniku grupowania, więc kliknięcie w nią nic nie otwiera.
332
+ Znika po wyczyszczeniu pola i po kliknięciu w mapę.
333
+ - Nowy komponent `KptMapSearch` (kpt-map-search) w publicznym API — jak legenda, do postawienia
334
+ także poza mapą. Świadomie **nie** stoi na `kpt-autocomplete`: tamten filtruje opcje po stronie
335
+ klienta (`includes(q)`), więc wynik geokodera tolerancyjny na literówki wypadałby z listy;
336
+ jego wartością jest sam `string`, więc nie ma czym przewieźć współrzędnych; i wymaga CDK
337
+ Overlay, którego mapa poza tym nie potrzebuje. Panel podpowiedzi rysuje więc warstwa Angulara
338
+ nad mapą, tak samo jak popup i menu prawego przycisku (`z-index: 5`, nad tamtymi).
339
+ - Wzorzec combobox z `aria-activedescendant` (fokus zostaje w `<input>`), klawiatura ↑/↓/Enter,
340
+ pierwszy Esc zamyka listę, drugi czyści pole; liczba wyników i „brak wyników" w `aria-live`.
341
+ - **Port przyjmuje dostawców, którzy nie podają współrzędnych na liście.** `search()` zwraca teraz
342
+ `KptMapSuggestion` (`at` opcjonalne) zamiast `KptMapSearchResult`, a port może dopisać
343
+ `resolve(suggestion)`, które dokłada `lat`/`lng` **po wybraniu pozycji**. Tak działa Google Places
344
+ Autocomplete: oddaje `placeId` i tekst, a współrzędne dociąga osobny Place Details — na tym stoi
345
+ jego rozliczanie sesyjne, więc bez tego kroku podpowiadanie z Google kosztowałoby pięć wywołań
346
+ Place Details na każdy zestaw podpowiedzi zamiast jednego na wybór.
347
+ Zmiana jest **wstecznie zgodna**: `KptMapSearchResult` to teraz `KptMapSuggestion` z wymaganym
348
+ `at`, więc adaptery zwracające pełne wyniki (Nominatim, Photon, każdy geokoder pytany wprost)
349
+ spełniają interfejs bez zmiany linijki, a `selected`/`searchSelect` dalej mają ten sam typ.
350
+ - Obsługa `resolve()` siedzi w `KptMapSearch`, nie w `kpt-map` — dzięki temu **`selected`
351
+ i `searchSelect` zawsze niosą współrzędne**, także gdy pole stoi poza mapą, i aplikacja nigdy
352
+ nie musi sprawdzać, czy `at` jest. Podpowiedź ze współrzędnymi emituje się natychmiast (zero
353
+ opóźnienia dla Nominatima); bez nich panel pokazuje „Szukam…" i `aria-busy`, a nieudane
354
+ dociągnięcie zostawia etykietę w polu i wraca do listy zamiast wywracać pole. Gdy port nie ma
355
+ `resolve()`, podpowiedzi bez `at` w ogóle nie trafiają na listę — pozycja, której kliknięcie
356
+ nie miałoby dokąd polecieć, jest gorsza niż jej brak.
357
+ - **`KptNominatimOptions.minIntervalMs`** — sekundowy odstęp przestał być stałą w kodzie.
358
+ Domyślna zależy od `baseUrl`: publiczna instancja OSM dostaje 1000 ms jak dotąd, własna 0,
359
+ bo regulamin OSMF nie obowiązuje na cudzym serwerze. `0` zdejmuje też kolejkowanie.
360
+ Wcześniej własna instancja Nominatima płaciła sekundę za każdą podpowiedź bez żadnego powodu
361
+ i nie dało się tego wyłączyć opcją.
362
+ - `provideKptMapGeocoderStub(address, results?, options?)` przyjmuje teraz listę podpowiedzi —
363
+ Storybook i testy dostają wyszukiwarkę bez ruchu sieciowego. Trzeci argument
364
+ (`{ deferCoordinates: true }`) każe atrapie udawać dostawcę bez współrzędnych na liście, żeby
365
+ ścieżkę `resolve()` dało się pokazać bez prawdziwego klucza API. Sygnatura wstecznie zgodna.
366
+ - Nowe tokeny `--kpt-map-search-width` i `--kpt-map-search-panel-max-height` oraz klucze
367
+ i18n `map.search.*` (EN + PL).
368
+ - Wyszukiwarkę pozycjonuje **opakowanie** `<div class="kpt-map__search">`, a nie klasa
369
+ postawiona wprost na `<kpt-map-search>`. Jeden element z obiema klasami znaczył dwie reguły
370
+ o identycznej specyficzności (0,1,0) w tej samej warstwie `kpt.components`, więc rozstrzygała
371
+ kolejność wstrzyknięcia stylów — a Angular wstrzykuje style dziecka po stylach rodzica.
372
+ `.kpt-map-search { position: relative }` (blok zawierający dla panelu podpowiedzi) wygrywało
373
+ z `absolute` rodzica, pole wypadało do normalnego przepływu za `.kpt-map__canvas`
374
+ (`height: 100%`) i znikało przycięte przez `overflow: hidden` widoku. Element był w DOM
375
+ z poprawnymi atrybutami — usterkę widać wyłącznie na zrzucie ekranu.
376
+
377
+ **Uwaga o polityce Nominatim.** Podpowiadanie w trakcie pisania z natury generuje serie zapytań,
378
+ więc pole broni się progiem `minLength` i `debounce`, a adapter wspólną kolejką i przerywaniem
379
+ porzuconych zapytań przez `AbortController`. Do produkcji dalej obowiązuje własna instancja
380
+ (`baseUrl`) albo dostawca komercyjny pod `KPT_MAP_GEOCODER` — publiczna instancja OSM zabrania
381
+ masowego odpytywania i może zablokować ruch bez uprzedzenia.
382
+
3
383
  ## 0.7.0
4
384
 
5
385
  ### Minor Changes
@@ -62,7 +442,7 @@
62
442
 
63
443
  ### Minor Changes
64
444
 
65
- - eecb506: Pierwsze wydanie Koncept UI: design tokeny (OKLCH, motyw jasny/ciemny), bazowy CSS,
445
+ - Pierwsze wydanie Koncept UI: design tokeny (OKLCH, motyw jasny/ciemny), bazowy CSS,
66
446
  oraz komponenty Angular 22 — layout (app-shell/toolbar/sidenav/card/divider), formularze
67
447
  na Signal Forms (input/select/checkbox/radio/switch/datepicker/form-field), flagowa tabela
68
448
  danych (kpt-data-table) z paginatorem, oraz feedback (alert/dialog/toast/tooltip).