@aiaiai-pt/design-system 0.5.6 → 0.5.7

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.
@@ -1,25 +1,23 @@
1
1
  <!--
2
2
  @component GeoSearch
3
3
 
4
- Geocoding search input powered by Nominatim (OpenStreetMap).
5
- Wraps Combobox with built-in address/place search and debounced API calls.
6
- Emits lon/lat coordinates when a result is selected.
4
+ Bidirectional geocoding input powered by Nominatim (OpenStreetMap).
5
+ Forward: type address get coordinates.
6
+ Reverse: set `coords` prop shows resolved address.
7
7
  Consumes --combobox-* tokens from components.css.
8
8
 
9
- @example Basic
9
+ @example Forward geocoding
10
10
  <GeoSearch onlocation={(lon, lat) => console.log(lon, lat)} />
11
11
 
12
- @example With label and bounding box bias
12
+ @example Bidirectional (MapPicker sets coords on click → address shows)
13
13
  <GeoSearch
14
- label="Search location"
15
- placeholder="City, address, or place..."
16
- viewbox={[-9.5, 38.5, -8.8, 39.0]}
17
- onlocation={(lon, lat) => { mapCenter = [lon, lat]; mapZoom = 15; }}
14
+ bind:coords={pickedCoords}
15
+ onlocation={(lon, lat) => { mapCenter = [lon, lat]; }}
18
16
  />
19
17
 
20
- @example Custom provider URL
18
+ @example With viewbox bias
21
19
  <GeoSearch
22
- providerUrl="https://my-nominatim.example.com/search"
20
+ viewbox={[-9.5, 38.5, -8.8, 39.0]}
23
21
  onlocation={(lon, lat) => console.log(lon, lat)}
24
22
  />
25
23
  -->
@@ -45,6 +43,8 @@
45
43
  viewbox = undefined,
46
44
  /** @type {((lon: number, lat: number, item: { label: string, value: string }) => void) | undefined} */
47
45
  onlocation = undefined,
46
+ /** @type {[number, number] | undefined} — set externally to reverse-geocode and show address */
47
+ coords = $bindable(undefined),
48
48
  /** @type {string} */
49
49
  class: className = '',
50
50
  ...rest
@@ -55,6 +55,36 @@
55
55
  let loading = $state(false);
56
56
  let value = $state('');
57
57
  let debounceTimer = 0;
58
+ let lastReversed = '';
59
+
60
+ // Reverse geocode when coords change (map click or initial load)
61
+ $effect(() => {
62
+ if (!coords) return;
63
+ const key = `${coords[0]},${coords[1]}`;
64
+ if (key === lastReversed) return;
65
+ lastReversed = key;
66
+ reverseGeocode(coords[0], coords[1]);
67
+ });
68
+
69
+ async function reverseGeocode(lon, lat) {
70
+ loading = true;
71
+ try {
72
+ const base = providerUrl.replace('/search', '/reverse');
73
+ const resp = await fetch(`${base}?lon=${lon}&lat=${lat}&format=json`, {
74
+ headers: { 'Accept': 'application/json' },
75
+ });
76
+ if (!resp.ok) return;
77
+ const result = await resp.json();
78
+ if (result.display_name) {
79
+ value = `${lon},${lat}`;
80
+ items = [{ value: `${lon},${lat}`, label: result.display_name, description: result.type?.replace(/_/g, ' ') ?? '' }];
81
+ }
82
+ } catch {
83
+ // Reverse geocoding failed — leave search bar as-is
84
+ } finally {
85
+ loading = false;
86
+ }
87
+ }
58
88
 
59
89
  /** Nominatim rate limit: 1 req/sec. Debounce at 400ms. */
60
90
  function handleSearch(query) {
@@ -4,11 +4,15 @@
4
4
  Interactive map for selecting a point or drawing a polygon.
5
5
  OpenLayers with configurable tiles and Draw interaction.
6
6
  Draw sketch styled with DS tokens (not OL blue default).
7
+ Built-in GeoSearch (Nominatim) for address lookup + pan.
7
8
  Consumes --map-* tokens from components.css.
8
9
 
9
- @example Point selection
10
+ @example Point selection with search
10
11
  <MapPicker mode="point" onchange={(coords) => console.log(coords)} />
11
12
 
13
+ @example Without search
14
+ <MapPicker mode="point" search={false} onchange={(coords) => console.log(coords)} />
15
+
12
16
  @example Polygon drawing
13
17
  <MapPicker mode="polygon" onchange={(coords) => console.log(coords)} />
14
18
  -->
@@ -19,6 +23,7 @@
19
23
  <script>
20
24
  import { fromLonLat, toLonLat } from 'ol/proj.js';
21
25
  import { createTileLayer, createMapStyles, watchTheme, renderMapError } from './map-utils.js';
26
+ import GeoSearch from './GeoSearch.svelte';
22
27
 
23
28
  let {
24
29
  /** @type {'point' | 'polygon'} */
@@ -37,6 +42,12 @@
37
42
  error = undefined,
38
43
  /** @type {boolean} */
39
44
  disabled = false,
45
+ /** @type {boolean} — show GeoSearch bar above map */
46
+ search = true,
47
+ /** @type {string} — Nominatim-compatible search endpoint */
48
+ searchProviderUrl = undefined,
49
+ /** @type {[number, number, number, number] | undefined} — viewbox bias for search */
50
+ searchViewbox = undefined,
40
51
  /** @type {import('./map-utils.js').TileSourceConfig} */
41
52
  tileSource = { type: 'osm' },
42
53
  /** @type {((coords: [number, number] | number[][]) => void) | undefined} */
@@ -57,6 +68,45 @@
57
68
 
58
69
  /** @type {HTMLElement | undefined} */
59
70
  let container = $state();
71
+ /** @type {import('ol/Map.js').default | undefined} */
72
+ let _map = $state();
73
+ /** @type {any} — VectorSource for placing markers via search */
74
+ let _vectorSource;
75
+ /** @type {any} */
76
+ let _Feature;
77
+ /** @type {any} */
78
+ let _Point;
79
+
80
+ // Bidirectional coords for GeoSearch: search → map, map click → reverse geocode
81
+ let searchCoords = $state(/** @type {[number, number] | undefined} */ (undefined));
82
+ let initialReverseDone = false;
83
+
84
+ // Set searchCoords from initial value (triggers reverse geocode in GeoSearch)
85
+ $effect(() => {
86
+ if (!initialReverseDone && value) {
87
+ searchCoords = /** @type {[number, number]} */ ([...value]);
88
+ initialReverseDone = true;
89
+ }
90
+ });
91
+
92
+ function handleGeoLocation(lon, lat) {
93
+ if (!_map) return;
94
+ const view = _map.getView();
95
+ if (view) view.animate({ center: fromLonLat([lon, lat]), zoom: 16, duration: 400 });
96
+
97
+ // In point mode, also place a marker and emit the value
98
+ if (mode === 'point' && _vectorSource && _Feature && _Point) {
99
+ _vectorSource.clear();
100
+ _vectorSource.addFeature(new _Feature({ geometry: new _Point(fromLonLat([lon, lat])) }));
101
+ value = [lon, lat];
102
+ onchange?.([lon, lat]);
103
+ }
104
+ }
105
+
106
+ /** Called by MapPicker when user clicks to place a point — triggers reverse geocoding */
107
+ function handleMapPointPlaced(lon, lat) {
108
+ searchCoords = [lon, lat];
109
+ }
60
110
 
61
111
  $effect(() => {
62
112
  if (!container || disabled) return;
@@ -95,6 +145,9 @@
95
145
  if (disposed) return;
96
146
 
97
147
  const vectorSource = new VectorSource();
148
+ _vectorSource = vectorSource;
149
+ _Feature = Feature;
150
+ _Point = Point;
98
151
 
99
152
  if (value && mode === 'point') {
100
153
  vectorSource.addFeature(new Feature({ geometry: new Point(fromLonLat(value)) }));
@@ -133,6 +186,7 @@
133
186
  const coords = /** @type {import('ol/geom/Point.js').default} */ (geom).getCoordinates();
134
187
  const wgs84 = /** @type {[number, number]} */ (toLonLat(coords));
135
188
  value = wgs84;
189
+ handleMapPointPlaced(wgs84[0], wgs84[1]);
136
190
  onchange?.(wgs84);
137
191
  } else {
138
192
  const coords = /** @type {import('ol/geom/Polygon.js').default} */ (geom).getCoordinates()[0];
@@ -145,14 +199,14 @@
145
199
 
146
200
  map = new OlMap({
147
201
  target: container,
148
- layers: [tileLayer, vectorLayer],
149
- view: new View({
202
+ layers: [tileLayer, vectorLayer], view: new View({
150
203
  center: initialCenter,
151
204
  zoom,
152
205
  }),
153
206
  });
154
207
 
155
208
  map.addInteraction(drawInteraction);
209
+ _map = map;
156
210
 
157
211
  disposeTheme = watchTheme(() => {
158
212
  styles.refresh();
@@ -173,6 +227,17 @@
173
227
  <label class="map-picker-label" for={pickerId}>{label}</label>
174
228
  {/if}
175
229
 
230
+ {#if search && !disabled}
231
+ <GeoSearch
232
+ placeholder="Search address or place..."
233
+ providerUrl={searchProviderUrl}
234
+ viewbox={searchViewbox}
235
+ onlocation={handleGeoLocation}
236
+ bind:coords={searchCoords}
237
+ size="sm"
238
+ />
239
+ {/if}
240
+
176
241
  <div
177
242
  bind:this={container}
178
243
  id={pickerId}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/design-system",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Design system tokens and Svelte components for aiaiai products",
5
5
  "license": "MIT",
6
6
  "type": "module",