react-listing-engine 0.1.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/dist/chunk-2T5PE5MB.js +2 -0
  5. package/dist/chunk-2VUHLHHX.cjs +2 -0
  6. package/dist/chunk-35KYEBAC.js +2 -0
  7. package/dist/chunk-5FBI2WIM.js +2 -0
  8. package/dist/chunk-5O36WI7H.cjs +2 -0
  9. package/dist/chunk-6PZHSHU3.js +2 -0
  10. package/dist/chunk-CHNUE46K.cjs +2 -0
  11. package/dist/chunk-LCQZIWBO.cjs +2 -0
  12. package/dist/chunk-N3OZ2HK2.cjs +2 -0
  13. package/dist/chunk-PFPHWJAA.js +2 -0
  14. package/dist/components-provider-CF0Mo0B8.d.cts +120 -0
  15. package/dist/components-provider-CjMxkxrP.d.ts +120 -0
  16. package/dist/entity-adapter.interface-BDgbSxhq.d.cts +33 -0
  17. package/dist/entity-adapter.interface-BDgbSxhq.d.ts +33 -0
  18. package/dist/index.cjs +2 -0
  19. package/dist/index.d.cts +674 -0
  20. package/dist/index.d.ts +674 -0
  21. package/dist/index.js +2 -0
  22. package/dist/listing-app-De6OqpUC.d.ts +199 -0
  23. package/dist/listing-app-pKMQBuQI.d.cts +199 -0
  24. package/dist/listing-config-options.interface-ZJYY_ZvR.d.cts +12 -0
  25. package/dist/listing-config-options.interface-ZJYY_ZvR.d.ts +12 -0
  26. package/dist/map-provider.interface-BMnwW3ob.d.cts +37 -0
  27. package/dist/map-provider.interface-BxexMT3X.d.ts +37 -0
  28. package/dist/maps/google/index.cjs +2 -0
  29. package/dist/maps/google/index.d.cts +39 -0
  30. package/dist/maps/google/index.d.ts +39 -0
  31. package/dist/maps/google/index.js +2 -0
  32. package/dist/presets/rental/index.cjs +2 -0
  33. package/dist/presets/rental/index.d.cts +266 -0
  34. package/dist/presets/rental/index.d.ts +266 -0
  35. package/dist/presets/rental/index.js +2 -0
  36. package/dist/shadcn/index.cjs +2 -0
  37. package/dist/shadcn/index.d.cts +261 -0
  38. package/dist/shadcn/index.d.ts +261 -0
  39. package/dist/shadcn/index.js +2 -0
  40. package/dist/styled/index.cjs +2 -0
  41. package/dist/styled/index.d.cts +123 -0
  42. package/dist/styled/index.d.ts +123 -0
  43. package/dist/styled/index.js +2 -0
  44. package/dist/styles.css +972 -0
  45. package/dist/testing/index.cjs +2 -0
  46. package/dist/testing/index.d.cts +51 -0
  47. package/dist/testing/index.d.ts +51 -0
  48. package/dist/testing/index.js +2 -0
  49. package/dist/url-sync.controller-CsU_QxoS.d.cts +89 -0
  50. package/dist/url-sync.controller-DX67JivW.d.ts +89 -0
  51. package/package.json +143 -0
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _class;"use client";
2
+ var o= (_class =class{constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this); }__init() {this.mounts=[]}__init2() {this.renderedLayers=[]}__init3() {this.removedLayers=[]}__init4() {this.fitBoundsCalls=[]}__init5() {this.destroyed=[]}__init6() {this.destroyCount=0}__init7() {this.boundsListeners=new Set}mount(t,e){let n={raw:{el:t,opts:e}};return this.mounts.push(n),n}renderLayer(t,e){this.renderedLayers.push(e);let n=!1;return()=>{if(n)return;n=!0;let s=this.renderedLayers.indexOf(e);s!==-1&&(this.renderedLayers.splice(s,1),this.removedLayers.push(e))}}onBoundsChange(t,e){return this.boundsListeners.add(e),()=>{this.boundsListeners.delete(e)}}emitBounds(t){for(let e of this.boundsListeners)e(t)}fitBounds(t,e){this.fitBoundsCalls.push(e)}destroy(t){this.destroyed.push(t),this.destroyCount+=1,this.boundsListeners.clear()}}, _class);var d=class{constructor(t,e,n,s=r=>r.id){this.rows=t;this.predicate=e;this.toLatLng=n;this.idOf=s}async list(t,e){let n=this.rows.filter(u=>this.predicate(u,t)),s=e.cursor==null?0:Number(e.cursor)+1,r=n.slice(s,s+e.limit),i=s+r.length-1,l=i+1<n.length;return{items:r,nextCursor:l?String(i):null,total:n.length}}async getPoints(t,e){let s=this.rows.filter(r=>this.predicate(r,t)).map(r=>({id:this.idOf(r),position:this.toLatLng(r),entity:r}));return e?s.filter(r=>this.isInBounds(r.position,e)):s}async getById(t){let e=this.rows.find(n=>this.idOf(n)===t);if(!e)throw new Error(`InMemoryEntityAdapter: no row with id "${String(t)}"`);return e}isInBounds(t,e){return t.lat<=e.north&&t.lat>=e.south&&t.lng>=e.west&&t.lng<=e.east}};exports.FakeMapProvider = o; exports.InMemoryEntityAdapter = d;
@@ -0,0 +1,51 @@
1
+ import { B as Bounds, a as EntityAdapter, L as LatLng, E as EntityId, b as PageRequest, P as Page, M as MapPoint } from '../entity-adapter.interface-BDgbSxhq.cjs';
2
+ import { M as MapProvider, a as MapHandle, R as RenderedLayer, b as MapInitOptions, U as Unsubscribe } from '../map-provider.interface-BMnwW3ob.cjs';
3
+
4
+ type BoundsListener = (b: Bounds) => void;
5
+ /**
6
+ * Headless in-memory `MapProvider` test double. Records everything it is
7
+ * asked to do (mounts, rendered layers, fitBounds calls, destroys) into
8
+ * public arrays/counters, and exposes `emitBounds()` so a test can simulate
9
+ * a user pan/zoom without a real map SDK or browser.
10
+ */
11
+ declare class FakeMapProvider implements MapProvider {
12
+ readonly mounts: MapHandle[];
13
+ readonly renderedLayers: RenderedLayer[];
14
+ readonly removedLayers: RenderedLayer[];
15
+ readonly fitBoundsCalls: Bounds[];
16
+ readonly destroyed: MapHandle[];
17
+ destroyCount: number;
18
+ private readonly boundsListeners;
19
+ mount(el: HTMLElement, opts: MapInitOptions): MapHandle;
20
+ renderLayer(_handle: MapHandle, layer: RenderedLayer): Unsubscribe;
21
+ onBoundsChange(_handle: MapHandle, cb: BoundsListener): Unsubscribe;
22
+ /** Test-only: simulates a user pan/zoom by invoking every registered bounds listener. */
23
+ emitBounds(b: Bounds): void;
24
+ fitBounds(_handle: MapHandle, b: Bounds): void;
25
+ destroy(handle: MapHandle): void;
26
+ }
27
+
28
+ /**
29
+ * In-memory `EntityAdapter` test double. `list()` filters `rows` through
30
+ * `predicate` and paginates by array index: `cursor` is the (stringified)
31
+ * index of the last row already served (`null` means start from the
32
+ * beginning), and the returned `nextCursor` is that same kind of value for
33
+ * the next call — `null` once nothing is left. `getPoints()` runs the same
34
+ * predicate filter and projects each surviving row through `toLatLng`
35
+ * (further narrowed to `bounds` when one is given). `idOf` defaults to
36
+ * reading `row.id`, mirroring most real adapters, but can be overridden for
37
+ * rows keyed by something else.
38
+ */
39
+ declare class InMemoryEntityAdapter<TEntity, TFilters> implements EntityAdapter<TEntity, TFilters> {
40
+ private readonly rows;
41
+ private readonly predicate;
42
+ private readonly toLatLng;
43
+ private readonly idOf;
44
+ constructor(rows: TEntity[], predicate: (row: TEntity, filters: TFilters) => boolean, toLatLng: (row: TEntity) => LatLng, idOf?: (row: TEntity) => EntityId);
45
+ list(filters: TFilters, page: PageRequest): Promise<Page<TEntity>>;
46
+ getPoints(filters: TFilters, bounds?: Bounds): Promise<MapPoint<TEntity>[]>;
47
+ getById(id: EntityId): Promise<TEntity>;
48
+ private isInBounds;
49
+ }
50
+
51
+ export { FakeMapProvider, InMemoryEntityAdapter };
@@ -0,0 +1,51 @@
1
+ import { B as Bounds, a as EntityAdapter, L as LatLng, E as EntityId, b as PageRequest, P as Page, M as MapPoint } from '../entity-adapter.interface-BDgbSxhq.js';
2
+ import { M as MapProvider, a as MapHandle, R as RenderedLayer, b as MapInitOptions, U as Unsubscribe } from '../map-provider.interface-BxexMT3X.js';
3
+
4
+ type BoundsListener = (b: Bounds) => void;
5
+ /**
6
+ * Headless in-memory `MapProvider` test double. Records everything it is
7
+ * asked to do (mounts, rendered layers, fitBounds calls, destroys) into
8
+ * public arrays/counters, and exposes `emitBounds()` so a test can simulate
9
+ * a user pan/zoom without a real map SDK or browser.
10
+ */
11
+ declare class FakeMapProvider implements MapProvider {
12
+ readonly mounts: MapHandle[];
13
+ readonly renderedLayers: RenderedLayer[];
14
+ readonly removedLayers: RenderedLayer[];
15
+ readonly fitBoundsCalls: Bounds[];
16
+ readonly destroyed: MapHandle[];
17
+ destroyCount: number;
18
+ private readonly boundsListeners;
19
+ mount(el: HTMLElement, opts: MapInitOptions): MapHandle;
20
+ renderLayer(_handle: MapHandle, layer: RenderedLayer): Unsubscribe;
21
+ onBoundsChange(_handle: MapHandle, cb: BoundsListener): Unsubscribe;
22
+ /** Test-only: simulates a user pan/zoom by invoking every registered bounds listener. */
23
+ emitBounds(b: Bounds): void;
24
+ fitBounds(_handle: MapHandle, b: Bounds): void;
25
+ destroy(handle: MapHandle): void;
26
+ }
27
+
28
+ /**
29
+ * In-memory `EntityAdapter` test double. `list()` filters `rows` through
30
+ * `predicate` and paginates by array index: `cursor` is the (stringified)
31
+ * index of the last row already served (`null` means start from the
32
+ * beginning), and the returned `nextCursor` is that same kind of value for
33
+ * the next call — `null` once nothing is left. `getPoints()` runs the same
34
+ * predicate filter and projects each surviving row through `toLatLng`
35
+ * (further narrowed to `bounds` when one is given). `idOf` defaults to
36
+ * reading `row.id`, mirroring most real adapters, but can be overridden for
37
+ * rows keyed by something else.
38
+ */
39
+ declare class InMemoryEntityAdapter<TEntity, TFilters> implements EntityAdapter<TEntity, TFilters> {
40
+ private readonly rows;
41
+ private readonly predicate;
42
+ private readonly toLatLng;
43
+ private readonly idOf;
44
+ constructor(rows: TEntity[], predicate: (row: TEntity, filters: TFilters) => boolean, toLatLng: (row: TEntity) => LatLng, idOf?: (row: TEntity) => EntityId);
45
+ list(filters: TFilters, page: PageRequest): Promise<Page<TEntity>>;
46
+ getPoints(filters: TFilters, bounds?: Bounds): Promise<MapPoint<TEntity>[]>;
47
+ getById(id: EntityId): Promise<TEntity>;
48
+ private isInBounds;
49
+ }
50
+
51
+ export { FakeMapProvider, InMemoryEntityAdapter };
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ var o=class{mounts=[];renderedLayers=[];removedLayers=[];fitBoundsCalls=[];destroyed=[];destroyCount=0;boundsListeners=new Set;mount(t,e){let n={raw:{el:t,opts:e}};return this.mounts.push(n),n}renderLayer(t,e){this.renderedLayers.push(e);let n=!1;return()=>{if(n)return;n=!0;let s=this.renderedLayers.indexOf(e);s!==-1&&(this.renderedLayers.splice(s,1),this.removedLayers.push(e))}}onBoundsChange(t,e){return this.boundsListeners.add(e),()=>{this.boundsListeners.delete(e)}}emitBounds(t){for(let e of this.boundsListeners)e(t)}fitBounds(t,e){this.fitBoundsCalls.push(e)}destroy(t){this.destroyed.push(t),this.destroyCount+=1,this.boundsListeners.clear()}};var d=class{constructor(t,e,n,s=r=>r.id){this.rows=t;this.predicate=e;this.toLatLng=n;this.idOf=s}rows;predicate;toLatLng;idOf;async list(t,e){let n=this.rows.filter(u=>this.predicate(u,t)),s=e.cursor==null?0:Number(e.cursor)+1,r=n.slice(s,s+e.limit),i=s+r.length-1,l=i+1<n.length;return{items:r,nextCursor:l?String(i):null,total:n.length}}async getPoints(t,e){let s=this.rows.filter(r=>this.predicate(r,t)).map(r=>({id:this.idOf(r),position:this.toLatLng(r),entity:r}));return e?s.filter(r=>this.isInBounds(r.position,e)):s}async getById(t){let e=this.rows.find(n=>this.idOf(n)===t);if(!e)throw new Error(`InMemoryEntityAdapter: no row with id "${String(t)}"`);return e}isInBounds(t,e){return t.lat<=e.north&&t.lat>=e.south&&t.lng>=e.west&&t.lng<=e.east}};export{o as FakeMapProvider,d as InMemoryEntityAdapter};
@@ -0,0 +1,89 @@
1
+ import { Q as QueryParams } from './entity-adapter.interface-BDgbSxhq.cjs';
2
+
3
+ /**
4
+ * DOM-agnostic abstraction over "the URL's query string" — or, in tests/SSR,
5
+ * an in-memory stand-in (`MemoryHistoryPort`). `UrlSyncController` talks to
6
+ * this interface only, never to `window.location`/`history` directly, so it
7
+ * stays framework- and environment-free like the rest of `src/core`.
8
+ */
9
+ interface HistoryPort {
10
+ getQuery(): QueryParams;
11
+ setQuery(params: QueryParams): void;
12
+ subscribe(cb: () => void): () => void;
13
+ }
14
+
15
+ interface UrlSyncEngine<TFilters> {
16
+ subscribe(cb: () => void): () => void;
17
+ readonly state: {
18
+ readonly filters: TFilters;
19
+ };
20
+ applyFilters(patch: Partial<TFilters>): Promise<void> | void;
21
+ }
22
+ interface UrlSyncOptions<TFilters> {
23
+ history: HistoryPort;
24
+ toQuery(filters: TFilters): QueryParams;
25
+ toFilters(query: QueryParams): Partial<TFilters>;
26
+ hydrateOnStart?: boolean;
27
+ }
28
+ /**
29
+ * Bidirectional, DOM-agnostic sync between an engine's `filters` and a
30
+ * `HistoryPort`'s query params. Framework-free: talks to `UrlSyncEngine`
31
+ * (a structural subset `ListingEngine` satisfies) and `HistoryPort`, nothing
32
+ * else — no `window`, no React.
33
+ *
34
+ * OPTIONAL, not the primary path: `styled/listing-app.tsx`'s turnkey
35
+ * `ListingApp` (the package's main-entry default) does NOT wire this up —
36
+ * it uses an event-based URL API instead (`initialFilters` in,
37
+ * `onFiltersChange` out; see that file's doc comment), so the library never
38
+ * touches `window.history` by default. This class remains exported for
39
+ * consumers who explicitly want the library to own history writes/reads
40
+ * itself (e.g. via `/shadcn`'s `ListingApp.urlSync`, or wiring it directly
41
+ * against a hand-built engine).
42
+ *
43
+ * Echo-loop guard: both subscriptions below are driven by the SAME
44
+ * `isSyncing` flag. `ListingEngine.applyFilters()` and `MemoryHistoryPort`
45
+ * (and any real browser HistoryPort) both notify their subscribers
46
+ * SYNCHRONOUSLY as part of the write (`store.setFilters()` -> `notify()`
47
+ * happens before any debounce timer, and `MemoryHistoryPort.setQuery()`
48
+ * notifies before returning) — so when this controller initiates a write on
49
+ * one side, the reciprocal subscription on the other side fires within the
50
+ * very same call stack, before `isSyncing` is reset. Wrapping each
51
+ * controller-initiated write in `isSyncing = true; ...; isSyncing = false`
52
+ * (via try/finally, so a throwing `toQuery`/`toFilters`/notify can't leave it
53
+ * stuck) means that reciprocal callback observes `isSyncing === true` and
54
+ * short-circuits instead of writing back — one hop each direction, never a
55
+ * cascade. `hydrateOnStart`'s initial engine write is wrapped the same way,
56
+ * since it must not immediately echo the just-hydrated filters back out to
57
+ * history as a redundant `setQuery`.
58
+ *
59
+ * `isSyncing` only covers that SYNCHRONOUS window, though — the real
60
+ * `ListingEngine.applyFilters()` notifies AGAIN, asynchronously: after
61
+ * `await adapter.list(...)` resolves, `store.setResults()` and
62
+ * `store.setLoading(false)` each call `notify()`, well after `isSyncing` has
63
+ * already been reset to `false` by the `withSyncGuard` that kicked the query
64
+ * off. Left unguarded, that async tail would fire the engine subscription
65
+ * below and echo an identical (but spurious) `history.setQuery(...)` on
66
+ * every completed query. Rather than widening `isSyncing` to cover the whole
67
+ * async query (which would also swallow legitimate concurrent history
68
+ * changes that land mid-query), `syncEngineToHistory` is idempotent BY
69
+ * VALUE instead: it computes the target query and skips the write entirely
70
+ * when it already matches `history.getQuery()` — timing-independent, so it
71
+ * suppresses the async-tail echo without touching `isSyncing` at all.
72
+ */
73
+ declare class UrlSyncController<TFilters> {
74
+ private readonly history;
75
+ private readonly toQueryFn;
76
+ private readonly toFiltersFn;
77
+ private readonly hydrateOnStart;
78
+ private isSyncing;
79
+ private unsubscribeEngine;
80
+ private unsubscribeHistory;
81
+ constructor(opts: UrlSyncOptions<TFilters>);
82
+ start(engine: UrlSyncEngine<TFilters>): void;
83
+ stop(): void;
84
+ private syncEngineToHistory;
85
+ private applyFiltersSafely;
86
+ private withSyncGuard;
87
+ }
88
+
89
+ export { type HistoryPort as H, UrlSyncController as U, type UrlSyncEngine as a, type UrlSyncOptions as b };
@@ -0,0 +1,89 @@
1
+ import { Q as QueryParams } from './entity-adapter.interface-BDgbSxhq.js';
2
+
3
+ /**
4
+ * DOM-agnostic abstraction over "the URL's query string" — or, in tests/SSR,
5
+ * an in-memory stand-in (`MemoryHistoryPort`). `UrlSyncController` talks to
6
+ * this interface only, never to `window.location`/`history` directly, so it
7
+ * stays framework- and environment-free like the rest of `src/core`.
8
+ */
9
+ interface HistoryPort {
10
+ getQuery(): QueryParams;
11
+ setQuery(params: QueryParams): void;
12
+ subscribe(cb: () => void): () => void;
13
+ }
14
+
15
+ interface UrlSyncEngine<TFilters> {
16
+ subscribe(cb: () => void): () => void;
17
+ readonly state: {
18
+ readonly filters: TFilters;
19
+ };
20
+ applyFilters(patch: Partial<TFilters>): Promise<void> | void;
21
+ }
22
+ interface UrlSyncOptions<TFilters> {
23
+ history: HistoryPort;
24
+ toQuery(filters: TFilters): QueryParams;
25
+ toFilters(query: QueryParams): Partial<TFilters>;
26
+ hydrateOnStart?: boolean;
27
+ }
28
+ /**
29
+ * Bidirectional, DOM-agnostic sync between an engine's `filters` and a
30
+ * `HistoryPort`'s query params. Framework-free: talks to `UrlSyncEngine`
31
+ * (a structural subset `ListingEngine` satisfies) and `HistoryPort`, nothing
32
+ * else — no `window`, no React.
33
+ *
34
+ * OPTIONAL, not the primary path: `styled/listing-app.tsx`'s turnkey
35
+ * `ListingApp` (the package's main-entry default) does NOT wire this up —
36
+ * it uses an event-based URL API instead (`initialFilters` in,
37
+ * `onFiltersChange` out; see that file's doc comment), so the library never
38
+ * touches `window.history` by default. This class remains exported for
39
+ * consumers who explicitly want the library to own history writes/reads
40
+ * itself (e.g. via `/shadcn`'s `ListingApp.urlSync`, or wiring it directly
41
+ * against a hand-built engine).
42
+ *
43
+ * Echo-loop guard: both subscriptions below are driven by the SAME
44
+ * `isSyncing` flag. `ListingEngine.applyFilters()` and `MemoryHistoryPort`
45
+ * (and any real browser HistoryPort) both notify their subscribers
46
+ * SYNCHRONOUSLY as part of the write (`store.setFilters()` -> `notify()`
47
+ * happens before any debounce timer, and `MemoryHistoryPort.setQuery()`
48
+ * notifies before returning) — so when this controller initiates a write on
49
+ * one side, the reciprocal subscription on the other side fires within the
50
+ * very same call stack, before `isSyncing` is reset. Wrapping each
51
+ * controller-initiated write in `isSyncing = true; ...; isSyncing = false`
52
+ * (via try/finally, so a throwing `toQuery`/`toFilters`/notify can't leave it
53
+ * stuck) means that reciprocal callback observes `isSyncing === true` and
54
+ * short-circuits instead of writing back — one hop each direction, never a
55
+ * cascade. `hydrateOnStart`'s initial engine write is wrapped the same way,
56
+ * since it must not immediately echo the just-hydrated filters back out to
57
+ * history as a redundant `setQuery`.
58
+ *
59
+ * `isSyncing` only covers that SYNCHRONOUS window, though — the real
60
+ * `ListingEngine.applyFilters()` notifies AGAIN, asynchronously: after
61
+ * `await adapter.list(...)` resolves, `store.setResults()` and
62
+ * `store.setLoading(false)` each call `notify()`, well after `isSyncing` has
63
+ * already been reset to `false` by the `withSyncGuard` that kicked the query
64
+ * off. Left unguarded, that async tail would fire the engine subscription
65
+ * below and echo an identical (but spurious) `history.setQuery(...)` on
66
+ * every completed query. Rather than widening `isSyncing` to cover the whole
67
+ * async query (which would also swallow legitimate concurrent history
68
+ * changes that land mid-query), `syncEngineToHistory` is idempotent BY
69
+ * VALUE instead: it computes the target query and skips the write entirely
70
+ * when it already matches `history.getQuery()` — timing-independent, so it
71
+ * suppresses the async-tail echo without touching `isSyncing` at all.
72
+ */
73
+ declare class UrlSyncController<TFilters> {
74
+ private readonly history;
75
+ private readonly toQueryFn;
76
+ private readonly toFiltersFn;
77
+ private readonly hydrateOnStart;
78
+ private isSyncing;
79
+ private unsubscribeEngine;
80
+ private unsubscribeHistory;
81
+ constructor(opts: UrlSyncOptions<TFilters>);
82
+ start(engine: UrlSyncEngine<TFilters>): void;
83
+ stop(): void;
84
+ private syncEngineToHistory;
85
+ private applyFiltersSafely;
86
+ private withSyncGuard;
87
+ }
88
+
89
+ export { type HistoryPort as H, UrlSyncController as U, type UrlSyncEngine as a, type UrlSyncOptions as b };
package/package.json ADDED
@@ -0,0 +1,143 @@
1
+ {
2
+ "name": "react-listing-engine",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "Headless, composable listing engine for React: filterable list + Google-Maps multi-layer map, with pluggable data adapters, a filter/dataset registry, injectable components, and a shadcn-compatible styled adapter.",
6
+ "funding": [
7
+ {
8
+ "type": "github",
9
+ "url": "https://github.com/sponsors/knazark"
10
+ },
11
+ {
12
+ "type": "individual",
13
+ "url": "https://send.monobank.ua/jar/2jfphHthfY"
14
+ }
15
+ ],
16
+ "keywords": [
17
+ "react",
18
+ "listing",
19
+ "map",
20
+ "google-maps",
21
+ "filters",
22
+ "real-estate",
23
+ "directory",
24
+ "store-locator",
25
+ "headless",
26
+ "typescript",
27
+ "nextjs",
28
+ "react18",
29
+ "react19",
30
+ "shadcn"
31
+ ],
32
+ "author": "knazark",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/knazark/react-listing-engine.git"
36
+ },
37
+ "homepage": "https://github.com/knazark/react-listing-engine#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/knazark/react-listing-engine/issues"
40
+ },
41
+ "type": "module",
42
+ "sideEffects": [
43
+ "./dist/styles.css"
44
+ ],
45
+ "main": "./dist/index.cjs",
46
+ "module": "./dist/index.js",
47
+ "types": "./dist/index.d.ts",
48
+ "exports": {
49
+ ".": {
50
+ "types": "./dist/index.d.ts",
51
+ "import": "./dist/index.js",
52
+ "require": "./dist/index.cjs"
53
+ },
54
+ "./shadcn": {
55
+ "types": "./dist/shadcn/index.d.ts",
56
+ "import": "./dist/shadcn/index.js",
57
+ "require": "./dist/shadcn/index.cjs"
58
+ },
59
+ "./styled": {
60
+ "types": "./dist/styled/index.d.ts",
61
+ "import": "./dist/styled/index.js",
62
+ "require": "./dist/styled/index.cjs"
63
+ },
64
+ "./styles.css": "./dist/styles.css",
65
+ "./maps/google": {
66
+ "types": "./dist/maps/google/index.d.ts",
67
+ "import": "./dist/maps/google/index.js",
68
+ "require": "./dist/maps/google/index.cjs"
69
+ },
70
+ "./presets/rental": {
71
+ "types": "./dist/presets/rental/index.d.ts",
72
+ "import": "./dist/presets/rental/index.js",
73
+ "require": "./dist/presets/rental/index.cjs"
74
+ },
75
+ "./testing": {
76
+ "types": "./dist/testing/index.d.ts",
77
+ "import": "./dist/testing/index.js",
78
+ "require": "./dist/testing/index.cjs"
79
+ },
80
+ "./package.json": "./package.json"
81
+ },
82
+ "files": [
83
+ "dist",
84
+ "README.md",
85
+ "LICENSE",
86
+ "CHANGELOG.md"
87
+ ],
88
+ "peerDependencies": {
89
+ "@googlemaps/js-api-loader": ">=1",
90
+ "@googlemaps/markerclusterer": ">=2",
91
+ "@radix-ui/react-slot": ">=1",
92
+ "react": ">=18",
93
+ "react-dom": ">=18"
94
+ },
95
+ "peerDependenciesMeta": {
96
+ "@googlemaps/js-api-loader": {
97
+ "optional": true
98
+ },
99
+ "@googlemaps/markerclusterer": {
100
+ "optional": true
101
+ },
102
+ "@radix-ui/react-slot": {
103
+ "optional": true
104
+ }
105
+ },
106
+ "devDependencies": {
107
+ "@changesets/cli": "^2.31.1",
108
+ "@googlemaps/js-api-loader": "^2.1.1",
109
+ "@googlemaps/markerclusterer": "^2.6.2",
110
+ "@testing-library/jest-dom": "^7.0.0",
111
+ "@testing-library/react": "^16.3.2",
112
+ "@types/google.maps": "^3.65.3",
113
+ "@types/node": "^26.1.1",
114
+ "@types/react": "^19.2.17",
115
+ "@types/react-dom": "^19.2.3",
116
+ "eslint": "^10.8.0",
117
+ "eslint-plugin-react-hooks": "^7.1.1",
118
+ "happy-dom": "^20.11.1",
119
+ "lucide-react": "^1.26.0",
120
+ "tsup": "^8.5.1",
121
+ "typescript": "^5.9.3",
122
+ "typescript-eslint": "^8.65.0",
123
+ "vitest": "^4.1.10"
124
+ },
125
+ "dependencies": {
126
+ "clsx": "^2.1.1",
127
+ "tailwind-merge": "^3.6.0"
128
+ },
129
+ "engines": {
130
+ "node": ">=18"
131
+ },
132
+ "publishConfig": {
133
+ "access": "public"
134
+ },
135
+ "scripts": {
136
+ "build": "tsup",
137
+ "test": "vitest run",
138
+ "test:watch": "vitest",
139
+ "typecheck": "tsc -p tsconfig.json --noEmit",
140
+ "lint": "eslint .",
141
+ "release": "changeset publish"
142
+ }
143
+ }