@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 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 { useMapFirstCore, SmartFilter } from "@mapfirst/react";
27
+ import { useMapFirst, SmartFilter } from "@mapfirst/react";
29
28
  import { useState } from "react";
30
29
 
31
30
  function App() {
32
- const { mapFirst, state } = useMapFirstCore({
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
- // Implement search logic
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 { useMapFirstCore, useMapLibreAttachment } from "@mapfirst/react";
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 { mapFirst, state } = useMapFirstCore({
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
- useMapLibreAttachment({
114
- mapFirst,
115
- map,
116
- maplibregl,
117
- onMarkerClick: (marker) => {
118
- console.log("Marker clicked:", marker);
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 { useMapFirstCore, useGoogleMapsAttachment } from "@mapfirst/react";
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 { mapFirst, state } = useMapFirstCore({
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
- useGoogleMapsAttachment({
162
- mapFirst,
163
- map,
164
- google: window.google,
165
- onMarkerClick: (marker) => {
166
- console.log("Marker clicked:", marker);
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 { useMapFirstCore, useMapboxAttachment } from "@mapfirst/react";
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 { mapFirst, state } = useMapFirstCore({
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
- useMapboxAttachment({
221
- mapFirst,
222
- map,
223
- mapboxgl,
224
- onMarkerClick: (marker) => {
225
- console.log("Marker clicked:", marker);
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 `mapFirst` instance, and state updates automatically trigger React re-renders:
259
+ All SDK methods are available through the `instance`, and state updates automatically trigger React re-renders:
240
260
 
241
261
  ```tsx
242
- const { mapFirst, state } = useMapFirstCore({
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
- if (!mapFirst) return;
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
- // Set filters
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
- ### Alternative: Using Dedicated Hooks
328
+ ### Using Search Methods
287
329
 
288
- For more granular control, use the dedicated hooks that only update when specific state changes:
330
+ The unified hook provides search methods with their own loading states:
289
331
 
290
332
  ```tsx
291
- const { mapFirst } = useMapFirstCore({
333
+ const {
334
+ instance: mapFirst,
335
+ state,
336
+ propertiesSearch,
337
+ smartFilterSearch,
338
+ boundsSearch,
339
+ } = useMapFirst({
292
340
  /* ... */
293
341
  });
294
342
 
295
- // Only re-renders when properties change
296
- const properties = useMapFirstProperties(mapFirst);
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
- // Only re-renders when selection changes
299
- const selectedId = useMapFirstSelectedProperty(mapFirst);
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
- // Get and set primary type with a single hook
302
- const [primaryType, setPrimaryType] = usePrimaryType(mapFirst);
371
+ // Bounds search with loading state
372
+ const handleBoundsSearch = async () => {
373
+ if (!state?.pendingBounds) return;
303
374
 
304
- // Get and set selected marker with a single hook
305
- const [selectedMarker, setSelectedMarker] = useSelectedMarker(mapFirst);
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
- <h2>Properties ({properties.length})</h2>
384
+ <button
385
+ onClick={handlePropertiesSearch}
386
+ disabled={propertiesSearch.isLoading}
387
+ >
388
+ {propertiesSearch.isLoading ? "Searching..." : "Search Properties"}
389
+ </button>
310
390
 
311
- {/* Type selector */}
312
- <select
313
- value={primaryType}
314
- onChange={(e) => setPrimaryType(e.target.value as PropertyType)}
391
+ <button
392
+ onClick={() => handleSmartSearch("hotels with pool")}
393
+ disabled={smartFilterSearch.isLoading}
315
394
  >
316
- <option value="Accommodation">Hotels</option>
317
- <option value="Restaurant">Restaurants</option>
318
- <option value="Attraction">Attractions</option>
319
- </select>
320
-
321
- {/* Selection controls */}
322
- {selectedMarker && (
323
- <div>
324
- <p>Selected: {selectedMarker}</p>
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 = useMapFirstCore({
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 = useMapFirstCore({
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
- ### `useMapFirstCore(options)`
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
- Creates a MapFirst SDK instance and returns reactive state.
460
+ **Returns:**
383
461
 
384
- **Returns:** `{ mapFirst: MapFirstCore | null, state: MapState | null }`
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
- - `mapFirst` - The SDK instance for calling methods
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
- ### `useMapLibreAttachment(options)`
526
+ ### Map Attachment Methods
416
527
 
417
- Attaches MapLibre map to SDK instance.
528
+ The hook returns three functions for attaching different map types:
418
529
 
419
- **Options:**
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?` - Marker click handler
425
-
426
- ### `useGoogleMapsAttachment(options)`
534
+ - `options?.onMarkerClick` - Optional marker click handler
427
535
 
428
- Attaches Google Maps to SDK instance.
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?` - Marker click handler
436
-
437
- ### `useMapboxAttachment(options)`
540
+ - `options?.onMarkerClick` - Optional marker click handler
438
541
 
439
- Attaches Mapbox map to SDK instance.
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?` - Marker click handler
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
- **Returns:** `[PropertyType, (type: PropertyType) => void]`
548
+ ### Search Methods
495
549
 
496
- **Example:**
550
+ **`propertiesSearch.search(options)`**
497
551
 
498
- ```tsx
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
- ```tsx
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
- - `mapFirst` - SDK instance from `useMapFirstCore`
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:** `{ search: Function, isLoading: boolean, error: Error | null }`
560
+ **Returns:** Promise with search results
600
561
 
601
- **Example with natural language query:**
562
+ **`smartFilterSearch.search(options)`**
602
563
 
603
- ```tsx
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
- const handleSearch = async (query: string) => {
608
- try {
609
- await search({ query });
610
- } catch (err) {
611
- console.error("Search failed:", err);
612
- }
613
- };
566
+ **Options:**
614
567
 
615
- return (
616
- <div>
617
- <input
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
- **Example with predefined filters:**
572
+ **Returns:** Promise with search results
633
573
 
634
- ```tsx
635
- const { mapFirst } = useMapFirstCore({ ... });
636
- const { search, isLoading, error } = useSmartFilterSearch(mapFirst);
574
+ **`boundsSearch.perform()`**
637
575
 
638
- const handleFilterSearch = async () => {
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
- return (
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 is still available but deprecated:
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
- const mapFirst = useMapFirst({
664
- platform: "maplibre",
665
- mapInstance: mapLibreInstance,
666
- maplibregl: maplibregl,
667
- // ... other options
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
- **Note:** This requires the map to be available immediately. Use the new two-phase initialization pattern instead.
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