@mapfirst.ai/react 0.0.15 → 0.0.16
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/README.md +231 -298
- package/dist/index.d.mts +96 -295
- package/dist/index.d.ts +96 -295
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/EXAMPLES.md +0 -740
package/README.md
CHANGED
|
@@ -8,7 +8,6 @@ React hooks and components for the MapFirst SDK supporting MapLibre, Google Maps
|
|
|
8
8
|
- 🔍 **SmartFilter Component**: AI-powered search with interactive filter chips
|
|
9
9
|
- ⚛️ **React Hooks**: Reactive state management for properties, filters, and map state
|
|
10
10
|
- 🎨 **Customizable**: Native React styles (CSS-in-JS) - no framework dependencies
|
|
11
|
-
- 📱 **Responsive**: Adapts to different screen sizes and orientations
|
|
12
11
|
- ♿ **Accessible**: Full keyboard navigation and ARIA support
|
|
13
12
|
- 🌍 **i18n Ready**: Built-in translations with extensibility
|
|
14
13
|
|
|
@@ -25,11 +24,15 @@ yarn add @mapfirst/react @mapfirst/core
|
|
|
25
24
|
## Quick Start - SmartFilter Component
|
|
26
25
|
|
|
27
26
|
```tsx
|
|
28
|
-
import {
|
|
27
|
+
import { useMapFirst, SmartFilter } from "@mapfirst/react";
|
|
29
28
|
import { useState } from "react";
|
|
30
29
|
|
|
31
30
|
function App() {
|
|
32
|
-
const {
|
|
31
|
+
const {
|
|
32
|
+
instance: mapFirst,
|
|
33
|
+
state,
|
|
34
|
+
smartFilterSearch,
|
|
35
|
+
} = useMapFirst({
|
|
33
36
|
initialLocationData: {
|
|
34
37
|
city: "New York",
|
|
35
38
|
country: "United States",
|
|
@@ -45,7 +48,7 @@ function App() {
|
|
|
45
48
|
filters={filters}
|
|
46
49
|
isSearching={state?.isSearching}
|
|
47
50
|
onSearch={async (query) => {
|
|
48
|
-
|
|
51
|
+
await smartFilterSearch.search({ query });
|
|
49
52
|
}}
|
|
50
53
|
onFilterChange={setFilters}
|
|
51
54
|
/>
|
|
@@ -67,7 +70,7 @@ The React SDK supports a two-phase initialization pattern:
|
|
|
67
70
|
```tsx
|
|
68
71
|
import React, { useEffect, useRef, useState } from "react";
|
|
69
72
|
import maplibregl from "maplibre-gl";
|
|
70
|
-
import {
|
|
73
|
+
import { useMapFirst } from "@mapfirst/react";
|
|
71
74
|
import "maplibre-gl/dist/maplibre-gl.css";
|
|
72
75
|
|
|
73
76
|
function MapLibreExample() {
|
|
@@ -75,7 +78,13 @@ function MapLibreExample() {
|
|
|
75
78
|
const [map, setMap] = useState<maplibregl.Map | null>(null);
|
|
76
79
|
|
|
77
80
|
// Phase 1: Create SDK instance with location data
|
|
78
|
-
const {
|
|
81
|
+
const {
|
|
82
|
+
instance: mapFirst,
|
|
83
|
+
state,
|
|
84
|
+
attachMapLibre,
|
|
85
|
+
smartFilterSearch,
|
|
86
|
+
boundsSearch,
|
|
87
|
+
} = useMapFirst({
|
|
79
88
|
initialLocationData: {
|
|
80
89
|
city: "Paris",
|
|
81
90
|
country: "France",
|
|
@@ -110,14 +119,15 @@ function MapLibreExample() {
|
|
|
110
119
|
}, []);
|
|
111
120
|
|
|
112
121
|
// Phase 2: Attach map to SDK
|
|
113
|
-
|
|
114
|
-
mapFirst
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (map && mapFirst) {
|
|
124
|
+
attachMapLibre(map, maplibregl, {
|
|
125
|
+
onMarkerClick: (marker) => {
|
|
126
|
+
console.log("Marker clicked:", marker);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}, [map, mapFirst, attachMapLibre]);
|
|
121
131
|
|
|
122
132
|
return (
|
|
123
133
|
<div ref={mapContainerRef} style={{ width: "100%", height: "600px" }} />
|
|
@@ -129,14 +139,18 @@ function MapLibreExample() {
|
|
|
129
139
|
|
|
130
140
|
```tsx
|
|
131
141
|
import React, { useEffect, useRef, useState } from "react";
|
|
132
|
-
import {
|
|
142
|
+
import { useMapFirst } from "@mapfirst/react";
|
|
133
143
|
|
|
134
144
|
function GoogleMapsExample() {
|
|
135
145
|
const mapContainerRef = useRef<HTMLDivElement>(null);
|
|
136
146
|
const [map, setMap] = useState<google.maps.Map | null>(null);
|
|
137
147
|
|
|
138
148
|
// Phase 1: Create SDK instance
|
|
139
|
-
const {
|
|
149
|
+
const {
|
|
150
|
+
instance: mapFirst,
|
|
151
|
+
state,
|
|
152
|
+
attachGoogle,
|
|
153
|
+
} = useMapFirst({
|
|
140
154
|
initialLocationData: {
|
|
141
155
|
city: "Tokyo",
|
|
142
156
|
country: "Japan",
|
|
@@ -158,14 +172,15 @@ function GoogleMapsExample() {
|
|
|
158
172
|
}, []);
|
|
159
173
|
|
|
160
174
|
// Phase 2: Attach map to SDK
|
|
161
|
-
|
|
162
|
-
mapFirst
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (map && mapFirst) {
|
|
177
|
+
attachGoogle(map, window.google, {
|
|
178
|
+
onMarkerClick: (marker) => {
|
|
179
|
+
console.log("Marker clicked:", marker);
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}, [map, mapFirst, attachGoogle]);
|
|
169
184
|
|
|
170
185
|
return (
|
|
171
186
|
<div ref={mapContainerRef} style={{ width: "100%", height: "600px" }} />
|
|
@@ -178,7 +193,7 @@ function GoogleMapsExample() {
|
|
|
178
193
|
```tsx
|
|
179
194
|
import React, { useEffect, useRef, useState } from "react";
|
|
180
195
|
import mapboxgl from "mapbox-gl";
|
|
181
|
-
import {
|
|
196
|
+
import { useMapFirst } from "@mapfirst/react";
|
|
182
197
|
import "mapbox-gl/dist/mapbox-gl.css";
|
|
183
198
|
|
|
184
199
|
function MapboxExample() {
|
|
@@ -186,7 +201,11 @@ function MapboxExample() {
|
|
|
186
201
|
const [map, setMap] = useState<mapboxgl.Map | null>(null);
|
|
187
202
|
|
|
188
203
|
// Phase 1: Create SDK instance
|
|
189
|
-
const {
|
|
204
|
+
const {
|
|
205
|
+
instance: mapFirst,
|
|
206
|
+
state,
|
|
207
|
+
attachMapbox,
|
|
208
|
+
} = useMapFirst({
|
|
190
209
|
initialLocationData: {
|
|
191
210
|
city: "London",
|
|
192
211
|
country: "United Kingdom",
|
|
@@ -217,14 +236,15 @@ function MapboxExample() {
|
|
|
217
236
|
}, []);
|
|
218
237
|
|
|
219
238
|
// Phase 2: Attach map to SDK
|
|
220
|
-
|
|
221
|
-
mapFirst
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (map && mapFirst) {
|
|
241
|
+
attachMapbox(map, mapboxgl, {
|
|
242
|
+
onMarkerClick: (marker) => {
|
|
243
|
+
console.log("Marker clicked:", marker);
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}, [map, mapFirst, attachMapbox]);
|
|
228
248
|
|
|
229
249
|
return (
|
|
230
250
|
<div ref={mapContainerRef} style={{ width: "100%", height: "600px" }} />
|
|
@@ -236,10 +256,18 @@ function MapboxExample() {
|
|
|
236
256
|
|
|
237
257
|
### Accessing SDK Methods and Reactive State
|
|
238
258
|
|
|
239
|
-
All SDK methods are available through the `
|
|
259
|
+
All SDK methods are available through the `instance`, and state updates automatically trigger React re-renders:
|
|
240
260
|
|
|
241
261
|
```tsx
|
|
242
|
-
const {
|
|
262
|
+
const {
|
|
263
|
+
instance: mapFirst,
|
|
264
|
+
state,
|
|
265
|
+
setPrimaryType,
|
|
266
|
+
setSelectedMarker,
|
|
267
|
+
propertiesSearch,
|
|
268
|
+
smartFilterSearch,
|
|
269
|
+
boundsSearch,
|
|
270
|
+
} = useMapFirst({
|
|
243
271
|
/* ... */
|
|
244
272
|
});
|
|
245
273
|
|
|
@@ -256,11 +284,9 @@ useEffect(() => {
|
|
|
256
284
|
}
|
|
257
285
|
}, [mapFirst]);
|
|
258
286
|
|
|
259
|
-
// Run search
|
|
287
|
+
// Run properties search using the hook's search method
|
|
260
288
|
const handleSearch = async () => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
await mapFirst.runPropertiesSearch({
|
|
289
|
+
await propertiesSearch.search({
|
|
264
290
|
body: {
|
|
265
291
|
city: "Paris",
|
|
266
292
|
country: "France",
|
|
@@ -274,7 +300,23 @@ const handleSearch = async () => {
|
|
|
274
300
|
});
|
|
275
301
|
};
|
|
276
302
|
|
|
277
|
-
//
|
|
303
|
+
// Run smart filter search
|
|
304
|
+
const handleSmartSearch = async (query: string) => {
|
|
305
|
+
await smartFilterSearch.search({ query });
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Perform bounds search
|
|
309
|
+
const handleBoundsSearch = async () => {
|
|
310
|
+
await boundsSearch.perform();
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Set primary type
|
|
314
|
+
setPrimaryType("Restaurant");
|
|
315
|
+
|
|
316
|
+
// Set selected marker
|
|
317
|
+
setSelectedMarker(123456);
|
|
318
|
+
|
|
319
|
+
// Set filters directly on instance
|
|
278
320
|
mapFirst?.setFilters({
|
|
279
321
|
checkIn: new Date("2024-06-01"),
|
|
280
322
|
checkOut: new Date("2024-06-07"),
|
|
@@ -283,48 +325,82 @@ mapFirst?.setFilters({
|
|
|
283
325
|
});
|
|
284
326
|
```
|
|
285
327
|
|
|
286
|
-
###
|
|
328
|
+
### Using Search Methods
|
|
287
329
|
|
|
288
|
-
|
|
330
|
+
The unified hook provides search methods with their own loading states:
|
|
289
331
|
|
|
290
332
|
```tsx
|
|
291
|
-
const {
|
|
333
|
+
const {
|
|
334
|
+
instance: mapFirst,
|
|
335
|
+
state,
|
|
336
|
+
propertiesSearch,
|
|
337
|
+
smartFilterSearch,
|
|
338
|
+
boundsSearch,
|
|
339
|
+
} = useMapFirst({
|
|
292
340
|
/* ... */
|
|
293
341
|
});
|
|
294
342
|
|
|
295
|
-
//
|
|
296
|
-
const
|
|
343
|
+
// Properties search with loading state
|
|
344
|
+
const handlePropertiesSearch = async () => {
|
|
345
|
+
try {
|
|
346
|
+
await propertiesSearch.search({
|
|
347
|
+
body: {
|
|
348
|
+
city: "Paris",
|
|
349
|
+
country: "France",
|
|
350
|
+
filters: {
|
|
351
|
+
checkIn: new Date(),
|
|
352
|
+
checkOut: new Date(Date.now() + 86400000 * 3),
|
|
353
|
+
numAdults: 2,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
} catch (err) {
|
|
358
|
+
console.error("Search failed:", propertiesSearch.error);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
297
361
|
|
|
298
|
-
//
|
|
299
|
-
const
|
|
362
|
+
// Smart filter search with loading state
|
|
363
|
+
const handleSmartSearch = async (query: string) => {
|
|
364
|
+
try {
|
|
365
|
+
await smartFilterSearch.search({ query });
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error("Search failed:", smartFilterSearch.error);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
300
370
|
|
|
301
|
-
//
|
|
302
|
-
const
|
|
371
|
+
// Bounds search with loading state
|
|
372
|
+
const handleBoundsSearch = async () => {
|
|
373
|
+
if (!state?.pendingBounds) return;
|
|
303
374
|
|
|
304
|
-
|
|
305
|
-
|
|
375
|
+
try {
|
|
376
|
+
await boundsSearch.perform();
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.error("Search failed:", boundsSearch.error);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
306
381
|
|
|
307
382
|
return (
|
|
308
383
|
<div>
|
|
309
|
-
<
|
|
384
|
+
<button
|
|
385
|
+
onClick={handlePropertiesSearch}
|
|
386
|
+
disabled={propertiesSearch.isLoading}
|
|
387
|
+
>
|
|
388
|
+
{propertiesSearch.isLoading ? "Searching..." : "Search Properties"}
|
|
389
|
+
</button>
|
|
310
390
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
onChange={(e) => setPrimaryType(e.target.value as PropertyType)}
|
|
391
|
+
<button
|
|
392
|
+
onClick={() => handleSmartSearch("hotels with pool")}
|
|
393
|
+
disabled={smartFilterSearch.isLoading}
|
|
315
394
|
>
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
<button onClick={() => setSelectedMarker(null)}>Clear Selection</button>
|
|
326
|
-
</div>
|
|
327
|
-
)}
|
|
395
|
+
{smartFilterSearch.isLoading ? "Searching..." : "Smart Search"}
|
|
396
|
+
</button>
|
|
397
|
+
|
|
398
|
+
<button
|
|
399
|
+
onClick={handleBoundsSearch}
|
|
400
|
+
disabled={boundsSearch.isSearching || !state?.pendingBounds}
|
|
401
|
+
>
|
|
402
|
+
{boundsSearch.isSearching ? "Searching..." : "Search This Area"}
|
|
403
|
+
</button>
|
|
328
404
|
</div>
|
|
329
405
|
);
|
|
330
406
|
```
|
|
@@ -332,7 +408,7 @@ return (
|
|
|
332
408
|
### State Management with Callbacks
|
|
333
409
|
|
|
334
410
|
```tsx
|
|
335
|
-
const mapFirst =
|
|
411
|
+
const { instance: mapFirst, state } = useMapFirst({
|
|
336
412
|
initialLocationData: {
|
|
337
413
|
city: "Paris",
|
|
338
414
|
country: "France",
|
|
@@ -357,7 +433,7 @@ const mapFirst = useMapFirstCore({
|
|
|
357
433
|
### Configuring Map Behavior
|
|
358
434
|
|
|
359
435
|
```tsx
|
|
360
|
-
const mapFirst =
|
|
436
|
+
const { instance: mapFirst, state } = useMapFirst({
|
|
361
437
|
initialLocationData: {
|
|
362
438
|
city: "New York",
|
|
363
439
|
country: "United States",
|
|
@@ -377,13 +453,40 @@ const mapFirst = useMapFirstCore({
|
|
|
377
453
|
|
|
378
454
|
## API Reference
|
|
379
455
|
|
|
380
|
-
### `
|
|
456
|
+
### `useMapFirst(options)`
|
|
457
|
+
|
|
458
|
+
Comprehensive hook for MapFirst SDK with all functionality in one place. Creates a MapFirstCore instance with reactive state and provides all necessary methods.
|
|
381
459
|
|
|
382
|
-
|
|
460
|
+
**Returns:**
|
|
383
461
|
|
|
384
|
-
|
|
462
|
+
```typescript
|
|
463
|
+
{
|
|
464
|
+
instance: MapFirstCore | null,
|
|
465
|
+
state: MapState | null,
|
|
466
|
+
setPrimaryType: (type: PropertyType) => void,
|
|
467
|
+
setSelectedMarker: (id: number | null) => void,
|
|
468
|
+
propertiesSearch: {
|
|
469
|
+
search: (options) => Promise<any>,
|
|
470
|
+
isLoading: boolean,
|
|
471
|
+
error: Error | null
|
|
472
|
+
},
|
|
473
|
+
smartFilterSearch: {
|
|
474
|
+
search: (options) => Promise<any>,
|
|
475
|
+
isLoading: boolean,
|
|
476
|
+
error: Error | null
|
|
477
|
+
},
|
|
478
|
+
boundsSearch: {
|
|
479
|
+
perform: () => Promise<any>,
|
|
480
|
+
isSearching: boolean,
|
|
481
|
+
error: Error | null
|
|
482
|
+
},
|
|
483
|
+
attachMapLibre: (map, maplibregl, options?) => void,
|
|
484
|
+
attachGoogle: (map, google, options?) => void,
|
|
485
|
+
attachMapbox: (map, mapboxgl, options?) => void
|
|
486
|
+
}
|
|
487
|
+
```
|
|
385
488
|
|
|
386
|
-
- `
|
|
489
|
+
- `instance` - The SDK instance for calling methods
|
|
387
490
|
- `state` - Reactive state that updates when SDK state changes
|
|
388
491
|
- `properties` - Array of properties
|
|
389
492
|
- `selectedPropertyId` - Currently selected property ID
|
|
@@ -395,6 +498,14 @@ Creates a MapFirst SDK instance and returns reactive state.
|
|
|
395
498
|
- `center` - Map center coordinates
|
|
396
499
|
- `zoom` - Current zoom level
|
|
397
500
|
- `activeLocation` - Active location data
|
|
501
|
+
- `setPrimaryType` - Function to change the primary property type
|
|
502
|
+
- `setSelectedMarker` - Function to select/deselect markers
|
|
503
|
+
- `propertiesSearch` - Object with search method, loading state, and error
|
|
504
|
+
- `smartFilterSearch` - Object with search method, loading state, and error
|
|
505
|
+
- `boundsSearch` - Object with perform method, searching state, and error
|
|
506
|
+
- `attachMapLibre` - Function to attach MapLibre map
|
|
507
|
+
- `attachGoogle` - Function to attach Google Maps
|
|
508
|
+
- `attachMapbox` - Function to attach Mapbox map
|
|
398
509
|
|
|
399
510
|
**Options:**
|
|
400
511
|
|
|
@@ -412,263 +523,85 @@ Creates a MapFirst SDK instance and returns reactive state.
|
|
|
412
523
|
- `primaryType?` - Primary property type
|
|
413
524
|
- `autoSelectOnClick?` - Auto-select markers on click
|
|
414
525
|
|
|
415
|
-
###
|
|
526
|
+
### Map Attachment Methods
|
|
416
527
|
|
|
417
|
-
|
|
528
|
+
The hook returns three functions for attaching different map types:
|
|
418
529
|
|
|
419
|
-
|
|
530
|
+
**`attachMapLibre(map, maplibregl, options?)`**
|
|
420
531
|
|
|
421
|
-
- `mapFirst` - SDK instance
|
|
422
532
|
- `map` - MapLibre map instance
|
|
423
533
|
- `maplibregl` - MapLibre GL namespace
|
|
424
|
-
- `onMarkerClick
|
|
425
|
-
|
|
426
|
-
### `useGoogleMapsAttachment(options)`
|
|
534
|
+
- `options?.onMarkerClick` - Optional marker click handler
|
|
427
535
|
|
|
428
|
-
|
|
536
|
+
**`attachGoogle(map, google, options?)`**
|
|
429
537
|
|
|
430
|
-
**Options:**
|
|
431
|
-
|
|
432
|
-
- `mapFirst` - SDK instance
|
|
433
538
|
- `map` - Google Maps instance
|
|
434
539
|
- `google` - Google Maps namespace
|
|
435
|
-
- `onMarkerClick
|
|
436
|
-
|
|
437
|
-
### `useMapboxAttachment(options)`
|
|
540
|
+
- `options?.onMarkerClick` - Optional marker click handler
|
|
438
541
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
**Options:**
|
|
542
|
+
**`attachMapbox(map, mapboxgl, options?)`**
|
|
442
543
|
|
|
443
|
-
- `mapFirst` - SDK instance
|
|
444
544
|
- `map` - Mapbox map instance
|
|
445
545
|
- `mapboxgl` - Mapbox GL namespace
|
|
446
|
-
- `onMarkerClick
|
|
447
|
-
|
|
448
|
-
### `useMapFirstProperties(mapFirst)`
|
|
449
|
-
|
|
450
|
-
Returns the current properties array. Only triggers re-renders when properties change.
|
|
451
|
-
|
|
452
|
-
**Parameters:**
|
|
453
|
-
|
|
454
|
-
- `mapFirst` - SDK instance from `useMapFirstCore`
|
|
455
|
-
|
|
456
|
-
**Returns:** `Property[]`
|
|
457
|
-
|
|
458
|
-
**Example:**
|
|
459
|
-
|
|
460
|
-
```tsx
|
|
461
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
462
|
-
const properties = useMapFirstProperties(mapFirst);
|
|
463
|
-
|
|
464
|
-
return <div>Found {properties.length} properties</div>;
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### `useMapFirstSelectedProperty(mapFirst)`
|
|
468
|
-
|
|
469
|
-
Returns the currently selected property ID. Only triggers re-renders when selection changes.
|
|
470
|
-
|
|
471
|
-
**Parameters:**
|
|
472
|
-
|
|
473
|
-
- `mapFirst` - SDK instance from `useMapFirstCore`
|
|
474
|
-
|
|
475
|
-
**Returns:** `number | null`
|
|
476
|
-
|
|
477
|
-
**Example:**
|
|
478
|
-
|
|
479
|
-
```tsx
|
|
480
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
481
|
-
const selectedId = useMapFirstSelectedProperty(mapFirst);
|
|
482
|
-
|
|
483
|
-
return <div>Selected: {selectedId || 'None'}</div>;
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
### `usePrimaryType(mapFirst)`
|
|
487
|
-
|
|
488
|
-
Returns the current primary property type and a setter function. Re-renders when primary type changes.
|
|
489
|
-
|
|
490
|
-
**Parameters:**
|
|
491
|
-
|
|
492
|
-
- `mapFirst` - SDK instance from `useMapFirstCore`
|
|
546
|
+
- `options?.onMarkerClick` - Optional marker click handler
|
|
493
547
|
|
|
494
|
-
|
|
548
|
+
### Search Methods
|
|
495
549
|
|
|
496
|
-
|
|
550
|
+
**`propertiesSearch.search(options)`**
|
|
497
551
|
|
|
498
|
-
|
|
499
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
500
|
-
const [primaryType, setPrimaryType] = usePrimaryType(mapFirst);
|
|
501
|
-
|
|
502
|
-
return (
|
|
503
|
-
<select value={primaryType} onChange={(e) => setPrimaryType(e.target.value as PropertyType)}>
|
|
504
|
-
<option value="Accommodation">Hotels</option>
|
|
505
|
-
<option value="Restaurant">Restaurants</option>
|
|
506
|
-
<option value="Attraction">Attractions</option>
|
|
507
|
-
</select>
|
|
508
|
-
);
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
### `useSelectedMarker(mapFirst)`
|
|
512
|
-
|
|
513
|
-
Returns the currently selected marker ID and a setter function. Re-renders when selection changes.
|
|
514
|
-
|
|
515
|
-
**Parameters:**
|
|
516
|
-
|
|
517
|
-
- `mapFirst` - SDK instance from `useMapFirstCore`
|
|
518
|
-
|
|
519
|
-
**Returns:** `[number | null, (id: number | null) => void]`
|
|
520
|
-
|
|
521
|
-
**Example:**
|
|
522
|
-
|
|
523
|
-
```tsx
|
|
524
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
525
|
-
const [selectedMarker, setSelectedMarker] = useSelectedMarker(mapFirst);
|
|
526
|
-
|
|
527
|
-
return (
|
|
528
|
-
<div>
|
|
529
|
-
<p>Selected: {selectedMarker || 'None'}</p>
|
|
530
|
-
<button onClick={() => setSelectedMarker(null)}>Clear Selection</button>
|
|
531
|
-
<button onClick={() => setSelectedMarker(123456)}>Select Property 123456</button>
|
|
532
|
-
</div>
|
|
533
|
-
);
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
**Example:**
|
|
552
|
+
Runs a properties search with the specified options.
|
|
537
553
|
|
|
538
|
-
|
|
539
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
540
|
-
const selectedId = useMapFirstSelectedProperty(mapFirst);
|
|
541
|
-
|
|
542
|
-
return <div>Selected: {selectedId || 'None'}</div>;
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
### `usePropertiesSearch`
|
|
546
|
-
|
|
547
|
-
Hook to run properties search with the MapFirst SDK. Returns a function to trigger the search and loading state.
|
|
548
|
-
|
|
549
|
-
**Parameters:**
|
|
550
|
-
|
|
551
|
-
- `mapFirst` - SDK instance from `useMapFirstCore`
|
|
552
|
-
|
|
553
|
-
**Returns:** `{ search: Function, isLoading: boolean, error: Error | null }`
|
|
554
|
-
|
|
555
|
-
**Example:**
|
|
556
|
-
|
|
557
|
-
```tsx
|
|
558
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
559
|
-
const { search, isLoading, error } = usePropertiesSearch(mapFirst);
|
|
560
|
-
|
|
561
|
-
const handleSearch = async () => {
|
|
562
|
-
try {
|
|
563
|
-
await search({
|
|
564
|
-
body: {
|
|
565
|
-
city: "Paris",
|
|
566
|
-
country: "France",
|
|
567
|
-
filters: {
|
|
568
|
-
checkIn: new Date(),
|
|
569
|
-
checkOut: new Date(Date.now() + 86400000 * 3), // 3 days later
|
|
570
|
-
numAdults: 2,
|
|
571
|
-
numRooms: 1,
|
|
572
|
-
currency: "EUR"
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
} catch (err) {
|
|
577
|
-
console.error("Search failed:", err);
|
|
578
|
-
}
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
return (
|
|
582
|
-
<div>
|
|
583
|
-
<button onClick={handleSearch} disabled={isLoading}>
|
|
584
|
-
{isLoading ? "Searching..." : "Search Properties"}
|
|
585
|
-
</button>
|
|
586
|
-
{error && <p>Error: {error.message}</p>}
|
|
587
|
-
</div>
|
|
588
|
-
);
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
### `useSmartFilterSearch`
|
|
592
|
-
|
|
593
|
-
Hook to run smart filter search with natural language queries or predefined filters.
|
|
594
|
-
|
|
595
|
-
**Parameters:**
|
|
554
|
+
**Options:**
|
|
596
555
|
|
|
597
|
-
- `
|
|
556
|
+
- `body` - Search body with city, country, filters, etc.
|
|
557
|
+
- `beforeApplyProperties?` - Callback to modify data before applying
|
|
558
|
+
- `smartFiltersClearable?` - Whether smart filters can be cleared
|
|
598
559
|
|
|
599
|
-
**Returns:**
|
|
560
|
+
**Returns:** Promise with search results
|
|
600
561
|
|
|
601
|
-
|
|
562
|
+
**`smartFilterSearch.search(options)`**
|
|
602
563
|
|
|
603
|
-
|
|
604
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
605
|
-
const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
|
|
564
|
+
Runs a smart filter search with natural language or predefined filters.
|
|
606
565
|
|
|
607
|
-
|
|
608
|
-
try {
|
|
609
|
-
await search({ query });
|
|
610
|
-
} catch (err) {
|
|
611
|
-
console.error("Search failed:", err);
|
|
612
|
-
}
|
|
613
|
-
};
|
|
566
|
+
**Options:**
|
|
614
567
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
type="text"
|
|
619
|
-
placeholder="e.g., hotels near beach with pool"
|
|
620
|
-
onKeyDown={(e) => {
|
|
621
|
-
if (e.key === 'Enter') {
|
|
622
|
-
handleSearch(e.currentTarget.value);
|
|
623
|
-
}
|
|
624
|
-
}}
|
|
625
|
-
/>
|
|
626
|
-
{isLoading && <p>Searching...</p>}
|
|
627
|
-
{error && <p>Error: {error.message}</p>}
|
|
628
|
-
</div>
|
|
629
|
-
);
|
|
630
|
-
```
|
|
568
|
+
- `query?` - Natural language search query
|
|
569
|
+
- `filters?` - Array of SmartFilter objects
|
|
570
|
+
- `onProcessFilters?` - Callback to process filter response
|
|
631
571
|
|
|
632
|
-
**
|
|
572
|
+
**Returns:** Promise with search results
|
|
633
573
|
|
|
634
|
-
|
|
635
|
-
const { mapFirst } = useMapFirstCore({ ... });
|
|
636
|
-
const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
|
|
574
|
+
**`boundsSearch.perform()`**
|
|
637
575
|
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
await search({
|
|
641
|
-
filters: [
|
|
642
|
-
{ id: "pool", label: "Pool", type: "amenity", value: "pool" },
|
|
643
|
-
{ id: "4star", label: "4 Star", type: "starRating", value: "4", numericValue: 4 }
|
|
644
|
-
]
|
|
645
|
-
});
|
|
646
|
-
} catch (err) {
|
|
647
|
-
console.error("Search failed:", err);
|
|
648
|
-
}
|
|
649
|
-
};
|
|
576
|
+
Performs a search within the current map bounds when `state.pendingBounds` is set.
|
|
650
577
|
|
|
651
|
-
|
|
652
|
-
<button onClick={handleFilterSearch} disabled={isLoading}>
|
|
653
|
-
Apply Filters
|
|
654
|
-
</button>
|
|
655
|
-
);
|
|
656
|
-
```
|
|
578
|
+
**Returns:** Promise with search results
|
|
657
579
|
|
|
658
580
|
## Legacy API
|
|
659
581
|
|
|
660
|
-
The old `useMapFirst` hook
|
|
582
|
+
The old separate hooks (`useMapFirstCore`, `useMapLibreAttachment`, etc.) have been consolidated into the single `useMapFirst` hook. If you're using the old API:
|
|
661
583
|
|
|
662
584
|
```tsx
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
585
|
+
// Old API (deprecated)
|
|
586
|
+
const { mapFirst, state } = useMapFirstCore({ ... });
|
|
587
|
+
useMapLibreAttachment({ mapFirst, map, maplibregl });
|
|
588
|
+
const { search } = usePropertiesSearch(mapFirst);
|
|
589
|
+
|
|
590
|
+
// New unified API
|
|
591
|
+
const {
|
|
592
|
+
instance: mapFirst,
|
|
593
|
+
state,
|
|
594
|
+
attachMapLibre,
|
|
595
|
+
propertiesSearch
|
|
596
|
+
} = useMapFirst({ ... });
|
|
669
597
|
```
|
|
670
598
|
|
|
671
|
-
**
|
|
599
|
+
**Migration Benefits:**
|
|
600
|
+
|
|
601
|
+
- Single hook import instead of multiple
|
|
602
|
+
- All functionality available from one hook
|
|
603
|
+
- Better TypeScript support
|
|
604
|
+
- Cleaner, more maintainable code
|
|
672
605
|
|
|
673
606
|
## License
|
|
674
607
|
|