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,266 @@
1
+ import { E as EntityId, L as LatLng, B as Bounds, b as PageRequest, P as Page, Q as QueryParams } from '../../entity-adapter.interface-BDgbSxhq.cjs';
2
+ import { D as DatasetDefinition, a as FilterControlProps, c as IListingCardProps, h as IListingMarkerProps, b as FilterDefinition, F as FilterRegistry } from '../../components-provider-CF0Mo0B8.cjs';
3
+ import * as react from 'react';
4
+ import { H as HistoryPort, U as UrlSyncController } from '../../url-sync.controller-CsU_QxoS.cjs';
5
+
6
+ /**
7
+ * Deliberately an open string type, not a union -- new business categories
8
+ * (a new row of DATA, e.g. `'pharmacy'`) need zero library/core change to
9
+ * flow through `nearbyBusinessesDataset`. `KNOWN_BUSINESS_CATEGORIES` below
10
+ * is a seeded starter list for consumers building category pickers, not an
11
+ * exhaustive/closed taxonomy.
12
+ */
13
+ type BusinessCategory = string;
14
+ declare const KNOWN_BUSINESS_CATEGORIES: readonly ["grocery", "shopping", "restaurants", "schools", "hospitals", "parks", "gyms", "cafes"];
15
+ interface BusinessEntity {
16
+ id: EntityId;
17
+ name: string;
18
+ category: BusinessCategory;
19
+ coordinates: LatLng;
20
+ address?: string;
21
+ }
22
+ interface BusinessFilters {
23
+ categories?: BusinessCategory[];
24
+ bounds?: Bounds;
25
+ }
26
+ /**
27
+ * The "port" the CONSUMER implements against their real API -- same shape of
28
+ * seam as `PropertiesApiPort`. `list` is optional: many consumers only ever
29
+ * render businesses as a map layer (`search`) and never page through them in
30
+ * a results list.
31
+ */
32
+ interface BusinessesApiPort {
33
+ search(filters: BusinessFilters, bounds: Bounds): Promise<BusinessEntity[]>;
34
+ list?(filters: BusinessFilters, page: PageRequest): Promise<Page<BusinessEntity>>;
35
+ }
36
+ interface NearbyBusinessesOptions {
37
+ /** Restrict which categories render; default (omitted) shows everything `search` returns. */
38
+ categories?: BusinessCategory[];
39
+ /** Per-category icon URL -- DATA, not code. New category = new map entry. */
40
+ icons?: Record<string, string>;
41
+ onClick?(business: BusinessEntity): void;
42
+ }
43
+ /**
44
+ * Wraps a `BusinessesApiPort` into a `DatasetDefinition` -- the nearby
45
+ * businesses marker layer, proving req #6: this is just a SECOND
46
+ * dataset/marker layer sitting next to `propertiesDataset`, and the category
47
+ * taxonomy is DATA (`opts.categories` / `opts.icons`), not core code.
48
+ */
49
+ declare function nearbyBusinessesDataset(api: BusinessesApiPort, opts?: NearbyBusinessesOptions): DatasetDefinition<BusinessEntity, BusinessFilters>;
50
+
51
+ /** Unstyled free-text keyword input. */
52
+ declare function KeywordFilterControl({ value, onChange }: FilterControlProps<string>): react.JSX.Element;
53
+
54
+ type PropertyType = 'house' | 'apartment' | 'condo' | 'townhouse' | 'land';
55
+ /**
56
+ * Mapped from the app's `@libs/core-base` `Listing` shape (nested
57
+ * `property.coordinates`) -- not the old flat `/find` DTO. See design doc
58
+ * D6/#11.
59
+ */
60
+ interface PropertyEntity {
61
+ id: EntityId;
62
+ title: string;
63
+ address?: string;
64
+ coordinates: LatLng;
65
+ price: number;
66
+ bedrooms: number;
67
+ bathrooms: number;
68
+ propertyType: PropertyType;
69
+ imageUrl?: string;
70
+ }
71
+ interface RentalFilters {
72
+ minPrice?: number;
73
+ maxPrice?: number;
74
+ minBeds?: number;
75
+ maxBeds?: number;
76
+ minBaths?: number;
77
+ maxBaths?: number;
78
+ propertyTypes?: PropertyType[];
79
+ keyword?: string;
80
+ bounds?: Bounds;
81
+ }
82
+ /** Generic min/max shape shared by every range-style filter control (price, beds, baths). */
83
+ interface RangeValue {
84
+ min?: number;
85
+ max?: number;
86
+ }
87
+
88
+ /**
89
+ * Compact dropdown for the property-type multi-select -- a native
90
+ * `<details>`/`<summary>` disclosure (no Radix dependency, so this preset
91
+ * package doesn't have to pull one in) instead of the previous
92
+ * always-expanded checkbox list, so the top filter bar (`ListingLayout`'s
93
+ * horizontal row) stays compact regardless of how many property types exist.
94
+ * `<summary>` is the button-like trigger: "Property type" plus a count badge
95
+ * once 1+ types are selected. The panel (absolutely positioned so it doesn't
96
+ * push sibling filter groups around) holds one checkbox per `PropertyType`,
97
+ * unchanged from the previous always-expanded version.
98
+ *
99
+ * `open` is fully CONTROLLED local state, not left to `<details>`'s own
100
+ * built-in toggle -- the trigger's `onClick` calls `event.preventDefault()`
101
+ * (which, per spec, suppresses `<summary>`'s native open/close activation for
102
+ * that click) and flips `open` itself instead. This makes `aria-expanded`
103
+ * always exactly match what's rendered, and makes the open/close behavior
104
+ * independent of a given DOM engine's level of native `<details>` support --
105
+ * it doesn't rely on a `toggle` event firing. Keyboard access is unaffected:
106
+ * `preventDefault()` only suppresses the native toggle side effect, not the
107
+ * click event itself, so Enter/Space on a focused `<summary>` (which the
108
+ * browser turns into a `click`) still reaches this handler. Outside click is
109
+ * NOT handled (native `<details>` doesn't require it either) -- clicking
110
+ * elsewhere leaves the panel open until the trigger (or a checkbox) is
111
+ * clicked again; not fixed here, matching the design brief.
112
+ */
113
+ declare function PropertyTypeFilterControl({ value, onChange }: FilterControlProps<PropertyType[]>): react.JSX.Element;
114
+
115
+ /**
116
+ * Unstyled min/max number inputs for any `RangeValue`-shaped filter (price,
117
+ * beds, baths). Deliberately generic -- no per-domain label prop -- consumers
118
+ * restyle/relabel via a shadcn wrapper or their own `render` override.
119
+ */
120
+ declare function RangeFilterControl({ value, onChange }: FilterControlProps<RangeValue>): react.JSX.Element;
121
+
122
+ /**
123
+ * The "port" the CONSUMER implements against their real API (e.g. the app's
124
+ * `LocaListingApi`) -- this preset never imports an HTTP client itself. `list`
125
+ * feeds the paginated results list, `search` feeds the map (bounds-scoped,
126
+ * no pagination), `getById` is optional (deep-link / selection lookups).
127
+ */
128
+ interface PropertiesApiPort {
129
+ list(filters: RentalFilters, page: PageRequest): Promise<Page<PropertyEntity>>;
130
+ search(filters: RentalFilters, bounds: Bounds): Promise<PropertyEntity[]>;
131
+ getById?(id: EntityId): Promise<PropertyEntity>;
132
+ }
133
+ interface PropertiesDatasetOptions {
134
+ onClick?(property: PropertyEntity): void;
135
+ iconUrl?(property: PropertyEntity): string;
136
+ /**
137
+ * Builds the `AdvancedMarkerElement` content for a property's map pin.
138
+ * Defaults to `defaultPriceMarkerElement` (a green teardrop price pin
139
+ * showing `formatRentalPrice(property.price)`) when omitted -- pass this
140
+ * to replace the pin entirely with a custom marker.
141
+ */
142
+ element?(property: PropertyEntity): HTMLElement;
143
+ }
144
+ /**
145
+ * Default `marker.element` builder: a raw DOM teardrop price PIN
146
+ * (`formatRentalPrice(entity.price)` in a rounded pill, plus a small
147
+ * rotated-square pointer tail so the marker's visual tip lands on the exact
148
+ * coordinate -- Rentler-style) for the map's `AdvancedMarkerElement` content
149
+ * -- `GoogleMapsProvider` prefers `element` over `iconUrl` (see
150
+ * `google-maps.provider.ts`) when both are set.
151
+ *
152
+ * Built with `document.createElement` rather than React because map markers
153
+ * are raw DOM nodes, not React elements (rendering the injected `Marker`
154
+ * React component INTO a real marker via a portal is a documented future
155
+ * enhancement -- see `listing-map.tsx`'s doc comment). Since this element is
156
+ * constructed at runtime, Tailwind's build-time content scanner can't see
157
+ * the `className` strings below to generate their CSS, so the classes are
158
+ * kept (any consumer app that DOES happen to scan this file's literal
159
+ * strings, e.g. via a broad `content` glob, gets real utility classes) but
160
+ * are backed by the same values set directly as inline styles on both the
161
+ * pill and its pointer child, so the pin renders correctly with zero
162
+ * build-time cooperation from the consumer's Tailwind config. Mirrors
163
+ * `.rle-pin`/`.rle-pin::after` in `src/styled/styles.css` (the `/styled`
164
+ * equivalent for `StyledMarker`) -- a pseudo-element isn't an option here
165
+ * since inline styles can't target `::after` on a JS-constructed node, so
166
+ * the pointer is a real child element instead.
167
+ */
168
+ declare function defaultPriceMarkerElement(property: PropertyEntity): HTMLElement;
169
+ /**
170
+ * Wraps a `PropertiesApiPort` into a `DatasetDefinition` -- the properties
171
+ * marker layer. Row -> `MapPoint` mapping reads `entity.coordinates`
172
+ * directly; no other core/library change is needed to add this layer to a
173
+ * listing.
174
+ */
175
+ declare function propertiesDataset(api: PropertiesApiPort, opts?: PropertiesDatasetOptions): DatasetDefinition<PropertyEntity, RentalFilters>;
176
+
177
+ /** Formats a whole-dollar rental price as USD, e.g. `2800` -> `"$2,800"`. */
178
+ declare function formatRentalPrice(price: number): string;
179
+ /**
180
+ * Real-estate `Card` slot matching the production `/find` widget's property
181
+ * card layout: image -> title -> address -> "Type · N bd · N ba" -> price.
182
+ * `item` is `unknown` at this layer (the engine is entity-erased, same
183
+ * reasoning as `DefaultCard` -- see `src/shadcn/default-card.tsx`), so it's
184
+ * read DEFENSIVELY via a cast to `PropertyEntity`, never assumed to be the
185
+ * caller's real `TEntity`. Every field is optional and simply omitted from
186
+ * the render when absent.
187
+ *
188
+ * Mirrors `DefaultCard`'s interactive/non-interactive split: the whole card
189
+ * is a `button` (keyboard operable, `aria-pressed`) when `onSelect` is
190
+ * given, otherwise a non-interactive `<article>` with the same visual
191
+ * classes minus the button/focus/hover-shadow semantics.
192
+ */
193
+ declare function PropertyCard({ item, selected, onSelect }: IListingCardProps): react.JSX.Element;
194
+ /**
195
+ * Real-estate `Marker` slot: a green teardrop price pin (Rentler-style) for
196
+ * a map point -- a rounded pill plus a small rotated-square tail so the
197
+ * marker's visual tip lands on the exact coordinate. Same defensive
198
+ * view-model reasoning as `DefaultMarker` -- `point.entity` is the raw row
199
+ * (`unknown` at this layer), read defensively via a cast. Tailwind-styled by
200
+ * design (not `.rle-*`) -- see `properties-dataset.ts`'s `defaultPriceMarkerElement`
201
+ * for the raw-DOM equivalent used by the map's own marker content, and
202
+ * `StyledMarker` for the `.rle-pin` (Tailwind-free `/styled`) equivalent.
203
+ */
204
+ declare function PropertyMarker({ point }: IListingMarkerProps): react.JSX.Element;
205
+
206
+ /**
207
+ * Porting today's `/find` widget's fixed filter set (price, beds/baths,
208
+ * property-type, keyword) to `FilterDefinition`s. Each `render` is an
209
+ * unstyled control from `./controls/*`; consumers restyle/replace via
210
+ * `FilterRegistry.replace()`.
211
+ *
212
+ * `render`'s `ComponentType<FilterControlProps<TValue>>` is (correctly)
213
+ * contravariant in `TValue`, so this heterogeneous array of concretely-typed
214
+ * defs can't assign element-by-element into `FilterDefinition<RentalFilters>[]`
215
+ * (TValue defaults to `unknown`) without a cast -- `FilterRegistry` hits the
216
+ * identical shape and resolves it by storing defs as `..., any>` internally
217
+ * (see `core/registries/filter-registry.ts`); same fix here, scoped to this
218
+ * one array literal. The exported type stays `FilterDefinition<RentalFilters>[]`,
219
+ * `any`-free.
220
+ */
221
+ declare const rentalFilters: FilterDefinition<RentalFilters>[];
222
+ /** `withFilters(withRentalFilters())` registers all 5 rental filters, in order, on a fresh or existing `FilterRegistry`. */
223
+ declare function withRentalFilters(): (registry: FilterRegistry<RentalFilters>) => void;
224
+
225
+ /**
226
+ * `RentalFilters` -> `QueryParams` for URL sync. Short, stable query keys
227
+ * (`minPrice`, `maxPrice`, `minBeds`, `maxBeds`, `minBaths`, `maxBaths`,
228
+ * `type`, `q`) -- `type` is a comma-joined `PropertyType[]`, `keyword` maps
229
+ * to `q`. `bounds` is intentionally NOT serialized: it's map-driven,
230
+ * high-frequency, transient viewport state, not a shareable filter.
231
+ */
232
+ declare function rentalFiltersToQuery(filters: RentalFilters): QueryParams;
233
+ /**
234
+ * Inverse of `rentalFiltersToQuery`. Numbers are parsed NaN-safely (an
235
+ * unparsable value is omitted, not coerced to `NaN`); `type` is split on
236
+ * `,` and filtered down to known `PropertyType` values (an unrecognized
237
+ * value in the URL -- hand-edited or stale -- is silently dropped rather
238
+ * than propagated into `RentalFilters`). Missing/empty query keys produce
239
+ * an omitted (not `undefined`-valued) filter field.
240
+ */
241
+ declare function rentalFiltersFromQuery(query: QueryParams): RentalFilters;
242
+ interface RentalUrlSyncOptions {
243
+ history?: HistoryPort;
244
+ mode?: 'replace' | 'push';
245
+ hydrateOnStart?: boolean;
246
+ }
247
+ /**
248
+ * Convenience factory wiring `rentalFiltersToQuery`/`rentalFiltersFromQuery`
249
+ * onto a `UrlSyncController<RentalFilters>`, ready to pass as
250
+ * `composeListingProviders`' `urlSync` option (or straight into
251
+ * `UrlSyncController.start(engine)`).
252
+ *
253
+ * OPTIONAL helper, not the primary URL-sync path for the main-entry
254
+ * `ListingApp` (`~/styled/listing-app`) — that component takes
255
+ * `initialFilters`/`onFiltersChange` instead and never touches
256
+ * `window.history` itself (see `UrlSyncController`'s doc comment). For that
257
+ * event-based API, use `rentalFiltersToQuery`/`rentalFiltersFromQuery`
258
+ * directly: `initialFilters={rentalFiltersFromQuery(query)}` and
259
+ * `onFiltersChange={f => history.replaceState(null, '', '?' +
260
+ * new URLSearchParams(rentalFiltersToQuery(f)))}`. `rentalUrlSync()` remains
261
+ * the right choice when wiring `/shadcn`'s `ListingApp.urlSync` or a
262
+ * hand-built engine where the library owning `window.history` is desired.
263
+ */
264
+ declare function rentalUrlSync(opts?: RentalUrlSyncOptions): UrlSyncController<RentalFilters>;
265
+
266
+ export { type BusinessCategory, type BusinessEntity, type BusinessFilters, type BusinessesApiPort, KNOWN_BUSINESS_CATEGORIES, KeywordFilterControl, type NearbyBusinessesOptions, type PropertiesApiPort, type PropertiesDatasetOptions, PropertyCard, type PropertyEntity, PropertyMarker, type PropertyType, PropertyTypeFilterControl, RangeFilterControl, type RangeValue, type RentalFilters, type RentalUrlSyncOptions, defaultPriceMarkerElement, formatRentalPrice, nearbyBusinessesDataset, propertiesDataset, rentalFilters, rentalFiltersFromQuery, rentalFiltersToQuery, rentalUrlSync, withRentalFilters };
@@ -0,0 +1,266 @@
1
+ import { E as EntityId, L as LatLng, B as Bounds, b as PageRequest, P as Page, Q as QueryParams } from '../../entity-adapter.interface-BDgbSxhq.js';
2
+ import { D as DatasetDefinition, a as FilterControlProps, c as IListingCardProps, h as IListingMarkerProps, b as FilterDefinition, F as FilterRegistry } from '../../components-provider-CjMxkxrP.js';
3
+ import * as react from 'react';
4
+ import { H as HistoryPort, U as UrlSyncController } from '../../url-sync.controller-DX67JivW.js';
5
+
6
+ /**
7
+ * Deliberately an open string type, not a union -- new business categories
8
+ * (a new row of DATA, e.g. `'pharmacy'`) need zero library/core change to
9
+ * flow through `nearbyBusinessesDataset`. `KNOWN_BUSINESS_CATEGORIES` below
10
+ * is a seeded starter list for consumers building category pickers, not an
11
+ * exhaustive/closed taxonomy.
12
+ */
13
+ type BusinessCategory = string;
14
+ declare const KNOWN_BUSINESS_CATEGORIES: readonly ["grocery", "shopping", "restaurants", "schools", "hospitals", "parks", "gyms", "cafes"];
15
+ interface BusinessEntity {
16
+ id: EntityId;
17
+ name: string;
18
+ category: BusinessCategory;
19
+ coordinates: LatLng;
20
+ address?: string;
21
+ }
22
+ interface BusinessFilters {
23
+ categories?: BusinessCategory[];
24
+ bounds?: Bounds;
25
+ }
26
+ /**
27
+ * The "port" the CONSUMER implements against their real API -- same shape of
28
+ * seam as `PropertiesApiPort`. `list` is optional: many consumers only ever
29
+ * render businesses as a map layer (`search`) and never page through them in
30
+ * a results list.
31
+ */
32
+ interface BusinessesApiPort {
33
+ search(filters: BusinessFilters, bounds: Bounds): Promise<BusinessEntity[]>;
34
+ list?(filters: BusinessFilters, page: PageRequest): Promise<Page<BusinessEntity>>;
35
+ }
36
+ interface NearbyBusinessesOptions {
37
+ /** Restrict which categories render; default (omitted) shows everything `search` returns. */
38
+ categories?: BusinessCategory[];
39
+ /** Per-category icon URL -- DATA, not code. New category = new map entry. */
40
+ icons?: Record<string, string>;
41
+ onClick?(business: BusinessEntity): void;
42
+ }
43
+ /**
44
+ * Wraps a `BusinessesApiPort` into a `DatasetDefinition` -- the nearby
45
+ * businesses marker layer, proving req #6: this is just a SECOND
46
+ * dataset/marker layer sitting next to `propertiesDataset`, and the category
47
+ * taxonomy is DATA (`opts.categories` / `opts.icons`), not core code.
48
+ */
49
+ declare function nearbyBusinessesDataset(api: BusinessesApiPort, opts?: NearbyBusinessesOptions): DatasetDefinition<BusinessEntity, BusinessFilters>;
50
+
51
+ /** Unstyled free-text keyword input. */
52
+ declare function KeywordFilterControl({ value, onChange }: FilterControlProps<string>): react.JSX.Element;
53
+
54
+ type PropertyType = 'house' | 'apartment' | 'condo' | 'townhouse' | 'land';
55
+ /**
56
+ * Mapped from the app's `@libs/core-base` `Listing` shape (nested
57
+ * `property.coordinates`) -- not the old flat `/find` DTO. See design doc
58
+ * D6/#11.
59
+ */
60
+ interface PropertyEntity {
61
+ id: EntityId;
62
+ title: string;
63
+ address?: string;
64
+ coordinates: LatLng;
65
+ price: number;
66
+ bedrooms: number;
67
+ bathrooms: number;
68
+ propertyType: PropertyType;
69
+ imageUrl?: string;
70
+ }
71
+ interface RentalFilters {
72
+ minPrice?: number;
73
+ maxPrice?: number;
74
+ minBeds?: number;
75
+ maxBeds?: number;
76
+ minBaths?: number;
77
+ maxBaths?: number;
78
+ propertyTypes?: PropertyType[];
79
+ keyword?: string;
80
+ bounds?: Bounds;
81
+ }
82
+ /** Generic min/max shape shared by every range-style filter control (price, beds, baths). */
83
+ interface RangeValue {
84
+ min?: number;
85
+ max?: number;
86
+ }
87
+
88
+ /**
89
+ * Compact dropdown for the property-type multi-select -- a native
90
+ * `<details>`/`<summary>` disclosure (no Radix dependency, so this preset
91
+ * package doesn't have to pull one in) instead of the previous
92
+ * always-expanded checkbox list, so the top filter bar (`ListingLayout`'s
93
+ * horizontal row) stays compact regardless of how many property types exist.
94
+ * `<summary>` is the button-like trigger: "Property type" plus a count badge
95
+ * once 1+ types are selected. The panel (absolutely positioned so it doesn't
96
+ * push sibling filter groups around) holds one checkbox per `PropertyType`,
97
+ * unchanged from the previous always-expanded version.
98
+ *
99
+ * `open` is fully CONTROLLED local state, not left to `<details>`'s own
100
+ * built-in toggle -- the trigger's `onClick` calls `event.preventDefault()`
101
+ * (which, per spec, suppresses `<summary>`'s native open/close activation for
102
+ * that click) and flips `open` itself instead. This makes `aria-expanded`
103
+ * always exactly match what's rendered, and makes the open/close behavior
104
+ * independent of a given DOM engine's level of native `<details>` support --
105
+ * it doesn't rely on a `toggle` event firing. Keyboard access is unaffected:
106
+ * `preventDefault()` only suppresses the native toggle side effect, not the
107
+ * click event itself, so Enter/Space on a focused `<summary>` (which the
108
+ * browser turns into a `click`) still reaches this handler. Outside click is
109
+ * NOT handled (native `<details>` doesn't require it either) -- clicking
110
+ * elsewhere leaves the panel open until the trigger (or a checkbox) is
111
+ * clicked again; not fixed here, matching the design brief.
112
+ */
113
+ declare function PropertyTypeFilterControl({ value, onChange }: FilterControlProps<PropertyType[]>): react.JSX.Element;
114
+
115
+ /**
116
+ * Unstyled min/max number inputs for any `RangeValue`-shaped filter (price,
117
+ * beds, baths). Deliberately generic -- no per-domain label prop -- consumers
118
+ * restyle/relabel via a shadcn wrapper or their own `render` override.
119
+ */
120
+ declare function RangeFilterControl({ value, onChange }: FilterControlProps<RangeValue>): react.JSX.Element;
121
+
122
+ /**
123
+ * The "port" the CONSUMER implements against their real API (e.g. the app's
124
+ * `LocaListingApi`) -- this preset never imports an HTTP client itself. `list`
125
+ * feeds the paginated results list, `search` feeds the map (bounds-scoped,
126
+ * no pagination), `getById` is optional (deep-link / selection lookups).
127
+ */
128
+ interface PropertiesApiPort {
129
+ list(filters: RentalFilters, page: PageRequest): Promise<Page<PropertyEntity>>;
130
+ search(filters: RentalFilters, bounds: Bounds): Promise<PropertyEntity[]>;
131
+ getById?(id: EntityId): Promise<PropertyEntity>;
132
+ }
133
+ interface PropertiesDatasetOptions {
134
+ onClick?(property: PropertyEntity): void;
135
+ iconUrl?(property: PropertyEntity): string;
136
+ /**
137
+ * Builds the `AdvancedMarkerElement` content for a property's map pin.
138
+ * Defaults to `defaultPriceMarkerElement` (a green teardrop price pin
139
+ * showing `formatRentalPrice(property.price)`) when omitted -- pass this
140
+ * to replace the pin entirely with a custom marker.
141
+ */
142
+ element?(property: PropertyEntity): HTMLElement;
143
+ }
144
+ /**
145
+ * Default `marker.element` builder: a raw DOM teardrop price PIN
146
+ * (`formatRentalPrice(entity.price)` in a rounded pill, plus a small
147
+ * rotated-square pointer tail so the marker's visual tip lands on the exact
148
+ * coordinate -- Rentler-style) for the map's `AdvancedMarkerElement` content
149
+ * -- `GoogleMapsProvider` prefers `element` over `iconUrl` (see
150
+ * `google-maps.provider.ts`) when both are set.
151
+ *
152
+ * Built with `document.createElement` rather than React because map markers
153
+ * are raw DOM nodes, not React elements (rendering the injected `Marker`
154
+ * React component INTO a real marker via a portal is a documented future
155
+ * enhancement -- see `listing-map.tsx`'s doc comment). Since this element is
156
+ * constructed at runtime, Tailwind's build-time content scanner can't see
157
+ * the `className` strings below to generate their CSS, so the classes are
158
+ * kept (any consumer app that DOES happen to scan this file's literal
159
+ * strings, e.g. via a broad `content` glob, gets real utility classes) but
160
+ * are backed by the same values set directly as inline styles on both the
161
+ * pill and its pointer child, so the pin renders correctly with zero
162
+ * build-time cooperation from the consumer's Tailwind config. Mirrors
163
+ * `.rle-pin`/`.rle-pin::after` in `src/styled/styles.css` (the `/styled`
164
+ * equivalent for `StyledMarker`) -- a pseudo-element isn't an option here
165
+ * since inline styles can't target `::after` on a JS-constructed node, so
166
+ * the pointer is a real child element instead.
167
+ */
168
+ declare function defaultPriceMarkerElement(property: PropertyEntity): HTMLElement;
169
+ /**
170
+ * Wraps a `PropertiesApiPort` into a `DatasetDefinition` -- the properties
171
+ * marker layer. Row -> `MapPoint` mapping reads `entity.coordinates`
172
+ * directly; no other core/library change is needed to add this layer to a
173
+ * listing.
174
+ */
175
+ declare function propertiesDataset(api: PropertiesApiPort, opts?: PropertiesDatasetOptions): DatasetDefinition<PropertyEntity, RentalFilters>;
176
+
177
+ /** Formats a whole-dollar rental price as USD, e.g. `2800` -> `"$2,800"`. */
178
+ declare function formatRentalPrice(price: number): string;
179
+ /**
180
+ * Real-estate `Card` slot matching the production `/find` widget's property
181
+ * card layout: image -> title -> address -> "Type · N bd · N ba" -> price.
182
+ * `item` is `unknown` at this layer (the engine is entity-erased, same
183
+ * reasoning as `DefaultCard` -- see `src/shadcn/default-card.tsx`), so it's
184
+ * read DEFENSIVELY via a cast to `PropertyEntity`, never assumed to be the
185
+ * caller's real `TEntity`. Every field is optional and simply omitted from
186
+ * the render when absent.
187
+ *
188
+ * Mirrors `DefaultCard`'s interactive/non-interactive split: the whole card
189
+ * is a `button` (keyboard operable, `aria-pressed`) when `onSelect` is
190
+ * given, otherwise a non-interactive `<article>` with the same visual
191
+ * classes minus the button/focus/hover-shadow semantics.
192
+ */
193
+ declare function PropertyCard({ item, selected, onSelect }: IListingCardProps): react.JSX.Element;
194
+ /**
195
+ * Real-estate `Marker` slot: a green teardrop price pin (Rentler-style) for
196
+ * a map point -- a rounded pill plus a small rotated-square tail so the
197
+ * marker's visual tip lands on the exact coordinate. Same defensive
198
+ * view-model reasoning as `DefaultMarker` -- `point.entity` is the raw row
199
+ * (`unknown` at this layer), read defensively via a cast. Tailwind-styled by
200
+ * design (not `.rle-*`) -- see `properties-dataset.ts`'s `defaultPriceMarkerElement`
201
+ * for the raw-DOM equivalent used by the map's own marker content, and
202
+ * `StyledMarker` for the `.rle-pin` (Tailwind-free `/styled`) equivalent.
203
+ */
204
+ declare function PropertyMarker({ point }: IListingMarkerProps): react.JSX.Element;
205
+
206
+ /**
207
+ * Porting today's `/find` widget's fixed filter set (price, beds/baths,
208
+ * property-type, keyword) to `FilterDefinition`s. Each `render` is an
209
+ * unstyled control from `./controls/*`; consumers restyle/replace via
210
+ * `FilterRegistry.replace()`.
211
+ *
212
+ * `render`'s `ComponentType<FilterControlProps<TValue>>` is (correctly)
213
+ * contravariant in `TValue`, so this heterogeneous array of concretely-typed
214
+ * defs can't assign element-by-element into `FilterDefinition<RentalFilters>[]`
215
+ * (TValue defaults to `unknown`) without a cast -- `FilterRegistry` hits the
216
+ * identical shape and resolves it by storing defs as `..., any>` internally
217
+ * (see `core/registries/filter-registry.ts`); same fix here, scoped to this
218
+ * one array literal. The exported type stays `FilterDefinition<RentalFilters>[]`,
219
+ * `any`-free.
220
+ */
221
+ declare const rentalFilters: FilterDefinition<RentalFilters>[];
222
+ /** `withFilters(withRentalFilters())` registers all 5 rental filters, in order, on a fresh or existing `FilterRegistry`. */
223
+ declare function withRentalFilters(): (registry: FilterRegistry<RentalFilters>) => void;
224
+
225
+ /**
226
+ * `RentalFilters` -> `QueryParams` for URL sync. Short, stable query keys
227
+ * (`minPrice`, `maxPrice`, `minBeds`, `maxBeds`, `minBaths`, `maxBaths`,
228
+ * `type`, `q`) -- `type` is a comma-joined `PropertyType[]`, `keyword` maps
229
+ * to `q`. `bounds` is intentionally NOT serialized: it's map-driven,
230
+ * high-frequency, transient viewport state, not a shareable filter.
231
+ */
232
+ declare function rentalFiltersToQuery(filters: RentalFilters): QueryParams;
233
+ /**
234
+ * Inverse of `rentalFiltersToQuery`. Numbers are parsed NaN-safely (an
235
+ * unparsable value is omitted, not coerced to `NaN`); `type` is split on
236
+ * `,` and filtered down to known `PropertyType` values (an unrecognized
237
+ * value in the URL -- hand-edited or stale -- is silently dropped rather
238
+ * than propagated into `RentalFilters`). Missing/empty query keys produce
239
+ * an omitted (not `undefined`-valued) filter field.
240
+ */
241
+ declare function rentalFiltersFromQuery(query: QueryParams): RentalFilters;
242
+ interface RentalUrlSyncOptions {
243
+ history?: HistoryPort;
244
+ mode?: 'replace' | 'push';
245
+ hydrateOnStart?: boolean;
246
+ }
247
+ /**
248
+ * Convenience factory wiring `rentalFiltersToQuery`/`rentalFiltersFromQuery`
249
+ * onto a `UrlSyncController<RentalFilters>`, ready to pass as
250
+ * `composeListingProviders`' `urlSync` option (or straight into
251
+ * `UrlSyncController.start(engine)`).
252
+ *
253
+ * OPTIONAL helper, not the primary URL-sync path for the main-entry
254
+ * `ListingApp` (`~/styled/listing-app`) — that component takes
255
+ * `initialFilters`/`onFiltersChange` instead and never touches
256
+ * `window.history` itself (see `UrlSyncController`'s doc comment). For that
257
+ * event-based API, use `rentalFiltersToQuery`/`rentalFiltersFromQuery`
258
+ * directly: `initialFilters={rentalFiltersFromQuery(query)}` and
259
+ * `onFiltersChange={f => history.replaceState(null, '', '?' +
260
+ * new URLSearchParams(rentalFiltersToQuery(f)))}`. `rentalUrlSync()` remains
261
+ * the right choice when wiring `/shadcn`'s `ListingApp.urlSync` or a
262
+ * hand-built engine where the library owning `window.history` is desired.
263
+ */
264
+ declare function rentalUrlSync(opts?: RentalUrlSyncOptions): UrlSyncController<RentalFilters>;
265
+
266
+ export { type BusinessCategory, type BusinessEntity, type BusinessFilters, type BusinessesApiPort, KNOWN_BUSINESS_CATEGORIES, KeywordFilterControl, type NearbyBusinessesOptions, type PropertiesApiPort, type PropertiesDatasetOptions, PropertyCard, type PropertyEntity, PropertyMarker, type PropertyType, PropertyTypeFilterControl, RangeFilterControl, type RangeValue, type RentalFilters, type RentalUrlSyncOptions, defaultPriceMarkerElement, formatRentalPrice, nearbyBusinessesDataset, propertiesDataset, rentalFilters, rentalFiltersFromQuery, rentalFiltersToQuery, rentalUrlSync, withRentalFilters };
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{a as P}from"../../chunk-5FBI2WIM.js";import{a as h}from"../../chunk-6PZHSHU3.js";import{a as x}from"../../chunk-35KYEBAC.js";var Y=["grocery","shopping","restaurants","schools","hospitals","parks","gyms","cafes"];function _(e,t){let n=e.list;return{id:"businesses",adapter:{list:n?(r,o)=>n(r,o):async()=>({items:[],nextCursor:null}),getPoints:(r,o)=>e.search(r,o).then(s=>s.filter(i=>!t?.categories||t.categories.includes(i.category))).then(s=>s.map(i=>({id:i.id,position:i.coordinates,entity:i})))},marker:{iconUrl:r=>t?.icons?.[r.category]??"",onClick:t?.onClick},clustering:{maxZoom:15},visible:()=>!0}}import{jsx as C}from"react/jsx-runtime";function b({value:e,onChange:t}){return C("input",{type:"search",value:e,onChange:r=>{t(r.target.value)},"aria-label":"Keyword",placeholder:"Search keyword",className:"h-9 w-full rounded-md border border-border bg-background px-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"})}import{useState as E}from"react";import{jsx as m,jsxs as u}from"react/jsx-runtime";var k=["house","apartment","condo","townhouse","land"],N={house:"House",apartment:"Apartment",condo:"Condo",townhouse:"Townhouse",land:"Land"};function F({value:e,onChange:t}){let[n,r]=E(!1),o=i=>l=>{t(l.target.checked?[...e,i]:e.filter(c=>c!==i))};return u("details",{open:n,className:"group relative",children:[u("summary",{onClick:i=>{i.preventDefault(),r(l=>!l)},"aria-expanded":n,className:"flex h-9 w-fit cursor-pointer list-none items-center gap-1.5 rounded-md border border-border bg-background px-3 text-sm text-foreground motion-safe:transition-colors hover:bg-foreground/[0.06] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden",children:["Property type",e.length>0&&m("span",{className:"inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold tabular-nums text-primary-foreground",children:e.length}),m("svg",{"aria-hidden":"true",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",className:"size-3.5 shrink-0 text-muted-foreground motion-safe:transition-transform group-open:rotate-180",children:m("path",{d:"m6 9 6 6 6-6"})})]}),u("fieldset",{className:"absolute z-10 mt-1.5 flex w-48 flex-col gap-1.5 rounded-md border border-border bg-popover p-3 text-popover-foreground shadow-md",children:[m("legend",{className:"sr-only",children:"Property type"}),k.map(i=>u("label",{className:"flex items-center gap-2 text-sm",children:[m("input",{type:"checkbox",checked:e.includes(i),onChange:o(i),className:"size-4 rounded border-border accent-primary"}),N[i]]},i))]})]})}import{jsx as g,jsxs as T}from"react/jsx-runtime";function R(e){if(e==="")return;let t=Number(e);return Number.isNaN(t)?void 0:t}function y({value:e,onChange:t}){let n=s=>{t({...e,min:R(s.target.value)})},r=s=>{t({...e,max:R(s.target.value)})},o="h-9 w-full rounded-md border border-border bg-background px-2 text-sm tabular-nums text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";return T("div",{className:"flex items-center gap-2",children:[g("input",{type:"number",inputMode:"numeric","aria-label":"Minimum",placeholder:"Min",value:e.min??"",onChange:n,className:o}),g("span",{"aria-hidden":"true",className:"text-muted-foreground",children:"-"}),g("input",{type:"number",inputMode:"numeric","aria-label":"Maximum",placeholder:"Max",value:e.max??"",onChange:r,className:o})]})}import{Fragment as w,jsx as a,jsxs as d}from"react/jsx-runtime";function f(e){return new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(e)}function v(e){return e.length?`${e[0].toUpperCase()}${e.slice(1)}`:e}function J({item:e,selected:t,onSelect:n}){let r=e,o=h("flex w-full flex-col overflow-hidden rounded-[10px] border border-border bg-card text-left text-card-foreground","motion-safe:transition-shadow",n&&"hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",t&&"ring-2 ring-ring"),s=d(w,{children:[r?.imageUrl?a("img",{src:r.imageUrl,alt:r.title??"",className:"aspect-[4/3] w-full rounded-t-[10px] object-cover"}):a("div",{className:"aspect-[4/3] w-full rounded-t-[10px] bg-muted","aria-hidden":"true"}),d("div",{className:"flex flex-1 flex-col gap-1 p-3",children:[r?.title&&a("span",{className:"text-base font-semibold",children:r.title}),r?.address&&a("span",{className:"text-sm text-muted-foreground",children:r.address}),r?.propertyType!=null&&r.bedrooms!=null&&r.bathrooms!=null&&d("span",{className:"text-sm text-muted-foreground",children:[v(r.propertyType)," \xB7 ",r.bedrooms," bd \xB7 ",r.bathrooms," ba"]}),r?.price!=null&&d("span",{className:"mt-1 font-bold tabular-nums",children:[f(r.price)," ",a("span",{className:"text-sm font-normal text-muted-foreground",children:"/mo"})]})]})]});return n?a("button",{type:"button",onClick:n,"aria-pressed":t??!1,className:o,children:s}):a("article",{className:o,children:s})}function ee({point:e}){let t=e.entity;return d("span",{className:"relative inline-flex items-center whitespace-nowrap rounded-full bg-primary px-2.5 py-1 text-xs font-bold tabular-nums text-primary-foreground shadow",children:[t?.price!=null?f(t.price):"",a("span",{"aria-hidden":"true",className:"absolute -bottom-[3px] left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 rounded-br-[2px] bg-primary"})]})}function S(e){let t=document.createElement("div");t.className="relative inline-flex items-center whitespace-nowrap rounded-full bg-primary px-2.5 py-1 text-xs font-bold text-primary-foreground shadow",t.textContent=f(e.price),t.style.position="relative",t.style.display="inline-flex",t.style.alignItems="center",t.style.whiteSpace="nowrap",t.style.borderRadius="9999px",t.style.backgroundColor="var(--primary, #16a34a)",t.style.color="var(--primary-foreground, #ffffff)",t.style.padding="4px 10px",t.style.fontSize="12px",t.style.fontWeight="700",t.style.fontFamily="inherit",t.style.boxShadow="0 1px 2px rgba(0, 0, 0, 0.3)";let n=document.createElement("span");return n.className="absolute -bottom-[3px] left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 rounded-br-[2px] bg-primary",n.style.position="absolute",n.style.left="50%",n.style.bottom="-3px",n.style.width="8px",n.style.height="8px",n.style.borderRadius="0 0 2px 0",n.style.backgroundColor="var(--primary, #16a34a)",n.style.transform="translateX(-50%) rotate(45deg)",t.appendChild(n),t}function ie(e,t){let n=e.getById;return{id:"properties",adapter:{list:(r,o)=>e.list(r,o),getPoints:(r,o)=>e.search(r,o).then(s=>s.map(i=>({id:i.id,position:i.coordinates,entity:i}))),getById:n?r=>n(r):void 0},marker:{iconUrl:t?.iconUrl,element:t?.element??S,onClick:t?.onClick},clustering:{maxZoom:14}}}var D={key:"price",order:10,label:"Price",render:y,toParams:e=>({minPrice:e.min,maxPrice:e.max}),fromParams:e=>({min:e.minPrice,max:e.maxPrice}),isActive:e=>e.minPrice!=null||e.maxPrice!=null},I={key:"beds",order:20,label:"Bedrooms",render:y,toParams:e=>({minBeds:e.min,maxBeds:e.max}),fromParams:e=>({min:e.minBeds,max:e.maxBeds}),isActive:e=>e.minBeds!=null||e.maxBeds!=null},M={key:"baths",order:30,label:"Bathrooms",render:y,toParams:e=>({minBaths:e.min,maxBaths:e.max}),fromParams:e=>({min:e.minBaths,max:e.maxBaths}),isActive:e=>e.minBaths!=null||e.maxBaths!=null},L={key:"propertyType",order:40,label:"Property type",render:F,toParams:e=>({propertyTypes:e.length?e:void 0}),fromParams:e=>e.propertyTypes??[],isActive:e=>(e.propertyTypes?.length??0)>0},O={key:"keyword",order:50,label:"Keyword",render:b,toParams:e=>({keyword:e||void 0}),fromParams:e=>e.keyword??"",isActive:e=>!!e.keyword},U=[D,I,M,L,O];function pe(){return e=>{U.forEach(t=>e.add(t))}}var A=["house","apartment","condo","townhouse","land"];function V(e){return A.includes(e)}function p(e){if(e===void 0||e==="")return;let t=Number(e);return Number.isNaN(t)?void 0:t}function H(e){let t={};return e.minPrice!=null&&(t.minPrice=String(e.minPrice)),e.maxPrice!=null&&(t.maxPrice=String(e.maxPrice)),e.minBeds!=null&&(t.minBeds=String(e.minBeds)),e.maxBeds!=null&&(t.maxBeds=String(e.maxBeds)),e.minBaths!=null&&(t.minBaths=String(e.minBaths)),e.maxBaths!=null&&(t.maxBaths=String(e.maxBaths)),e.propertyTypes?.length&&(t.type=e.propertyTypes.join(",")),e.keyword&&(t.q=e.keyword),t}function Q(e){let t={},n=p(e.minPrice);n!==void 0&&(t.minPrice=n);let r=p(e.maxPrice);r!==void 0&&(t.maxPrice=r);let o=p(e.minBeds);o!==void 0&&(t.minBeds=o);let s=p(e.maxBeds);s!==void 0&&(t.maxBeds=s);let i=p(e.minBaths);i!==void 0&&(t.minBaths=i);let l=p(e.maxBaths);l!==void 0&&(t.maxBaths=l);let c=e.type?.split(",").map(B=>B.trim()).filter(V);return c?.length&&(t.propertyTypes=c),e.q&&(t.keyword=e.q),t}function ue(e={}){let t=e.history??new P({mode:e.mode});return new x({history:t,toQuery:H,toFilters:Q,hydrateOnStart:e.hydrateOnStart})}export{Y as KNOWN_BUSINESS_CATEGORIES,b as KeywordFilterControl,J as PropertyCard,ee as PropertyMarker,F as PropertyTypeFilterControl,y as RangeFilterControl,S as defaultPriceMarkerElement,f as formatRentalPrice,_ as nearbyBusinessesDataset,ie as propertiesDataset,U as rentalFilters,Q as rentalFiltersFromQuery,H as rentalFiltersToQuery,ue as rentalUrlSync,pe as withRentalFilters};
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
+ var _chunk5O36WI7Hcjs = require('../chunk-5O36WI7H.cjs');var _chunkLCQZIWBOcjs = require('../chunk-LCQZIWBO.cjs');var _chunk2VUHLHHXcjs = require('../chunk-2VUHLHHX.cjs');var _jsxruntime = require('react/jsx-runtime');function oe(e){return typeof e!="number"?e:new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(e)}function w({item:e,selected:i,onSelect:t}){let r=_nullishCoalesce(e, () => ({})),l=_chunkLCQZIWBOcjs.a.call(void 0, "flex w-full flex-col overflow-hidden rounded-lg border bg-card text-left text-card-foreground shadow-sm","motion-safe:transition-shadow",t&&"hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",i&&"ring-2 ring-ring"),a=_jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[r.imageUrl&&_jsxruntime.jsx.call(void 0, "img",{src:r.imageUrl,alt:_nullishCoalesce(r.title, () => ("")),className:"aspect-video w-full rounded-t-lg object-cover"}),_jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-1 flex-col gap-1 p-3",children:[_jsxruntime.jsxs.call(void 0, "div",{className:"flex items-start justify-between gap-2",children:[r.title&&_jsxruntime.jsx.call(void 0, "span",{className:"font-medium",children:r.title}),r.badge&&_jsxruntime.jsx.call(void 0, "span",{className:"shrink-0 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground",children:r.badge})]}),r.subtitle&&_jsxruntime.jsx.call(void 0, "span",{className:"text-sm text-muted-foreground",children:r.subtitle}),r.price!=null&&_jsxruntime.jsx.call(void 0, "span",{className:"mt-1 font-semibold tabular-nums",children:oe(r.price)})]})]});return t?_jsxruntime.jsx.call(void 0, "button",{type:"button",onClick:t,"aria-pressed":_nullishCoalesce(i, () => (!1)),className:l,children:a}):_jsxruntime.jsx.call(void 0, "article",{className:l,children:a})}function C(e){return _jsxruntime.jsxs.call(void 0, "div",{role:"status",className:"flex flex-col items-center justify-center gap-2 px-4 py-12 text-center text-muted-foreground",children:[_jsxruntime.jsxs.call(void 0, "svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round",className:"h-8 w-8","aria-hidden":"true",children:[_jsxruntime.jsx.call(void 0, "circle",{cx:"11",cy:"11",r:"7"}),_jsxruntime.jsx.call(void 0, "path",{d:"m21 21-4.3-4.3"})]}),_jsxruntime.jsx.call(void 0, "p",{className:"text-sm font-medium text-foreground",children:"No results"}),_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-muted-foreground",children:"Try adjusting your filters or search terms."})]})}function I({children:e}){return _jsxruntime.jsx.call(void 0, "div",{className:"flex flex-col gap-3",children:e})}function D(e){return _jsxruntime.jsx.call(void 0, "div",{className:"flex flex-col gap-3 p-3",role:"status","aria-busy":"true","aria-label":"Loading results",children:Array.from({length:3}).map((i,t)=>_jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-col gap-2 motion-safe:animate-pulse",children:[_jsxruntime.jsx.call(void 0, "div",{className:"aspect-video w-full rounded-md bg-muted"}),_jsxruntime.jsx.call(void 0, "div",{className:"h-4 w-2/3 rounded-md bg-muted"}),_jsxruntime.jsx.call(void 0, "div",{className:"h-3 w-1/3 rounded-md bg-muted"})]},t))})}function M({point:e}){let i=_nullishCoalesce(e.entity, () => ({}));return _jsxruntime.jsx.call(void 0, "span",{className:"rounded-full border bg-background px-2 py-0.5 text-xs font-medium tabular-nums shadow-sm",children:_nullishCoalesce(i.price, () => (""))})}function F({entity:e,onClose:i}){let t=_nullishCoalesce(e, () => ({}));return _jsxruntime.jsxs.call(void 0, "div",{className:"relative w-64 rounded-lg border bg-popover p-3 text-popover-foreground shadow-md",role:"group","aria-label":"Location details",children:[_jsxruntime.jsx.call(void 0, "button",{type:"button",onClick:i,"aria-label":"Close",className:"absolute right-2 top-2 rounded-md p-1 text-muted-foreground motion-safe:transition-colors hover:bg-foreground/[0.06] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",children:_jsxruntime.jsxs.call(void 0, "svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",className:"h-4 w-4","aria-hidden":"true",children:[_jsxruntime.jsx.call(void 0, "path",{d:"M18 6 6 18"}),_jsxruntime.jsx.call(void 0, "path",{d:"m6 6 12 12"})]})}),_jsxruntime.jsxs.call(void 0, "div",{className:"pr-6",children:[t.title&&_jsxruntime.jsx.call(void 0, "div",{className:"font-medium",children:t.title}),t.subtitle&&_jsxruntime.jsx.call(void 0, "div",{className:"text-sm text-muted-foreground",children:t.subtitle}),t.price!=null&&_jsxruntime.jsx.call(void 0, "div",{className:"mt-1 font-semibold tabular-nums",children:t.price})]})]})}function S({count:e,total:i}){let t=i!=null&&i!==e?`${e} of ${i} results`:`${e} results`;return _jsxruntime.jsx.call(void 0, "div",{className:"text-sm tabular-nums text-muted-foreground",children:t})}function T({value:e,onChange:i,placeholder:t}){return _jsxruntime.jsx.call(void 0, "input",{type:"search",value:e,placeholder:t,onChange:r=>i(r.target.value),"aria-label":_nullishCoalesce(t, () => ("Search")),className:_chunkLCQZIWBOcjs.a.call(void 0, "h-9 w-full rounded-md border bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground","focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring")})}function R({children:e}){return _jsxruntime.jsx.call(void 0, "aside",{className:"flex w-full flex-col gap-4 p-4 md:border-r md:border-border",children:e})}function A({children:e}){return _jsxruntime.jsx.call(void 0, "div",{className:"flex items-center gap-2 border-b p-2",children:e})}var g={Card:w,Marker:M,Popup:F,Sidebar:R,FilterPanel:I,Search:T,Empty:C,Loading:D,ResultHeader:S,Toolbar:A};function fe({children:e}){return _jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.p,{...g,children:e})}var _react = require('react');var ve=_jsxruntime.jsx.call(void 0, "p",{className:"px-4 text-center text-sm text-muted-foreground",children:"Map unavailable - provide a Google Maps API key"});function E({search:e,className:i,toolbarEnd:t,autoFetch:r=!0,mapCenter:l,mapZoom:a}){let p=_chunk5O36WI7Hcjs.s.call(void 0, ),{Search:u}=_chunk5O36WI7Hcjs.q.call(void 0, ),[f,y]=_react.useState.call(void 0, "list");_react.useEffect.call(void 0, ()=>{r!==!1&&p.applyFilters({})},[p,r]);let h=f==="map"?"hidden md:block":"block",P=f==="list"?"hidden md:block":"block";return _jsxruntime.jsxs.call(void 0, "div",{className:_chunkLCQZIWBOcjs.a.call(void 0, "flex h-full min-h-0 w-full flex-col bg-background text-foreground",i),"data-slot":"listing-layout",children:[_jsxruntime.jsxs.call(void 0, "div",{className:"sticky top-0 z-10 flex flex-wrap items-end gap-3 border-b border-border bg-background p-3","data-slot":"listing-layout-filter-bar",children:[e&&_jsxruntime.jsx.call(void 0, "div",{className:"w-full min-w-0 sm:w-auto sm:max-w-xs sm:flex-1",children:_jsxruntime.jsx.call(void 0, u,{value:e.value,onChange:e.onChange,placeholder:e.placeholder})}),_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.v,{className:"flex flex-wrap items-end gap-3",groupClassName:"min-w-0"}),_jsxruntime.jsxs.call(void 0, "div",{className:"ml-auto flex items-center gap-3",children:[_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.A,{}),t]})]}),_jsxruntime.jsx.call(void 0, "div",{className:"flex items-center justify-center gap-1 border-b border-border bg-background p-2 md:hidden","data-slot":"listing-layout-mobile-toggle",children:_jsxruntime.jsx.call(void 0, "div",{role:"group","aria-label":"View",className:"inline-flex rounded-md border border-border p-0.5",children:[{view:"list",label:"List"},{view:"map",label:"Map"}].map(({view:d,label:n})=>_jsxruntime.jsx.call(void 0, "button",{type:"button","aria-pressed":f===d,onClick:()=>y(d),className:_chunkLCQZIWBOcjs.a.call(void 0, "rounded-[5px] px-3 py-1 text-sm font-medium motion-safe:transition-colors",f===d?"bg-primary text-primary-foreground":"text-muted-foreground hover:bg-foreground/[0.06] hover:text-foreground"),children:n},d))})}),_jsxruntime.jsxs.call(void 0, "div",{className:"grid min-h-0 flex-1 md:grid-cols-[minmax(340px,42%)_1fr]","data-slot":"listing-layout-split",children:[_jsxruntime.jsxs.call(void 0, "div",{className:_chunkLCQZIWBOcjs.a.call(void 0, "min-h-0 overflow-y-auto p-3",h),"data-slot":"listing-layout-list",children:[_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.x,{className:"grid content-start gap-3 [grid-template-columns:repeat(auto-fill,minmax(200px,1fr))]"}),_jsxruntime.jsx.call(void 0, "div",{className:"mt-4 flex justify-center",children:_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.z,{})})]}),_jsxruntime.jsx.call(void 0, "div",{className:_chunkLCQZIWBOcjs.a.call(void 0, "min-h-0 md:sticky md:top-0",P),"data-slot":"listing-layout-map",children:_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.y,{center:l,zoom:a,fallback:ve})})]})]})}function ee(e){return"provider"in e}function ye(e){let i=e!=null&&!ee(e),t=i?e.apiKey:void 0,r=i?e.mapId:void 0,[l,a]=_react.useState.call(void 0, ()=>!e||ee(e)?{ready:!0,provider:_optionalChain([e, 'optionalAccess', _2 => _2.provider])}:{ready:!1});return _react.useEffect.call(void 0, ()=>{if(!t)return;let p=!1;return Promise.resolve().then(() => _interopRequireWildcard(require("../maps/google/index.cjs"))).then(({googleProvider:u})=>{p||a({ready:!0,provider:u({apiKey:t,mapId:r})})}),()=>{p=!0}},[t,r]),l}function he(e){let{datasets:i,filters:t,map:r,components:l,urlSync:a,initialFilters:p,config:u,className:f,search:y,autoFetch:h}=e,{ready:P,provider:d}=ye(r);if(!P)return null;let n=[];for(let ie of i)n.push(_chunk5O36WI7Hcjs.k.call(void 0, ie));t&&n.push(_chunk5O36WI7Hcjs.l.call(void 0, t)),d&&n.push(_chunk5O36WI7Hcjs.j.call(void 0, d)),a instanceof _chunk2VUHLHHXcjs.a&&n.push(_chunk5O36WI7Hcjs.m.call(void 0, a)),p&&n.push(_chunk5O36WI7Hcjs.n.call(void 0, p)),u&&n.push(_chunk5O36WI7Hcjs.i.call(void 0, u));let te=_chunk5O36WI7Hcjs.h.call(void 0, ...n),re={...g,...l};return _jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.B,{...te,children:_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.p,{...re,children:_jsxruntime.jsx.call(void 0, E,{className:f,search:y,autoFetch:h,mapCenter:_optionalChain([r, 'optionalAccess', _3 => _3.center]),mapZoom:_optionalChain([r, 'optionalAccess', _4 => _4.zoom])})})})}exports.DefaultCard = w; exports.DefaultEmpty = C; exports.DefaultFilterPanel = I; exports.DefaultLoading = D; exports.DefaultMarker = M; exports.DefaultPopup = F; exports.DefaultResultHeader = S; exports.DefaultSearch = T; exports.DefaultSidebar = R; exports.DefaultToolbar = A; exports.ListingApp = he; exports.ListingComponentsProviderWithDefaults = fe; exports.ListingLayout = E; exports.cn = _chunkLCQZIWBOcjs.a; exports.shadcnDefaultComponents = g;