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.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/chunk-2T5PE5MB.js +2 -0
- package/dist/chunk-2VUHLHHX.cjs +2 -0
- package/dist/chunk-35KYEBAC.js +2 -0
- package/dist/chunk-5FBI2WIM.js +2 -0
- package/dist/chunk-5O36WI7H.cjs +2 -0
- package/dist/chunk-6PZHSHU3.js +2 -0
- package/dist/chunk-CHNUE46K.cjs +2 -0
- package/dist/chunk-LCQZIWBO.cjs +2 -0
- package/dist/chunk-N3OZ2HK2.cjs +2 -0
- package/dist/chunk-PFPHWJAA.js +2 -0
- package/dist/components-provider-CF0Mo0B8.d.cts +120 -0
- package/dist/components-provider-CjMxkxrP.d.ts +120 -0
- package/dist/entity-adapter.interface-BDgbSxhq.d.cts +33 -0
- package/dist/entity-adapter.interface-BDgbSxhq.d.ts +33 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +674 -0
- package/dist/index.d.ts +674 -0
- package/dist/index.js +2 -0
- package/dist/listing-app-De6OqpUC.d.ts +199 -0
- package/dist/listing-app-pKMQBuQI.d.cts +199 -0
- package/dist/listing-config-options.interface-ZJYY_ZvR.d.cts +12 -0
- package/dist/listing-config-options.interface-ZJYY_ZvR.d.ts +12 -0
- package/dist/map-provider.interface-BMnwW3ob.d.cts +37 -0
- package/dist/map-provider.interface-BxexMT3X.d.ts +37 -0
- package/dist/maps/google/index.cjs +2 -0
- package/dist/maps/google/index.d.cts +39 -0
- package/dist/maps/google/index.d.ts +39 -0
- package/dist/maps/google/index.js +2 -0
- package/dist/presets/rental/index.cjs +2 -0
- package/dist/presets/rental/index.d.cts +266 -0
- package/dist/presets/rental/index.d.ts +266 -0
- package/dist/presets/rental/index.js +2 -0
- package/dist/shadcn/index.cjs +2 -0
- package/dist/shadcn/index.d.cts +261 -0
- package/dist/shadcn/index.d.ts +261 -0
- package/dist/shadcn/index.js +2 -0
- package/dist/styled/index.cjs +2 -0
- package/dist/styled/index.d.cts +123 -0
- package/dist/styled/index.d.ts +123 -0
- package/dist/styled/index.js +2 -0
- package/dist/styles.css +972 -0
- package/dist/testing/index.cjs +2 -0
- package/dist/testing/index.d.cts +51 -0
- package/dist/testing/index.d.ts +51 -0
- package/dist/testing/index.js +2 -0
- package/dist/url-sync.controller-CsU_QxoS.d.cts +89 -0
- package/dist/url-sync.controller-DX67JivW.d.ts +89 -0
- package/package.json +143 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 knazark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# react-listing-engine
|
|
2
|
+
|
|
3
|
+
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. Drop into property search, business directories, store locators, or any map+list browse experience.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Headless core + React bindings.** `~/core` is framework-agnostic TypeScript (no React import); `react-listing-engine` adds hooks and compound components on top. The styled `/shadcn` adapter is opt-in.
|
|
8
|
+
- **Generic over any entity.** Implement `EntityAdapter<TEntity, TFilters>` (`list`, `getPoints`, optional `getById`) against your own API — the engine never assumes a shape. URL serialization is a separate concern, handled by `UrlSyncController` (see `withUrlSync`), not the adapter.
|
|
9
|
+
- **Fully customizable filters.** `FilterRegistry` supports `add` / `remove` / `reorder` / `replace` at runtime, not just at setup.
|
|
10
|
+
- **Multiple marker layers.** `DatasetRegistry` composes any number of layers (properties, businesses, …) onto one map; each is its own `DatasetDefinition` with its own adapter and marker renderer.
|
|
11
|
+
- **Google Maps behind a provider seam.** `MapProvider` is an interface — `googleProvider({ apiKey })` is the shipped implementation. The API key always comes from your own config; there is no hardcoded fallback.
|
|
12
|
+
- **Component injection.** `ListingComponentsProvider` overrides any UI slot (`Card`, `Marker`, `Popup`, `Sidebar`, `FilterPanel`, `Search`, `Empty`, `Loading`, `ResultHeader`, `Toolbar`) — anything not provided falls back to an unstyled (or, with `/shadcn`, styled) default. `Marker` and `Popup` are defined on `IListingComponents` but **not yet wired into the map's render output** — `ListingMap` currently renders markers via each dataset's `marker.iconUrl`/`marker.element` only; injecting `Marker`/`Popup` today has no visible effect.
|
|
13
|
+
- **URL sync.** `UrlSyncController` (via `withUrlSync`) keeps filters in sync with your router/history without the engine depending on a specific router.
|
|
14
|
+
- **Dual ESM + CJS, full TypeScript types.** `sideEffects: false`, tree-shakeable subpaths, `'use client'` banners for Next.js App Router.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pnpm add react-listing-engine
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`react >=18` is a required peer dependency. Three more peers are declared but **optional**:
|
|
23
|
+
|
|
24
|
+
- `@googlemaps/js-api-loader` — only needed if you use `react-listing-engine/maps/google`.
|
|
25
|
+
- `@googlemaps/markerclusterer` — only needed for `DatasetDefinition.clustering` support on `googleProvider`; without it, clustered layers fall back to plain (unclustered) markers with a one-time console warning, no crash.
|
|
26
|
+
- `@radix-ui/react-slot` — reserved for future compound-component support; no shipped component currently imports it.
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
Minimal setup using the shipped rental preset — one dataset, the shipped filters, Google Maps, and the styled `/shadcn` layout:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import {
|
|
34
|
+
composeListingProviders,
|
|
35
|
+
ListingProvider,
|
|
36
|
+
withDataset,
|
|
37
|
+
withFilters,
|
|
38
|
+
withMap,
|
|
39
|
+
} from 'react-listing-engine';
|
|
40
|
+
import { ListingComponentsProviderWithDefaults, ListingLayout } from 'react-listing-engine/shadcn';
|
|
41
|
+
import { googleProvider } from 'react-listing-engine/maps/google';
|
|
42
|
+
import {
|
|
43
|
+
propertiesDataset,
|
|
44
|
+
withRentalFilters,
|
|
45
|
+
type PropertiesApiPort,
|
|
46
|
+
type PropertyEntity,
|
|
47
|
+
type RentalFilters,
|
|
48
|
+
} from 'react-listing-engine/presets/rental';
|
|
49
|
+
|
|
50
|
+
declare const propertiesApi: PropertiesApiPort; // your implementation, calling your real API
|
|
51
|
+
|
|
52
|
+
export function PropertySearch() {
|
|
53
|
+
return (
|
|
54
|
+
<ListingProvider<PropertyEntity, RentalFilters>
|
|
55
|
+
{...composeListingProviders<RentalFilters>(
|
|
56
|
+
withMap<RentalFilters>(googleProvider({ apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY! })),
|
|
57
|
+
withDataset(propertiesDataset(propertiesApi)),
|
|
58
|
+
withFilters(withRentalFilters()),
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
<ListingComponentsProviderWithDefaults>
|
|
62
|
+
<ListingLayout />
|
|
63
|
+
</ListingComponentsProviderWithDefaults>
|
|
64
|
+
</ListingProvider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`propertiesApi` only needs to implement `PropertiesApiPort` (`list`, `search`, optional `getById`) against your own backend — the preset never makes an HTTP call itself.
|
|
70
|
+
|
|
71
|
+
## Customization tiers
|
|
72
|
+
|
|
73
|
+
The engine is layered so you can go as deep as you need and stop:
|
|
74
|
+
|
|
75
|
+
1. **Data** — implement `EntityAdapter<TEntity, TFilters>` (or a preset's narrower port, e.g. `PropertiesApiPort`) against your own API. Nothing in the engine assumes a specific backend or entity shape.
|
|
76
|
+
2. **Structure** — compose the provider with `composeListingProviders(withMap(...), withDataset(...), withFilters(...), withUrlSync(...), withInitialFilters(...), withPrimaryDataset(...), withConfig(...))`. Mutate filters via `FilterRegistry` (`add`/`remove`/`reorder`/`replace`) and layers via `DatasetRegistry` (`add`/`get`/`has`/`list`/`visibleIds`).
|
|
77
|
+
3. **Presentation** — swap any slot via `ListingComponentsProvider` (or start from `ListingComponentsProviderWithDefaults` for the `/shadcn` look and override only what you need). Injectable slots: `Card`, `Marker`, `Popup`, `Sidebar`, `FilterPanel`, `Search`, `Empty`, `Loading`, `ResultHeader`, `Toolbar`. `Marker`/`Popup` are defined but not yet wired into the map's render output (see the Features note above) — every other slot renders as described.
|
|
78
|
+
4. **Layout** — skip `ListingLayout` entirely and arrange the structure-only compound components yourself: `ListingList`, `ListingMap`, `ListingFilters`, `ListingResultHeader`, `ListingToolbar`, `ListingPagination`.
|
|
79
|
+
|
|
80
|
+
## Google Maps setup
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { googleProvider } from 'react-listing-engine/maps/google';
|
|
84
|
+
|
|
85
|
+
const map = googleProvider({
|
|
86
|
+
apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY!, // required — no hardcoded fallback
|
|
87
|
+
mapId: 'YOUR_MAP_ID', // required by Google for AdvancedMarkerElement
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- `apiKey` always comes from **your** env/config; `googleProvider` throws immediately if it's falsy.
|
|
92
|
+
- `mapId` is required by Google to render `AdvancedMarkerElement` markers.
|
|
93
|
+
- `loaderOptions` (optional) forwards extra `@googlemaps/js-api-loader` config (language, region, preloaded libraries); `key` is always taken from `apiKey` and can't be overridden there.
|
|
94
|
+
- `DatasetDefinition.clustering` (`{ maxZoom }` or `false`) is implemented by the shipped `googleProvider`: when set, that layer's markers are wrapped in a `MarkerClusterer` (from the OPTIONAL peer dependency `@googlemaps/markerclusterer` — install it to enable clustering; without it, `googleProvider` warns once and falls back to plain, unclustered markers, no crash) with a custom renderer that draws a solid red circle showing the cluster's count. No Mapbox provider ships either; `MapProvider` is the seam if you want to add one.
|
|
95
|
+
|
|
96
|
+
## Nearby businesses / multiple layers
|
|
97
|
+
|
|
98
|
+
A second marker layer composes onto the same map with one more `withDataset` call:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { nearbyBusinessesDataset } from 'react-listing-engine/presets/rental';
|
|
102
|
+
|
|
103
|
+
withDataset(
|
|
104
|
+
nearbyBusinessesDataset(businessesApi, {
|
|
105
|
+
icons: { grocery: '/icons/grocery.png', schools: '/icons/schools.png' },
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`BusinessCategory` is an open `string` type, not a closed union — new categories are just new entries in your `icons`/`categories` data, no library change required. `KNOWN_BUSINESS_CATEGORIES` ships as a seeded starter list for building category pickers. Additional datasets are map-only layers; only the primary dataset (the first one added, or whichever id you pass as `primaryDatasetId` via `withPrimaryDataset`) drives the results list and pagination — implementing `list` on a secondary dataset's adapter has no effect on the list/pagination unless that dataset is made primary.
|
|
111
|
+
|
|
112
|
+
**Filter shape caveat.** `ListingEngine.loadPoints` calls every visible layer's `getPoints` with the *primary* dataset's `TFilters` — there's a single filter state per engine, not one per layer. A secondary dataset whose filters have a genuinely different shape (e.g. `nearbyBusinessesDataset`'s `BusinessFilters` vs. the primary `RentalFilters`) is **not** driven by the engine's filter state at all; the engine's filter object simply isn't in `BusinessFilters`' shape by the time it reaches that layer's adapter. Filter such a layer through the dataset factory's own `opts` instead — e.g. `nearbyBusinessesDataset(api, { categories })` — which closes over the restriction at construction time rather than reading it from `useListingFilters()`.
|
|
113
|
+
|
|
114
|
+
## Styled adapter
|
|
115
|
+
|
|
116
|
+
`react-listing-engine/shadcn` ships a full default UI (`ListingLayout` plus every `Default*` slot component) built on Tailwind utility classes matching shadcn's token conventions:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import { ListingComponentsProviderWithDefaults, ListingLayout } from 'react-listing-engine/shadcn';
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
It needs Tailwind to see its class names and the shadcn-style CSS variables to be defined. Either:
|
|
123
|
+
|
|
124
|
+
- point Tailwind's `@source` at the package's compiled output so its classes aren't purged:
|
|
125
|
+
```css
|
|
126
|
+
@import "tailwindcss";
|
|
127
|
+
@source "../node_modules/react-listing-engine/dist/**/*.{js,cjs}";
|
|
128
|
+
```
|
|
129
|
+
- or define the semantic tokens it reads yourself (`--color-background`, `--color-foreground`, `--color-card`, `--color-popover`, `--color-border`, `--color-primary`, `--color-secondary`, `--color-muted`, `--color-accent`, `--color-destructive`, plus `-foreground` variants), the same set any shadcn-based project already ships. [`examples/basic/src/index.css`](./examples/basic/src/index.css) is the source of truth for the full list.
|
|
130
|
+
|
|
131
|
+
## Hooks
|
|
132
|
+
|
|
133
|
+
- `useListing()` — the active `ListingEngine` from context; throws outside a `<ListingProvider>`.
|
|
134
|
+
- `useListingState()` — the full store snapshot, subscribed via `useSyncExternalStore`.
|
|
135
|
+
- `useListingResults()` — just the paginated results slice.
|
|
136
|
+
- `useListingFilters()` — current filters plus `set(patch)` / `setField(key, value)`.
|
|
137
|
+
- `useListingMap()` — bounds + per-dataset points, plus `loadPoints(bounds)` / `selectPoint(datasetId, id)`.
|
|
138
|
+
- `useListingLayer(id)` — one dataset's visibility, points, and a `toggle()`.
|
|
139
|
+
- `useListingEvent(type | '*', handler)` — subscribe to engine events for the component's lifetime.
|
|
140
|
+
|
|
141
|
+
## Examples
|
|
142
|
+
|
|
143
|
+
[`examples/basic`](./examples/basic) is a Vite app with four scenarios, run locally (`pnpm install && pnpm dev` inside that folder):
|
|
144
|
+
|
|
145
|
+
- **Properties only** — one dataset, the shipped rental filters, and the styled `/shadcn` defaults.
|
|
146
|
+
- **Properties + businesses** — a second nearby-businesses marker layer composed alongside the properties dataset.
|
|
147
|
+
- **Custom components** — `ListingComponentsProvider` with an app-authored `Card` and `Empty` slot.
|
|
148
|
+
- **Custom filters** — `withFilters(reg => reg.add/remove/reorder)` mutating the shipped rental filter set.
|
|
149
|
+
|
|
150
|
+
Each scenario works without a Google Maps key (the map is simply not rendered; a notice explains why) — set `VITE_GOOGLE_MAPS_KEY` in `examples/basic/.env` to see the map layer too.
|
|
151
|
+
|
|
152
|
+
## Support
|
|
153
|
+
|
|
154
|
+
If `react-listing-engine` saves you time, consider sponsoring continued maintenance:
|
|
155
|
+
|
|
156
|
+
[](https://github.com/sponsors/knazark)
|
|
157
|
+
[](https://send.monobank.ua/jar/2jfphHthfY)
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT (c) knazark
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{A as re,B as ie,h as z,i as U,j as Z,k as q,l as G,n as J,p as Q,q as X,s as w,v as M,w as Y,x as j,y as ee,z as te}from"./chunk-PFPHWJAA.js";import{useEffect as fe,useRef as ye}from"react";function oe(e,r){let t=w(),i=ye(r);i.current=r,fe(()=>t.on(e,s=>i.current(s)),[t,e])}function h(e){return typeof e!="number"?e:new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(e)}import{Fragment as he,jsx as m,jsxs as ne}from"react/jsx-runtime";function ve(e){return e?"rle-card rle-card--selected":"rle-card"}function E({item:e,selected:r,onSelect:t}){let i=e??{},s=ve(r),a=ne(he,{children:[i.imageUrl?m("img",{src:i.imageUrl,alt:i.title??"",className:"rle-card-media"}):m("div",{className:"rle-card-media rle-card-media--placeholder","aria-hidden":"true"}),ne("div",{className:"rle-card-body",children:[i.title&&m("span",{className:"rle-card-title",children:i.title}),i.subtitle&&m("span",{className:"rle-card-address",children:i.subtitle}),i.badge&&m("div",{className:"rle-card-info",children:m("span",{className:"rle-card-info-item",children:i.badge})}),i.price!=null&&m("span",{className:"rle-card-price",children:h(i.price)})]})]});return t?m("button",{type:"button",onClick:t,"aria-pressed":r??!1,className:s,children:a}):m("article",{className:s,children:a})}import{jsx as P,jsxs as se}from"react/jsx-runtime";function T(e){return se("div",{role:"status",className:"rle-empty",children:[se("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:"rle-empty-icon","aria-hidden":"true",children:[P("circle",{cx:"11",cy:"11",r:"7"}),P("path",{d:"m21 21-4.3-4.3"})]}),P("p",{className:"rle-empty-title",children:"No results"}),P("p",{className:"rle-empty-hint",children:"Try adjusting your filters or search terms."})]})}import{jsx as be}from"react/jsx-runtime";function _({children:e}){return be("div",{className:"rle-filter-panel",children:e})}import{jsx as C,jsxs as Le}from"react/jsx-runtime";function R(e){return C("div",{className:"rle-loading",role:"status","aria-busy":"true","aria-label":"Loading results",children:Array.from({length:3}).map((r,t)=>Le("div",{className:"rle-loading-item",children:[C("div",{className:"rle-skeleton",style:{aspectRatio:"4 / 3",width:"100%"}}),C("div",{className:"rle-skeleton",style:{height:16,width:"65%"}}),C("div",{className:"rle-skeleton",style:{height:12,width:"35%"}})]},t))})}import{jsx as Ne}from"react/jsx-runtime";function B({point:e}){let r=e.entity??{};return Ne("span",{className:"rle-pin",children:r.price!=null?h(r.price):""})}import{jsx as b,jsxs as A}from"react/jsx-runtime";function V({entity:e,onClose:r}){let t=e??{};return A("div",{className:"rle-popup",role:"group","aria-label":"Location details",children:[b("button",{type:"button",onClick:r,"aria-label":"Close",className:"rle-popup-close",children:A("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",width:"16",height:"16","aria-hidden":"true",children:[b("path",{d:"M18 6 6 18"}),b("path",{d:"m6 6 12 12"})]})}),A("div",{children:[t.title&&b("div",{className:"rle-card-title",children:t.title}),t.subtitle&&b("div",{className:"rle-card-address",children:t.subtitle}),t.price!=null&&b("div",{className:"rle-card-price",children:h(t.price)})]})]})}import{jsx as Se}from"react/jsx-runtime";function D({count:e,total:r}){let t=r!=null&&r!==e?`${e} of ${r} results`:`${e} results`;return Se("div",{className:"rle-result-header",children:t})}import{jsx as we}from"react/jsx-runtime";function O({value:e,onChange:r,placeholder:t}){return we("input",{type:"search",value:e,placeholder:t,onChange:i=>r(i.target.value),"aria-label":t??"Search",className:"rle-input"})}import{jsx as Pe}from"react/jsx-runtime";function K({children:e}){return Pe("aside",{className:"rle-sidebar",children:e})}import{jsx as Ce}from"react/jsx-runtime";function W({children:e}){return Ce("div",{className:"rle-toolbar",children:e})}var ae={Card:E,Marker:B,Popup:V,Sidebar:K,FilterPanel:_,Search:O,Empty:T,Loading:R,ResultHeader:D,Toolbar:W};import{jsx as o,jsxs as c}from"react/jsx-runtime";function le({view:e,onViewChange:r,onFiltersClick:t,action:i}){return c("nav",{className:"rle-bottom-nav","aria-label":"Listing navigation",children:[c("button",{type:"button",className:"rle-bottom-nav__btn",onClick:t,children:[o(xe,{}),o("span",{children:"Filters"})]}),i&&c("button",{type:"button",className:"rle-bottom-nav__btn",onClick:i.onClick,children:[i.icon??o(Fe,{}),o("span",{children:i.label})]}),c("div",{className:"rle-viewtoggle",role:"group","aria-label":"View",children:[c("button",{type:"button",className:`rle-viewtoggle__btn${e==="list"?" rle-viewtoggle__btn--active":""}`,"aria-pressed":e==="list",onClick:()=>r("list"),children:[o(ke,{}),"List"]}),c("button",{type:"button",className:`rle-viewtoggle__btn${e==="map"?" rle-viewtoggle__btn--active":""}`,"aria-pressed":e==="map",onClick:()=>r("map"),children:[o(Ie,{}),"Map"]})]})]})}function xe(){return c("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[o("line",{x1:"4",y1:"6",x2:"20",y2:"6"}),o("circle",{cx:"9",cy:"6",r:"2",fill:"currentColor",stroke:"none"}),o("line",{x1:"4",y1:"12",x2:"20",y2:"12"}),o("circle",{cx:"15",cy:"12",r:"2",fill:"currentColor",stroke:"none"}),o("line",{x1:"4",y1:"18",x2:"20",y2:"18"}),o("circle",{cx:"11",cy:"18",r:"2",fill:"currentColor",stroke:"none"})]})}function ke(){return c("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",width:"16",height:"16","aria-hidden":"true",children:[o("line",{x1:"8",y1:"6",x2:"20",y2:"6"}),o("line",{x1:"8",y1:"12",x2:"20",y2:"12"}),o("line",{x1:"8",y1:"18",x2:"20",y2:"18"}),o("line",{x1:"4",y1:"6",x2:"4.01",y2:"6"}),o("line",{x1:"4",y1:"12",x2:"4.01",y2:"12"}),o("line",{x1:"4",y1:"18",x2:"4.01",y2:"18"})]})}function Ie(){return c("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",width:"16",height:"16","aria-hidden":"true",children:[o("polygon",{points:"1 6 8 3 16 6 23 3 23 18 16 21 8 18 1 21 1 6"}),o("line",{x1:"8",y1:"3",x2:"8",y2:"18"}),o("line",{x1:"16",y1:"6",x2:"16",y2:"21"})]})}function Fe(){return c("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[o("line",{x1:"12",y1:"5",x2:"12",y2:"19"}),o("line",{x1:"5",y1:"12",x2:"19",y2:"12"})]})}import{useEffect as H,useRef as Me,useState as de}from"react";import{createPortal as Ee}from"react-dom";import{Fragment as _e,jsx as u,jsxs as x}from"react/jsx-runtime";function pe({open:e,onOpenChange:r,title:t,children:i,footer:s}){let a=Me(null),[n,p]=de(!1),[v,f]=de(!1);return H(()=>{if(!e){f(!1),p(!1);return}p(!0);let l=requestAnimationFrame(()=>f(!0));return()=>cancelAnimationFrame(l)},[e]),H(()=>{if(!n)return;let l=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.body.style.overflow=l}},[n]),H(()=>{if(!n)return;a.current?.focus();function l(L){L.key==="Escape"&&r(!1)}return document.addEventListener("keydown",l),()=>document.removeEventListener("keydown",l)},[n,r]),!n||typeof document>"u"?null:Ee(x(_e,{children:[u("div",{className:`rle-sheet-backdrop${v?" rle-sheet-backdrop--open":""}`,onClick:()=>r(!1),"aria-hidden":"true"}),x("div",{ref:a,className:`rle-sheet${v?" rle-sheet--open":""}`,role:"dialog","aria-modal":"true","aria-label":t,tabIndex:-1,children:[u("div",{className:"rle-sheet__handle","aria-hidden":"true"}),x("div",{className:"rle-sheet__header",children:[t&&u("div",{className:"rle-sheet__title",children:t}),u("button",{type:"button",className:"rle-sheet__close",onClick:()=>r(!1),"aria-label":"Close",children:u(Te,{})})]}),u("div",{className:"rle-sheet__body",children:i}),s&&u("div",{className:"rle-sheet__footer",children:s})]})]}),document.body)}function Te(){return x("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",width:"16",height:"16","aria-hidden":"true",children:[u("path",{d:"M18 6 6 18"}),u("path",{d:"m6 6 12 12"})]})}import{useEffect as Re,useState as ce}from"react";import{Fragment as Ae,jsx as d,jsxs as y}from"react/jsx-runtime";var Be=d("div",{className:"rle-empty",children:"Map unavailable"});function me({search:e,toolbarEnd:r,mobileAction:t,autoFetch:i=!0,mapCenter:s,mapZoom:a,className:n}){let p=w(),{Search:v}=X(),f=Y(),[l,L]=ce("list"),[k,N]=ce(!1);Re(()=>{i!==!1&&p.applyFilters({})},[p,i]);let S=()=>{let I=Object.fromEntries(p.filters.list().map(F=>[F.key,void 0]));p.applyFilters(I)},g=f.total??f.items.length;return y("div",{className:n?`rle-app ${n}`:"rle-app",children:[y("div",{className:"rle-filter-bar",children:[e&&d(v,{value:e.value,onChange:e.onChange,placeholder:e.placeholder}),d(M,{className:"rle-filters-row",groupClassName:"rle-filter-group"}),y("div",{className:"rle-filter-bar__end",children:[d(re,{}),r]})]}),y("div",{className:"rle-body rle-split","data-mobile-view":l,children:[y("div",{className:"rle-list",children:[d(j,{className:"rle-list-grid"}),d(te,{})]}),d("div",{className:"rle-map",children:d(ee,{center:s,zoom:a,fallback:Be})})]}),d(le,{view:l,onViewChange:L,onFiltersClick:()=>N(!0),action:t}),d(pe,{title:"Filters",open:k,onOpenChange:N,footer:y(Ae,{children:[d("button",{type:"button",className:"rle-btn rle-btn--ghost",onClick:S,children:"Clear all"}),y("button",{type:"button",className:"rle-btn rle-btn--primary",onClick:()=>N(!1),children:["Show ",g," results"]})]}),children:d(M,{className:"rle-filter-stack",groupClassName:"rle-filter-group"})})]})}import{useEffect as Ve,useRef as De,useState as Oe}from"react";import{jsx as $,jsxs as He}from"react/jsx-runtime";function ue(e){return"provider"in e}function Ke(e){let r=e!=null&&!ue(e),t=r?e.apiKey:void 0,i=r?e.mapId:void 0,[s,a]=Oe(()=>!e||ue(e)?{ready:!0,provider:e?.provider}:{ready:!1});return Ve(()=>{if(!t)return;let n=!1;return import("./maps/google/index.js").then(({googleProvider:p})=>{n||a({ready:!0,provider:p({apiKey:t,mapId:i})})}),()=>{n=!0}},[t,i]),s}function We({onFiltersChange:e}){let r=De(e);return r.current=e,oe("FiltersChanged",t=>{t.type==="FiltersChanged"&&r.current(t.filters)}),null}function Zt(e){let{datasets:r,filters:t,map:i,components:s,initialFilters:a,onFiltersChange:n,mobileAction:p,search:v,toolbarEnd:f,config:l,autoFetch:L,className:k}=e,{ready:N,provider:S}=Ke(i);if(!N)return null;let g=[];for(let ge of r)g.push(q(ge));t&&g.push(G(t)),S&&g.push(Z(S)),a&&g.push(J(a)),l&&g.push(U(l));let I=z(...g),F={...ae,...s};return He(ie,{...I,children:[n&&$(We,{onFiltersChange:n}),$(Q,{...F,children:$(me,{className:k,search:v,toolbarEnd:f,mobileAction:p,autoFetch:L,mapCenter:i?.center,mapZoom:i?.zoom})})]})}export{oe as a,E as b,T as c,_ as d,R as e,B as f,V as g,D as h,O as i,K as j,W as k,ae as l,le as m,pe as n,me as o,Zt as p};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); 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; } var _class;"use client";
|
|
2
|
+
function n(i,t){let r=new Set([...Object.keys(i),...Object.keys(t)]);for(let s of r)if(i[s]!==t[s])return!1;return!0}var e= (_class =class{__init() {this.isSyncing=!1}__init2() {this.unsubscribeEngine=null}__init3() {this.unsubscribeHistory=null}constructor(t){;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);this.history=t.history,this.toQueryFn=t.toQuery,this.toFiltersFn=t.toFilters,this.hydrateOnStart=_nullishCoalesce(t.hydrateOnStart, () => (!0))}start(t){this.stop(),this.unsubscribeEngine=t.subscribe(()=>{this.isSyncing||this.syncEngineToHistory(t)}),this.unsubscribeHistory=this.history.subscribe(()=>{this.isSyncing||this.withSyncGuard(()=>{this.applyFiltersSafely(t,this.toFiltersFn(this.history.getQuery()))})}),this.hydrateOnStart&&this.withSyncGuard(()=>{this.applyFiltersSafely(t,this.toFiltersFn(this.history.getQuery()))})}stop(){_optionalChain([this, 'access', _ => _.unsubscribeEngine, 'optionalCall', _2 => _2()]),this.unsubscribeEngine=null,_optionalChain([this, 'access', _3 => _3.unsubscribeHistory, 'optionalCall', _4 => _4()]),this.unsubscribeHistory=null}syncEngineToHistory(t){let r=this.toQueryFn(t.state.filters);n(r,this.history.getQuery())||this.withSyncGuard(()=>this.history.setQuery(r))}applyFiltersSafely(t,r){Promise.resolve(t.applyFilters(r)).catch(()=>{})}withSyncGuard(t){this.isSyncing=!0;try{t()}finally{this.isSyncing=!1}}}, _class);exports.a = e;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
function n(i,t){let r=new Set([...Object.keys(i),...Object.keys(t)]);for(let s of r)if(i[s]!==t[s])return!1;return!0}var e=class{history;toQueryFn;toFiltersFn;hydrateOnStart;isSyncing=!1;unsubscribeEngine=null;unsubscribeHistory=null;constructor(t){this.history=t.history,this.toQueryFn=t.toQuery,this.toFiltersFn=t.toFilters,this.hydrateOnStart=t.hydrateOnStart??!0}start(t){this.stop(),this.unsubscribeEngine=t.subscribe(()=>{this.isSyncing||this.syncEngineToHistory(t)}),this.unsubscribeHistory=this.history.subscribe(()=>{this.isSyncing||this.withSyncGuard(()=>{this.applyFiltersSafely(t,this.toFiltersFn(this.history.getQuery()))})}),this.hydrateOnStart&&this.withSyncGuard(()=>{this.applyFiltersSafely(t,this.toFiltersFn(this.history.getQuery()))})}stop(){this.unsubscribeEngine?.(),this.unsubscribeEngine=null,this.unsubscribeHistory?.(),this.unsubscribeHistory=null}syncEngineToHistory(t){let r=this.toQueryFn(t.state.filters);n(r,this.history.getQuery())||this.withSyncGuard(()=>this.history.setQuery(r))}applyFiltersSafely(t,r){Promise.resolve(t.applyFilters(r)).catch(()=>{})}withSyncGuard(t){this.isSyncing=!0;try{t()}finally{this.isSyncing=!1}}};export{e as a};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var i=class{mode;constructor(e={}){this.mode=e.mode??"replace"}getQuery(){if(typeof window>"u")return{};let e=new URLSearchParams(window.location.search),t={};for(let[r,o]of e)o!==""&&(t[r]=o);return t}setQuery(e){if(typeof window>"u")return;let t=new URLSearchParams;for(let[a,n]of Object.entries(e))n===void 0||n===""||t.set(a,n);let r=t.toString(),o=r?`?${r}`:"";if(o===window.location.search)return;let s=`${window.location.pathname}${o}${window.location.hash}`;this.mode==="push"?window.history.pushState(window.history.state,"",s):window.history.replaceState(window.history.state,"",s)}subscribe(e){return typeof window>"u"?()=>{}:(window.addEventListener("popstate",e),()=>{window.removeEventListener("popstate",e)})}};export{i as a};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); 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; } var _class; var _class2; var _class3; var _class4; var _class5;"use client";
|
|
2
|
+
var S= (_class =class{__init() {this.listeners=new Set}constructor(t){;_class.prototype.__init.call(this);this.state=this.freezeState({filters:{...t.filters},results:{items:[],nextCursor:null},bounds:null,selection:null,pagination:{mode:_nullishCoalesce(t.mode, () => ("paged")),loading:!1},layers:{},points:{}})}getState(){return this.state}setFilters(t){this.setState({filters:{...this.state.filters,...t}})}setResults(t){this.setState({results:{items:[...t.items],nextCursor:t.nextCursor,total:t.total}})}appendResults(t){this.setState({results:{items:[...this.state.results.items,...t.items],nextCursor:t.nextCursor,total:t.total}})}setBounds(t){this.setState({bounds:t?{...t}:null})}setSelection(t){this.setState({selection:t})}setLayerVisible(t,e){this.setState({layers:{...this.state.layers,[t]:e}})}setLoading(t){this.setState({pagination:{...this.state.pagination,loading:t}})}setPoints(t,e){this.setState({points:{...this.state.points,[t]:[...e]}})}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}setState(t){this.state=this.freezeState({...this.state,...t}),this.notify()}notify(){for(let t of this.listeners)t()}freezeState(t){Object.freeze(t.filters),Object.freeze(t.results.items),Object.freeze(t.results),t.bounds&&Object.freeze(t.bounds),Object.freeze(t.pagination),Object.freeze(t.layers);for(let e of Object.values(t.points))Object.freeze(e);return Object.freeze(t.points),Object.freeze(t)}}, _class);var P= (_class2 =class{constructor() { _class2.prototype.__init2.call(this); }__init2() {this.defs=new Map}add(t){if(this.defs.has(t.key))throw new Error(`FilterRegistry: filter "${t.key}" is already registered`);return this.defs.set(t.key,{...t}),this}remove(t){return this.defs.delete(t),this}replace(t,e){if(!this.defs.has(t))throw new Error(`FilterRegistry: cannot replace unregistered filter "${t}"`);return this.defs.set(t,{...e,key:t}),this}reorder(t){let e=this.list(),n=t.filter(r=>this.defs.has(r));n.forEach((r,o)=>{this.defs.get(r).order=o});let s=new Set(n);return e.filter(r=>!s.has(r.key)).forEach((r,o)=>{r.order=n.length+o}),this}list(){return[...this.defs.values()].sort((t,e)=>t.order-e.order)}has(t){return this.defs.has(t)}toFilters(t){return this.list().filter(n=>Object.hasOwn(t,n.key)).map(n=>n.toParams(t[n.key])).reduce((n,s)=>({...n,...s}),{})}activeKeys(t){return this.list().filter(e=>_optionalChain([e, 'access', _2 => _2.isActive, 'optionalCall', _3 => _3(t)])).map(e=>e.key)}}, _class2);var w= (_class3 =class{constructor() { _class3.prototype.__init3.call(this); }__init3() {this.defs=new Map}add(t){if(this.defs.has(t.id))throw new Error(`DatasetRegistry: dataset "${t.id}" is already registered`);return this.defs.set(t.id,{...t}),this}get(t){return this.defs.get(t)}has(t){return this.defs.has(t)}list(){return[...this.defs.values()]}visibleIds(){return this.list().filter(t=>_optionalChain([t, 'access', _4 => _4.visible, 'optionalCall', _5 => _5()])!==!1).map(t=>t.id)}}, _class3);var R= (_class4 =class{constructor() { _class4.prototype.__init4.call(this); }__init4() {this.map=new Map}dispose(){this.map.clear()}emit(t){let e=this.map.get(t.type);if(e)for(let s of[...e])s(t);let n=this.map.get("*");if(n)for(let s of[...n])s(t)}on(t,e){let n=this.map.get(t);return n||(n=new Set,this.map.set(t,n)),n.add(e),()=>{n.delete(e)}}}, _class4);var B={pagination:"paged",pageSize:20,debounceMs:250};var x=class{constructor(t){this.options=Object.freeze({...B,...t})}};var D= (_class5 =class{__init5() {this.emitter=new R}__init6() {this.debounceTimer=null}__init7() {this.debounceResolve=null}__init8() {this.queryToken=0}__init9() {this.pointsToken=0}constructor(t){;_class5.prototype.__init5.call(this);_class5.prototype.__init6.call(this);_class5.prototype.__init7.call(this);_class5.prototype.__init8.call(this);_class5.prototype.__init9.call(this);this.datasets=t.datasets,this.filters=_nullishCoalesce(t.filters, () => (new P)),this.map=t.map,this.config=new x(t.config);let e=_nullishCoalesce(t.primaryDatasetId, () => (_optionalChain([this, 'access', _6 => _6.datasets, 'access', _7 => _7.list, 'call', _8 => _8(), 'access', _9 => _9[0], 'optionalAccess', _10 => _10.id])));if(e==null||!this.datasets.has(e))throw new Error(`ListingEngine: primary dataset "${_nullishCoalesce(e, () => (""))}" is not registered \u2014 pass a valid primaryDatasetId or register at least one dataset`);this.primaryDatasetId=e,this.store=new S({filters:_nullishCoalesce(t.initialFilters, () => ({})),mode:this.config.options.pagination})}get state(){return this.store.getState()}get options(){return this.config.options}applyFilters(t){this.store.setFilters(t),this.emitter.emit({type:"FiltersChanged",filters:this.currentFilters()}),this.clearDebounce();let e=++this.queryToken,n=this.config.options.debounceMs;return n<=0?this.runQuery(e):new Promise(s=>{this.debounceResolve=s,this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.debounceResolve=null,s(this.runQuery(e))},n)})}async loadPage(){let t=this.state.results.nextCursor;if(t===null)return;let e=++this.queryToken,n=this.primaryDataset();this.store.setLoading(!0);try{let s=await n.adapter.list(this.currentFilters(),{cursor:t,limit:this.config.options.pageSize});if(e!==this.queryToken)return;this.config.options.pagination==="infinite"?this.store.appendResults(s):this.store.setResults(s),this.emitter.emit({type:"ResultsLoaded",datasetId:this.primaryDatasetId,count:s.items.length})}finally{e===this.queryToken&&this.store.setLoading(!1)}}async loadPoints(t){this.store.setBounds(t),this.emitter.emit({type:"BoundsChanged",bounds:t});let e=this.currentFilters(),n=++this.pointsToken;await Promise.allSettled(this.datasets.visibleIds().map(async s=>{let r=this.datasets.get(s);if(!r)return;let o=await r.adapter.getPoints(e,t);n===this.pointsToken&&this.store.setPoints(s,o)}))}selectPoint(t,e){this.store.setSelection(e);let n=_optionalChain([this, 'access', _11 => _11.state, 'access', _12 => _12.points, 'access', _13 => _13[t], 'optionalAccess', _14 => _14.find, 'call', _15 => _15(s=>s.id===e)]);n&&this.emitter.emit({type:"PointClicked",datasetId:t,id:e,entity:n.entity})}toggleLayer(t){let n=!(_nullishCoalesce(this.state.layers[t], () => (!0)));this.store.setLayerVisible(t,n),this.emitter.emit({type:"LayerToggled",datasetId:t,visible:n})}subscribe(t){return this.store.subscribe(t)}on(t,e){return this.emitter.on(t,e)}dispose(){this.clearDebounce(),this.emitter.dispose()}async runQuery(t){if(t!==this.queryToken)return;let e=this.primaryDataset();this.store.setLoading(!0);try{let n=await e.adapter.list(this.currentFilters(),{cursor:null,limit:this.config.options.pageSize});if(t!==this.queryToken)return;this.store.setResults(n),this.emitter.emit({type:"ResultsLoaded",datasetId:this.primaryDatasetId,count:n.items.length})}finally{t===this.queryToken&&this.store.setLoading(!1)}}primaryDataset(){return this.datasets.get(this.primaryDatasetId)}currentFilters(){return this.store.getState().filters}clearDebounce(){this.debounceTimer!==null&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.debounceResolve!==null&&(this.debounceResolve(),this.debounceResolve=null)}}, _class5);function Mt(...i){let t={config:{},filters:new P,datasets:new w};for(let e of i)e(t);return t}var Ot=i=>t=>{t.config={...t.config,...i}},Nt= exports.j =i=>t=>{t.map=i},zt= exports.k =i=>t=>{t.datasets.add(i)},Bt= exports.l =i=>t=>{i(t.filters)},Ut= exports.m =i=>t=>{t.urlSync=i},Ht= exports.n =i=>t=>{t.initialFilters=i},jt= exports.o =i=>t=>{t.primaryDatasetId=i};var _react = require('react');var _jsxruntime = require('react/jsx-runtime');function G(i){return String(_nullishCoalesce(_optionalChain([i, 'optionalAccess', _16 => _16.title]), () => ("")))}var W=({item:i})=>_jsxruntime.jsx.call(void 0, "div",{children:G(i)}),X=()=>_jsxruntime.jsx.call(void 0, "div",{}),J=()=>_jsxruntime.jsx.call(void 0, "div",{}),Y=({children:i})=>_jsxruntime.jsx.call(void 0, "div",{children:i}),Z=({children:i})=>_jsxruntime.jsx.call(void 0, "div",{children:i}),tt=({value:i,onChange:t,placeholder:e})=>_jsxruntime.jsx.call(void 0, "input",{type:"search",value:i,placeholder:e,onChange:n=>t(n.target.value),"aria-label":_nullishCoalesce(e, () => ("Search"))}),et=()=>_jsxruntime.jsx.call(void 0, "div",{role:"status",children:"No results"}),it=()=>_jsxruntime.jsx.call(void 0, "div",{role:"status","aria-busy":"true",children:"Loading\u2026"}),nt=({count:i})=>_jsxruntime.jsxs.call(void 0, "div",{children:[i," results"]}),st=({children:i})=>_jsxruntime.jsx.call(void 0, "div",{children:i}),c={Card:W,Marker:X,Popup:J,Sidebar:Y,FilterPanel:Z,Search:tt,Empty:et,Loading:it,ResultHeader:nt,Toolbar:st},U=_react.createContext.call(void 0, c);function Vt(i){let{Card:t,Marker:e,Popup:n,Sidebar:s,FilterPanel:r,Search:o,Empty:a,Loading:u,ResultHeader:L,Toolbar:y,children:k}=i,b={Card:_nullishCoalesce(t, () => (c.Card)),Marker:_nullishCoalesce(e, () => (c.Marker)),Popup:_nullishCoalesce(n, () => (c.Popup)),Sidebar:_nullishCoalesce(s, () => (c.Sidebar)),FilterPanel:_nullishCoalesce(r, () => (c.FilterPanel)),Search:_nullishCoalesce(o, () => (c.Search)),Empty:_nullishCoalesce(a, () => (c.Empty)),Loading:_nullishCoalesce(u, () => (c.Loading)),ResultHeader:_nullishCoalesce(L, () => (c.ResultHeader)),Toolbar:_nullishCoalesce(y, () => (c.Toolbar))};return _jsxruntime.jsx.call(void 0, U.Provider,{value:b,children:k})}function v(){return _react.useContext.call(void 0, U)}var M=_react.createContext.call(void 0, null);function d(){let i=_react.useContext.call(void 0, M);if(i===null)throw new Error("useListing must be used within a <ListingProvider>");return i}function m(){let i=d(),t=_react.useCallback.call(void 0, n=>i.subscribe(n),[i]),e=_react.useCallback.call(void 0, ()=>i.state,[i]);return _react.useSyncExternalStore.call(void 0, t,e,e)}function q(){let i=d(),t=m(),e=_react.useCallback.call(void 0, s=>i.applyFilters(s),[i]),n=_react.useCallback.call(void 0, (s,r)=>i.applyFilters({[s]:r}),[i]);return{filters:t.filters,set:e,setField:n}}function ae({className:i,groupClassName:t}={}){let e=d(),{FilterPanel:n}=v(),{filters:s}=q();return _jsxruntime.jsx.call(void 0, n,{children:_jsxruntime.jsx.call(void 0, "div",{className:_nullishCoalesce(i, () => ("space-y-5")),children:e.filters.list().map(r=>{if(typeof r.render=="string")return _jsxruntime.jsx.call(void 0, "div",{"data-filter":r.key,className:t},r.key);let o=r.render;return _jsxruntime.jsxs.call(void 0, "div",{className:t,children:[r.label&&_jsxruntime.jsx.call(void 0, "div",{className:"mb-1.5 text-[13px] font-medium text-foreground",children:r.label}),_jsxruntime.jsx.call(void 0, o,{value:r.fromParams(s),onChange:a=>{e.applyFilters(r.toParams(a))}})]},r.key)})})})}function O(){return m().results}function ut(i,t){if(i&&typeof i=="object"&&"id"in i){let e=i.id;if(typeof e=="string"||typeof e=="number")return e}return t}function ye({className:i}={}){let t=d(),{items:e}=O(),{pagination:n,selection:s}=m(),{Card:r,Empty:o,Loading:a}=v();return n.loading&&e.length===0?_jsxruntime.jsx.call(void 0, a,{}):e.length===0?_jsxruntime.jsx.call(void 0, o,{}):_jsxruntime.jsx.call(void 0, "div",{role:"list",className:i,children:e.map((u,L)=>{let y=ut(u,L);return _jsxruntime.jsx.call(void 0, r,{item:u,selected:s===y,onSelect:()=>t.selectPoint(t.primaryDatasetId,y)},y)})})}var ct={west:-179.9,south:-85,east:179.9,north:85},A=.1,V=.02;function gt(i){if(i.length===0)return null;let t=i[0].lng,e=i[0].lng,n=i[0].lat,s=i[0].lat;for(let{lat:a,lng:u}of i)u<t&&(t=u),u>e&&(e=u),a<n&&(n=a),a>s&&(s=a);let r=s>n?(s-n)*A:V,o=e>t?(e-t)*A:V;return{west:t-o,east:e+o,south:n-r,north:s+r}}function ve(i){let{center:t,zoom:e,fallback:n}=i,s=d(),r=m(),o=_react.useRef.call(void 0, null),a=_react.useRef.call(void 0, null),[u,L]=_react.useState.call(void 0, !1),y=_react.useRef.call(void 0, !1),k=_react.useRef.call(void 0, !1),b=_react.useRef.call(void 0, !1),l=s.map;return _react.useEffect.call(void 0, ()=>{let T=a.current;if(!l||!T)return;let h=[];for(let p of Object.keys(r.points)){if(r.layers[p]===!1)continue;let g=s.datasets.get(p),E=_nullishCoalesce(r.points[p], () => ([])),_={id:p,markers:E.map(F=>({id:F.id,position:F.position,iconUrl:_optionalChain([g, 'optionalAccess', _17 => _17.marker, 'access', _18 => _18.iconUrl, 'optionalCall', _19 => _19(F.entity)]),element:_optionalChain([g, 'optionalAccess', _20 => _20.marker, 'access', _21 => _21.element, 'optionalCall', _22 => _22(F.entity)])})),clustering:_optionalChain([g, 'optionalAccess', _23 => _23.clustering]),onMarkerClick:F=>s.selectPoint(p,F)};h.push(l.renderLayer(T,_))}return()=>{h.forEach(p=>p())}},[s,l,u,r.points,r.layers]),_react.useEffect.call(void 0, ()=>{if(!o.current||!l)return;let T=o.current,h=!1,p=null;return(async()=>{let g=await l.mount(T,{center:t,zoom:e});if(h){l.destroy(g);return}a.current=g,p=l.onBoundsChange(g,E=>{b.current?b.current=!1:k.current=!0,s.loadPoints(E)}),L(!0),s.loadPoints(ct)})(),()=>{h=!0,_optionalChain([p, 'optionalCall', _24 => _24()]),a.current&&(l.destroy(a.current),a.current=null),L(!1)}},[s,l]),_react.useEffect.call(void 0, ()=>{let T=a.current;if(!l||!T||t||y.current||k.current)return;let h=[];for(let g of Object.keys(r.points))if(r.layers[g]!==!1)for(let E of _nullishCoalesce(r.points[g], () => ([])))h.push(E.position);let p=gt(h);p&&(y.current=!0,b.current=!0,l.fitBounds(T,p))},[l,t,u,r.points,r.layers]),_jsxruntime.jsx.call(void 0, "div",{ref:o,className:!l&&n?"flex h-full min-h-0 w-full items-center justify-center":"h-full min-h-0 w-full",children:!l&&n})}function ke(){let i=d(),{results:t,pagination:e}=m(),n=t.nextCursor==null||e.loading;return _jsxruntime.jsx.call(void 0, "button",{type:"button",disabled:n,onClick:()=>{i.loadPage()},children:"Load more"})}function De(){let{items:i,total:t}=O(),{ResultHeader:e}=v();return _jsxruntime.jsx.call(void 0, e,{count:i.length,total:t})}function Ue(i){let{children:t,...e}=i,[n]=_react.useState.call(void 0, ()=>e),[s,r]=_react.useState.call(void 0, null);return _react.useEffect.call(void 0, ()=>{let o=new D({datasets:n.datasets,filters:n.filters,config:n.config,map:n.map,initialFilters:n.initialFilters,primaryDatasetId:n.primaryDatasetId});return n.urlSync&&n.urlSync.start(o),r(o),()=>{n.urlSync&&n.urlSync.stop(),o.dispose(),r(a=>a===o?null:a)}},[n]),s?_jsxruntime.jsx.call(void 0, M.Provider,{value:s,children:t}):null}exports.a = S; exports.b = P; exports.c = w; exports.d = R; exports.e = B; exports.f = x; exports.g = D; exports.h = Mt; exports.i = Ot; exports.j = Nt; exports.k = zt; exports.l = Bt; exports.m = Ut; exports.n = Ht; exports.o = jt; exports.p = Vt; exports.q = v; exports.r = M; exports.s = d; exports.t = m; exports.u = q; exports.v = ae; exports.w = O; exports.x = ye; exports.y = ve; exports.z = ke; exports.A = De; exports.B = Ue;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }"use client";
|
|
2
|
+
var i=class{constructor(e={}){this.mode=_nullishCoalesce(e.mode, () => ("replace"))}getQuery(){if(typeof window>"u")return{};let e=new URLSearchParams(window.location.search),t={};for(let[r,o]of e)o!==""&&(t[r]=o);return t}setQuery(e){if(typeof window>"u")return;let t=new URLSearchParams;for(let[a,n]of Object.entries(e))n===void 0||n===""||t.set(a,n);let r=t.toString(),o=r?`?${r}`:"";if(o===window.location.search)return;let s=`${window.location.pathname}${o}${window.location.hash}`;this.mode==="push"?window.history.pushState(window.history.state,"",s):window.history.replaceState(window.history.state,"",s)}subscribe(e){return typeof window>"u"?()=>{}:(window.addEventListener("popstate",e),()=>{window.removeEventListener("popstate",e)})}};exports.a = i;
|
|
@@ -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 _react = require('react');function oe(e,r){let t=_chunk5O36WI7Hcjs.s.call(void 0, ),i=_react.useRef.call(void 0, r);i.current=r,_react.useEffect.call(void 0, ()=>t.on(e,s=>i.current(s)),[t,e])}function h(e){return typeof e!="number"?e:new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(e)}var _jsxruntime = require('react/jsx-runtime');function ve(e){return e?"rle-card rle-card--selected":"rle-card"}function E({item:e,selected:r,onSelect:t}){let i=_nullishCoalesce(e, () => ({})),s=ve(r),a=_jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[i.imageUrl?_jsxruntime.jsx.call(void 0, "img",{src:i.imageUrl,alt:_nullishCoalesce(i.title, () => ("")),className:"rle-card-media"}):_jsxruntime.jsx.call(void 0, "div",{className:"rle-card-media rle-card-media--placeholder","aria-hidden":"true"}),_jsxruntime.jsxs.call(void 0, "div",{className:"rle-card-body",children:[i.title&&_jsxruntime.jsx.call(void 0, "span",{className:"rle-card-title",children:i.title}),i.subtitle&&_jsxruntime.jsx.call(void 0, "span",{className:"rle-card-address",children:i.subtitle}),i.badge&&_jsxruntime.jsx.call(void 0, "div",{className:"rle-card-info",children:_jsxruntime.jsx.call(void 0, "span",{className:"rle-card-info-item",children:i.badge})}),i.price!=null&&_jsxruntime.jsx.call(void 0, "span",{className:"rle-card-price",children:h(i.price)})]})]});return t?_jsxruntime.jsx.call(void 0, "button",{type:"button",onClick:t,"aria-pressed":_nullishCoalesce(r, () => (!1)),className:s,children:a}):_jsxruntime.jsx.call(void 0, "article",{className:s,children:a})}function T(e){return _jsxruntime.jsxs.call(void 0, "div",{role:"status",className:"rle-empty",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:"rle-empty-icon","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:"rle-empty-title",children:"No results"}),_jsxruntime.jsx.call(void 0, "p",{className:"rle-empty-hint",children:"Try adjusting your filters or search terms."})]})}function _({children:e}){return _jsxruntime.jsx.call(void 0, "div",{className:"rle-filter-panel",children:e})}function R(e){return _jsxruntime.jsx.call(void 0, "div",{className:"rle-loading",role:"status","aria-busy":"true","aria-label":"Loading results",children:Array.from({length:3}).map((r,t)=>_jsxruntime.jsxs.call(void 0, "div",{className:"rle-loading-item",children:[_jsxruntime.jsx.call(void 0, "div",{className:"rle-skeleton",style:{aspectRatio:"4 / 3",width:"100%"}}),_jsxruntime.jsx.call(void 0, "div",{className:"rle-skeleton",style:{height:16,width:"65%"}}),_jsxruntime.jsx.call(void 0, "div",{className:"rle-skeleton",style:{height:12,width:"35%"}})]},t))})}function B({point:e}){let r=_nullishCoalesce(e.entity, () => ({}));return _jsxruntime.jsx.call(void 0, "span",{className:"rle-pin",children:r.price!=null?h(r.price):""})}function V({entity:e,onClose:r}){let t=_nullishCoalesce(e, () => ({}));return _jsxruntime.jsxs.call(void 0, "div",{className:"rle-popup",role:"group","aria-label":"Location details",children:[_jsxruntime.jsx.call(void 0, "button",{type:"button",onClick:r,"aria-label":"Close",className:"rle-popup-close",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",width:"16",height:"16","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",{children:[t.title&&_jsxruntime.jsx.call(void 0, "div",{className:"rle-card-title",children:t.title}),t.subtitle&&_jsxruntime.jsx.call(void 0, "div",{className:"rle-card-address",children:t.subtitle}),t.price!=null&&_jsxruntime.jsx.call(void 0, "div",{className:"rle-card-price",children:h(t.price)})]})]})}function D({count:e,total:r}){let t=r!=null&&r!==e?`${e} of ${r} results`:`${e} results`;return _jsxruntime.jsx.call(void 0, "div",{className:"rle-result-header",children:t})}function O({value:e,onChange:r,placeholder:t}){return _jsxruntime.jsx.call(void 0, "input",{type:"search",value:e,placeholder:t,onChange:i=>r(i.target.value),"aria-label":_nullishCoalesce(t, () => ("Search")),className:"rle-input"})}function K({children:e}){return _jsxruntime.jsx.call(void 0, "aside",{className:"rle-sidebar",children:e})}function W({children:e}){return _jsxruntime.jsx.call(void 0, "div",{className:"rle-toolbar",children:e})}var ae={Card:E,Marker:B,Popup:V,Sidebar:K,FilterPanel:_,Search:O,Empty:T,Loading:R,ResultHeader:D,Toolbar:W};function le({view:e,onViewChange:r,onFiltersClick:t,action:i}){return _jsxruntime.jsxs.call(void 0, "nav",{className:"rle-bottom-nav","aria-label":"Listing navigation",children:[_jsxruntime.jsxs.call(void 0, "button",{type:"button",className:"rle-bottom-nav__btn",onClick:t,children:[_jsxruntime.jsx.call(void 0, xe,{}),_jsxruntime.jsx.call(void 0, "span",{children:"Filters"})]}),i&&_jsxruntime.jsxs.call(void 0, "button",{type:"button",className:"rle-bottom-nav__btn",onClick:i.onClick,children:[_nullishCoalesce(i.icon, () => (_jsxruntime.jsx.call(void 0, Fe,{}))),_jsxruntime.jsx.call(void 0, "span",{children:i.label})]}),_jsxruntime.jsxs.call(void 0, "div",{className:"rle-viewtoggle",role:"group","aria-label":"View",children:[_jsxruntime.jsxs.call(void 0, "button",{type:"button",className:`rle-viewtoggle__btn${e==="list"?" rle-viewtoggle__btn--active":""}`,"aria-pressed":e==="list",onClick:()=>r("list"),children:[_jsxruntime.jsx.call(void 0, ke,{}),"List"]}),_jsxruntime.jsxs.call(void 0, "button",{type:"button",className:`rle-viewtoggle__btn${e==="map"?" rle-viewtoggle__btn--active":""}`,"aria-pressed":e==="map",onClick:()=>r("map"),children:[_jsxruntime.jsx.call(void 0, Ie,{}),"Map"]})]})]})}function xe(){return _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","aria-hidden":"true",children:[_jsxruntime.jsx.call(void 0, "line",{x1:"4",y1:"6",x2:"20",y2:"6"}),_jsxruntime.jsx.call(void 0, "circle",{cx:"9",cy:"6",r:"2",fill:"currentColor",stroke:"none"}),_jsxruntime.jsx.call(void 0, "line",{x1:"4",y1:"12",x2:"20",y2:"12"}),_jsxruntime.jsx.call(void 0, "circle",{cx:"15",cy:"12",r:"2",fill:"currentColor",stroke:"none"}),_jsxruntime.jsx.call(void 0, "line",{x1:"4",y1:"18",x2:"20",y2:"18"}),_jsxruntime.jsx.call(void 0, "circle",{cx:"11",cy:"18",r:"2",fill:"currentColor",stroke:"none"})]})}function ke(){return _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",width:"16",height:"16","aria-hidden":"true",children:[_jsxruntime.jsx.call(void 0, "line",{x1:"8",y1:"6",x2:"20",y2:"6"}),_jsxruntime.jsx.call(void 0, "line",{x1:"8",y1:"12",x2:"20",y2:"12"}),_jsxruntime.jsx.call(void 0, "line",{x1:"8",y1:"18",x2:"20",y2:"18"}),_jsxruntime.jsx.call(void 0, "line",{x1:"4",y1:"6",x2:"4.01",y2:"6"}),_jsxruntime.jsx.call(void 0, "line",{x1:"4",y1:"12",x2:"4.01",y2:"12"}),_jsxruntime.jsx.call(void 0, "line",{x1:"4",y1:"18",x2:"4.01",y2:"18"})]})}function Ie(){return _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",width:"16",height:"16","aria-hidden":"true",children:[_jsxruntime.jsx.call(void 0, "polygon",{points:"1 6 8 3 16 6 23 3 23 18 16 21 8 18 1 21 1 6"}),_jsxruntime.jsx.call(void 0, "line",{x1:"8",y1:"3",x2:"8",y2:"18"}),_jsxruntime.jsx.call(void 0, "line",{x1:"16",y1:"6",x2:"16",y2:"21"})]})}function Fe(){return _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","aria-hidden":"true",children:[_jsxruntime.jsx.call(void 0, "line",{x1:"12",y1:"5",x2:"12",y2:"19"}),_jsxruntime.jsx.call(void 0, "line",{x1:"5",y1:"12",x2:"19",y2:"12"})]})}var _reactdom = require('react-dom');function pe({open:e,onOpenChange:r,title:t,children:i,footer:s}){let a=_react.useRef.call(void 0, null),[n,p]=_react.useState.call(void 0, !1),[v,f]=_react.useState.call(void 0, !1);return _react.useEffect.call(void 0, ()=>{if(!e){f(!1),p(!1);return}p(!0);let l=requestAnimationFrame(()=>f(!0));return()=>cancelAnimationFrame(l)},[e]),_react.useEffect.call(void 0, ()=>{if(!n)return;let l=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.body.style.overflow=l}},[n]),_react.useEffect.call(void 0, ()=>{if(!n)return;_optionalChain([a, 'access', _2 => _2.current, 'optionalAccess', _3 => _3.focus, 'call', _4 => _4()]);function l(L){L.key==="Escape"&&r(!1)}return document.addEventListener("keydown",l),()=>document.removeEventListener("keydown",l)},[n,r]),!n||typeof document>"u"?null:_reactdom.createPortal.call(void 0, _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[_jsxruntime.jsx.call(void 0, "div",{className:`rle-sheet-backdrop${v?" rle-sheet-backdrop--open":""}`,onClick:()=>r(!1),"aria-hidden":"true"}),_jsxruntime.jsxs.call(void 0, "div",{ref:a,className:`rle-sheet${v?" rle-sheet--open":""}`,role:"dialog","aria-modal":"true","aria-label":t,tabIndex:-1,children:[_jsxruntime.jsx.call(void 0, "div",{className:"rle-sheet__handle","aria-hidden":"true"}),_jsxruntime.jsxs.call(void 0, "div",{className:"rle-sheet__header",children:[t&&_jsxruntime.jsx.call(void 0, "div",{className:"rle-sheet__title",children:t}),_jsxruntime.jsx.call(void 0, "button",{type:"button",className:"rle-sheet__close",onClick:()=>r(!1),"aria-label":"Close",children:_jsxruntime.jsx.call(void 0, Te,{})})]}),_jsxruntime.jsx.call(void 0, "div",{className:"rle-sheet__body",children:i}),s&&_jsxruntime.jsx.call(void 0, "div",{className:"rle-sheet__footer",children:s})]})]}),document.body)}function Te(){return _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",width:"16",height:"16","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"})]})}var Be=_jsxruntime.jsx.call(void 0, "div",{className:"rle-empty",children:"Map unavailable"});function me({search:e,toolbarEnd:r,mobileAction:t,autoFetch:i=!0,mapCenter:s,mapZoom:a,className:n}){let p=_chunk5O36WI7Hcjs.s.call(void 0, ),{Search:v}=_chunk5O36WI7Hcjs.q.call(void 0, ),f=_chunk5O36WI7Hcjs.w.call(void 0, ),[l,L]=_react.useState.call(void 0, "list"),[k,N]=_react.useState.call(void 0, !1);_react.useEffect.call(void 0, ()=>{i!==!1&&p.applyFilters({})},[p,i]);let S=()=>{let I=Object.fromEntries(p.filters.list().map(F=>[F.key,void 0]));p.applyFilters(I)},g=_nullishCoalesce(f.total, () => (f.items.length));return _jsxruntime.jsxs.call(void 0, "div",{className:n?`rle-app ${n}`:"rle-app",children:[_jsxruntime.jsxs.call(void 0, "div",{className:"rle-filter-bar",children:[e&&_jsxruntime.jsx.call(void 0, v,{value:e.value,onChange:e.onChange,placeholder:e.placeholder}),_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.v,{className:"rle-filters-row",groupClassName:"rle-filter-group"}),_jsxruntime.jsxs.call(void 0, "div",{className:"rle-filter-bar__end",children:[_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.A,{}),r]})]}),_jsxruntime.jsxs.call(void 0, "div",{className:"rle-body rle-split","data-mobile-view":l,children:[_jsxruntime.jsxs.call(void 0, "div",{className:"rle-list",children:[_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.x,{className:"rle-list-grid"}),_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.z,{})]}),_jsxruntime.jsx.call(void 0, "div",{className:"rle-map",children:_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.y,{center:s,zoom:a,fallback:Be})})]}),_jsxruntime.jsx.call(void 0, le,{view:l,onViewChange:L,onFiltersClick:()=>N(!0),action:t}),_jsxruntime.jsx.call(void 0, pe,{title:"Filters",open:k,onOpenChange:N,footer:_jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[_jsxruntime.jsx.call(void 0, "button",{type:"button",className:"rle-btn rle-btn--ghost",onClick:S,children:"Clear all"}),_jsxruntime.jsxs.call(void 0, "button",{type:"button",className:"rle-btn rle-btn--primary",onClick:()=>N(!1),children:["Show ",g," results"]})]}),children:_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.v,{className:"rle-filter-stack",groupClassName:"rle-filter-group"})})]})}function ue(e){return"provider"in e}function Ke(e){let r=e!=null&&!ue(e),t=r?e.apiKey:void 0,i=r?e.mapId:void 0,[s,a]=_react.useState.call(void 0, ()=>!e||ue(e)?{ready:!0,provider:_optionalChain([e, 'optionalAccess', _5 => _5.provider])}:{ready:!1});return _react.useEffect.call(void 0, ()=>{if(!t)return;let n=!1;return Promise.resolve().then(() => _interopRequireWildcard(require("./maps/google/index.cjs"))).then(({googleProvider:p})=>{n||a({ready:!0,provider:p({apiKey:t,mapId:i})})}),()=>{n=!0}},[t,i]),s}function We({onFiltersChange:e}){let r=_react.useRef.call(void 0, e);return r.current=e,oe("FiltersChanged",t=>{t.type==="FiltersChanged"&&r.current(t.filters)}),null}function Zt(e){let{datasets:r,filters:t,map:i,components:s,initialFilters:a,onFiltersChange:n,mobileAction:p,search:v,toolbarEnd:f,config:l,autoFetch:L,className:k}=e,{ready:N,provider:S}=Ke(i);if(!N)return null;let g=[];for(let ge of r)g.push(_chunk5O36WI7Hcjs.k.call(void 0, ge));t&&g.push(_chunk5O36WI7Hcjs.l.call(void 0, t)),S&&g.push(_chunk5O36WI7Hcjs.j.call(void 0, S)),a&&g.push(_chunk5O36WI7Hcjs.n.call(void 0, a)),l&&g.push(_chunk5O36WI7Hcjs.i.call(void 0, l));let I=_chunk5O36WI7Hcjs.h.call(void 0, ...g),F={...ae,...s};return _jsxruntime.jsxs.call(void 0, _chunk5O36WI7Hcjs.B,{...I,children:[n&&_jsxruntime.jsx.call(void 0, We,{onFiltersChange:n}),_jsxruntime.jsx.call(void 0, _chunk5O36WI7Hcjs.p,{...F,children:_jsxruntime.jsx.call(void 0, me,{className:k,search:v,toolbarEnd:f,mobileAction:p,autoFetch:L,mapCenter:_optionalChain([i, 'optionalAccess', _6 => _6.center]),mapZoom:_optionalChain([i, 'optionalAccess', _7 => _7.zoom])})})]})}exports.a = oe; exports.b = E; exports.c = T; exports.d = _; exports.e = R; exports.f = B; exports.g = V; exports.h = D; exports.i = O; exports.j = K; exports.k = W; exports.l = ae; exports.m = le; exports.n = pe; exports.o = me; exports.p = Zt;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var S=class{state;listeners=new Set;constructor(t){this.state=this.freezeState({filters:{...t.filters},results:{items:[],nextCursor:null},bounds:null,selection:null,pagination:{mode:t.mode??"paged",loading:!1},layers:{},points:{}})}getState(){return this.state}setFilters(t){this.setState({filters:{...this.state.filters,...t}})}setResults(t){this.setState({results:{items:[...t.items],nextCursor:t.nextCursor,total:t.total}})}appendResults(t){this.setState({results:{items:[...this.state.results.items,...t.items],nextCursor:t.nextCursor,total:t.total}})}setBounds(t){this.setState({bounds:t?{...t}:null})}setSelection(t){this.setState({selection:t})}setLayerVisible(t,e){this.setState({layers:{...this.state.layers,[t]:e}})}setLoading(t){this.setState({pagination:{...this.state.pagination,loading:t}})}setPoints(t,e){this.setState({points:{...this.state.points,[t]:[...e]}})}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}setState(t){this.state=this.freezeState({...this.state,...t}),this.notify()}notify(){for(let t of this.listeners)t()}freezeState(t){Object.freeze(t.filters),Object.freeze(t.results.items),Object.freeze(t.results),t.bounds&&Object.freeze(t.bounds),Object.freeze(t.pagination),Object.freeze(t.layers);for(let e of Object.values(t.points))Object.freeze(e);return Object.freeze(t.points),Object.freeze(t)}};var P=class{defs=new Map;add(t){if(this.defs.has(t.key))throw new Error(`FilterRegistry: filter "${t.key}" is already registered`);return this.defs.set(t.key,{...t}),this}remove(t){return this.defs.delete(t),this}replace(t,e){if(!this.defs.has(t))throw new Error(`FilterRegistry: cannot replace unregistered filter "${t}"`);return this.defs.set(t,{...e,key:t}),this}reorder(t){let e=this.list(),n=t.filter(r=>this.defs.has(r));n.forEach((r,o)=>{this.defs.get(r).order=o});let s=new Set(n);return e.filter(r=>!s.has(r.key)).forEach((r,o)=>{r.order=n.length+o}),this}list(){return[...this.defs.values()].sort((t,e)=>t.order-e.order)}has(t){return this.defs.has(t)}toFilters(t){return this.list().filter(n=>Object.hasOwn(t,n.key)).map(n=>n.toParams(t[n.key])).reduce((n,s)=>({...n,...s}),{})}activeKeys(t){return this.list().filter(e=>e.isActive?.(t)).map(e=>e.key)}};var w=class{defs=new Map;add(t){if(this.defs.has(t.id))throw new Error(`DatasetRegistry: dataset "${t.id}" is already registered`);return this.defs.set(t.id,{...t}),this}get(t){return this.defs.get(t)}has(t){return this.defs.has(t)}list(){return[...this.defs.values()]}visibleIds(){return this.list().filter(t=>t.visible?.()!==!1).map(t=>t.id)}};var R=class{map=new Map;dispose(){this.map.clear()}emit(t){let e=this.map.get(t.type);if(e)for(let s of[...e])s(t);let n=this.map.get("*");if(n)for(let s of[...n])s(t)}on(t,e){let n=this.map.get(t);return n||(n=new Set,this.map.set(t,n)),n.add(e),()=>{n.delete(e)}}};var B={pagination:"paged",pageSize:20,debounceMs:250};var x=class{options;constructor(t){this.options=Object.freeze({...B,...t})}};var D=class{filters;map;datasets;primaryDatasetId;store;emitter=new R;config;debounceTimer=null;debounceResolve=null;queryToken=0;pointsToken=0;constructor(t){this.datasets=t.datasets,this.filters=t.filters??new P,this.map=t.map,this.config=new x(t.config);let e=t.primaryDatasetId??this.datasets.list()[0]?.id;if(e==null||!this.datasets.has(e))throw new Error(`ListingEngine: primary dataset "${e??""}" is not registered \u2014 pass a valid primaryDatasetId or register at least one dataset`);this.primaryDatasetId=e,this.store=new S({filters:t.initialFilters??{},mode:this.config.options.pagination})}get state(){return this.store.getState()}get options(){return this.config.options}applyFilters(t){this.store.setFilters(t),this.emitter.emit({type:"FiltersChanged",filters:this.currentFilters()}),this.clearDebounce();let e=++this.queryToken,n=this.config.options.debounceMs;return n<=0?this.runQuery(e):new Promise(s=>{this.debounceResolve=s,this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.debounceResolve=null,s(this.runQuery(e))},n)})}async loadPage(){let t=this.state.results.nextCursor;if(t===null)return;let e=++this.queryToken,n=this.primaryDataset();this.store.setLoading(!0);try{let s=await n.adapter.list(this.currentFilters(),{cursor:t,limit:this.config.options.pageSize});if(e!==this.queryToken)return;this.config.options.pagination==="infinite"?this.store.appendResults(s):this.store.setResults(s),this.emitter.emit({type:"ResultsLoaded",datasetId:this.primaryDatasetId,count:s.items.length})}finally{e===this.queryToken&&this.store.setLoading(!1)}}async loadPoints(t){this.store.setBounds(t),this.emitter.emit({type:"BoundsChanged",bounds:t});let e=this.currentFilters(),n=++this.pointsToken;await Promise.allSettled(this.datasets.visibleIds().map(async s=>{let r=this.datasets.get(s);if(!r)return;let o=await r.adapter.getPoints(e,t);n===this.pointsToken&&this.store.setPoints(s,o)}))}selectPoint(t,e){this.store.setSelection(e);let n=this.state.points[t]?.find(s=>s.id===e);n&&this.emitter.emit({type:"PointClicked",datasetId:t,id:e,entity:n.entity})}toggleLayer(t){let n=!(this.state.layers[t]??!0);this.store.setLayerVisible(t,n),this.emitter.emit({type:"LayerToggled",datasetId:t,visible:n})}subscribe(t){return this.store.subscribe(t)}on(t,e){return this.emitter.on(t,e)}dispose(){this.clearDebounce(),this.emitter.dispose()}async runQuery(t){if(t!==this.queryToken)return;let e=this.primaryDataset();this.store.setLoading(!0);try{let n=await e.adapter.list(this.currentFilters(),{cursor:null,limit:this.config.options.pageSize});if(t!==this.queryToken)return;this.store.setResults(n),this.emitter.emit({type:"ResultsLoaded",datasetId:this.primaryDatasetId,count:n.items.length})}finally{t===this.queryToken&&this.store.setLoading(!1)}}primaryDataset(){return this.datasets.get(this.primaryDatasetId)}currentFilters(){return this.store.getState().filters}clearDebounce(){this.debounceTimer!==null&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.debounceResolve!==null&&(this.debounceResolve(),this.debounceResolve=null)}};function Mt(...i){let t={config:{},filters:new P,datasets:new w};for(let e of i)e(t);return t}var Ot=i=>t=>{t.config={...t.config,...i}},Nt=i=>t=>{t.map=i},zt=i=>t=>{t.datasets.add(i)},Bt=i=>t=>{i(t.filters)},Ut=i=>t=>{t.urlSync=i},Ht=i=>t=>{t.initialFilters=i},jt=i=>t=>{t.primaryDatasetId=i};import{createContext as $,useContext as Q}from"react";import{jsx as f,jsxs as rt}from"react/jsx-runtime";function G(i){return String(i?.title??"")}var W=({item:i})=>f("div",{children:G(i)}),X=()=>f("div",{}),J=()=>f("div",{}),Y=({children:i})=>f("div",{children:i}),Z=({children:i})=>f("div",{children:i}),tt=({value:i,onChange:t,placeholder:e})=>f("input",{type:"search",value:i,placeholder:e,onChange:n=>t(n.target.value),"aria-label":e??"Search"}),et=()=>f("div",{role:"status",children:"No results"}),it=()=>f("div",{role:"status","aria-busy":"true",children:"Loading\u2026"}),nt=({count:i})=>rt("div",{children:[i," results"]}),st=({children:i})=>f("div",{children:i}),c={Card:W,Marker:X,Popup:J,Sidebar:Y,FilterPanel:Z,Search:tt,Empty:et,Loading:it,ResultHeader:nt,Toolbar:st},U=$(c);function Vt(i){let{Card:t,Marker:e,Popup:n,Sidebar:s,FilterPanel:r,Search:o,Empty:a,Loading:u,ResultHeader:L,Toolbar:y,children:k}=i,b={Card:t??c.Card,Marker:e??c.Marker,Popup:n??c.Popup,Sidebar:s??c.Sidebar,FilterPanel:r??c.FilterPanel,Search:o??c.Search,Empty:a??c.Empty,Loading:u??c.Loading,ResultHeader:L??c.ResultHeader,Toolbar:y??c.Toolbar};return f(U.Provider,{value:b,children:k})}function v(){return Q(U)}import{createContext as ot}from"react";var M=ot(null);import{useContext as at}from"react";function d(){let i=at(M);if(i===null)throw new Error("useListing must be used within a <ListingProvider>");return i}import{useCallback as H,useSyncExternalStore as lt}from"react";function m(){let i=d(),t=H(n=>i.subscribe(n),[i]),e=H(()=>i.state,[i]);return lt(t,e,e)}import{useCallback as j}from"react";function q(){let i=d(),t=m(),e=j(s=>i.applyFilters(s),[i]),n=j((s,r)=>i.applyFilters({[s]:r}),[i]);return{filters:t.filters,set:e,setField:n}}import{jsx as I,jsxs as pt}from"react/jsx-runtime";function ae({className:i,groupClassName:t}={}){let e=d(),{FilterPanel:n}=v(),{filters:s}=q();return I(n,{children:I("div",{className:i??"space-y-5",children:e.filters.list().map(r=>{if(typeof r.render=="string")return I("div",{"data-filter":r.key,className:t},r.key);let o=r.render;return pt("div",{className:t,children:[r.label&&I("div",{className:"mb-1.5 text-[13px] font-medium text-foreground",children:r.label}),I(o,{value:r.fromParams(s),onChange:a=>{e.applyFilters(r.toParams(a))}})]},r.key)})})})}function O(){return m().results}import{jsx as N}from"react/jsx-runtime";function ut(i,t){if(i&&typeof i=="object"&&"id"in i){let e=i.id;if(typeof e=="string"||typeof e=="number")return e}return t}function ye({className:i}={}){let t=d(),{items:e}=O(),{pagination:n,selection:s}=m(),{Card:r,Empty:o,Loading:a}=v();return n.loading&&e.length===0?N(a,{}):e.length===0?N(o,{}):N("div",{role:"list",className:i,children:e.map((u,L)=>{let y=ut(u,L);return N(r,{item:u,selected:s===y,onSelect:()=>t.selectPoint(t.primaryDatasetId,y)},y)})})}import{useEffect as z,useRef as C,useState as dt}from"react";import{jsx as ft}from"react/jsx-runtime";var ct={west:-179.9,south:-85,east:179.9,north:85},A=.1,V=.02;function gt(i){if(i.length===0)return null;let t=i[0].lng,e=i[0].lng,n=i[0].lat,s=i[0].lat;for(let{lat:a,lng:u}of i)u<t&&(t=u),u>e&&(e=u),a<n&&(n=a),a>s&&(s=a);let r=s>n?(s-n)*A:V,o=e>t?(e-t)*A:V;return{west:t-o,east:e+o,south:n-r,north:s+r}}function ve(i){let{center:t,zoom:e,fallback:n}=i,s=d(),r=m(),o=C(null),a=C(null),[u,L]=dt(!1),y=C(!1),k=C(!1),b=C(!1),l=s.map;return z(()=>{let T=a.current;if(!l||!T)return;let h=[];for(let p of Object.keys(r.points)){if(r.layers[p]===!1)continue;let g=s.datasets.get(p),E=r.points[p]??[],_={id:p,markers:E.map(F=>({id:F.id,position:F.position,iconUrl:g?.marker.iconUrl?.(F.entity),element:g?.marker.element?.(F.entity)})),clustering:g?.clustering,onMarkerClick:F=>s.selectPoint(p,F)};h.push(l.renderLayer(T,_))}return()=>{h.forEach(p=>p())}},[s,l,u,r.points,r.layers]),z(()=>{if(!o.current||!l)return;let T=o.current,h=!1,p=null;return(async()=>{let g=await l.mount(T,{center:t,zoom:e});if(h){l.destroy(g);return}a.current=g,p=l.onBoundsChange(g,E=>{b.current?b.current=!1:k.current=!0,s.loadPoints(E)}),L(!0),s.loadPoints(ct)})(),()=>{h=!0,p?.(),a.current&&(l.destroy(a.current),a.current=null),L(!1)}},[s,l]),z(()=>{let T=a.current;if(!l||!T||t||y.current||k.current)return;let h=[];for(let g of Object.keys(r.points))if(r.layers[g]!==!1)for(let E of r.points[g]??[])h.push(E.position);let p=gt(h);p&&(y.current=!0,b.current=!0,l.fitBounds(T,p))},[l,t,u,r.points,r.layers]),ft("div",{ref:o,className:!l&&n?"flex h-full min-h-0 w-full items-center justify-center":"h-full min-h-0 w-full",children:!l&&n})}import{jsx as mt}from"react/jsx-runtime";function ke(){let i=d(),{results:t,pagination:e}=m(),n=t.nextCursor==null||e.loading;return mt("button",{type:"button",disabled:n,onClick:()=>{i.loadPage()},children:"Load more"})}import{jsx as yt}from"react/jsx-runtime";function De(){let{items:i,total:t}=O(),{ResultHeader:e}=v();return yt(e,{count:i.length,total:t})}import{useEffect as ht,useState as K}from"react";import{jsx as Tt}from"react/jsx-runtime";function Ue(i){let{children:t,...e}=i,[n]=K(()=>e),[s,r]=K(null);return ht(()=>{let o=new D({datasets:n.datasets,filters:n.filters,config:n.config,map:n.map,initialFilters:n.initialFilters,primaryDatasetId:n.primaryDatasetId});return n.urlSync&&n.urlSync.start(o),r(o),()=>{n.urlSync&&n.urlSync.stop(),o.dispose(),r(a=>a===o?null:a)}},[n]),s?Tt(M.Provider,{value:s,children:t}):null}export{S as a,P as b,w as c,R as d,B as e,x as f,D as g,Mt as h,Ot as i,Nt as j,zt as k,Bt as l,Ut as m,Ht as n,jt as o,Vt as p,v as q,M as r,d as s,m as t,q as u,ae as v,O as w,ye as x,ve as y,ke as z,De as A,Ue as B};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { a as EntityAdapter, M as MapPoint } from './entity-adapter.interface-BDgbSxhq.cjs';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ComponentType, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface FilterControlProps<TValue> {
|
|
6
|
+
value: TValue;
|
|
7
|
+
onChange(next: TValue): void;
|
|
8
|
+
}
|
|
9
|
+
interface FilterDefinition<TFilters, TValue = unknown> {
|
|
10
|
+
key: string;
|
|
11
|
+
order: number;
|
|
12
|
+
render: string | ComponentType<FilterControlProps<TValue>>;
|
|
13
|
+
toParams(value: TValue): Partial<TFilters>;
|
|
14
|
+
fromParams(filters: TFilters): TValue;
|
|
15
|
+
isActive?(filters: TFilters): boolean;
|
|
16
|
+
/** Optional human-readable label rendered above the control by `ListingFilters`. Omit for an unlabeled filter (backward compatible). */
|
|
17
|
+
label?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ClusterOptions {
|
|
21
|
+
maxZoom?: number;
|
|
22
|
+
radius?: number;
|
|
23
|
+
}
|
|
24
|
+
interface MarkerRenderer<TEntity> {
|
|
25
|
+
iconUrl?(entity: TEntity): string;
|
|
26
|
+
element?(entity: TEntity): HTMLElement;
|
|
27
|
+
onClick?(entity: TEntity): void;
|
|
28
|
+
}
|
|
29
|
+
interface DatasetDefinition<TEntity, TFilters> {
|
|
30
|
+
id: string;
|
|
31
|
+
adapter: EntityAdapter<TEntity, TFilters>;
|
|
32
|
+
marker: MarkerRenderer<TEntity>;
|
|
33
|
+
clustering?: ClusterOptions | false;
|
|
34
|
+
visible?: () => boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Framework-free registry for `FilterDefinition`s: programmatic add / remove /
|
|
39
|
+
* reorder / replace, plus folding raw control values into `TFilters`
|
|
40
|
+
* (`toFilters`) and reporting which registered filters currently affect an
|
|
41
|
+
* applied `TFilters` (`activeKeys`).
|
|
42
|
+
*
|
|
43
|
+
* Defs are stored by VALUE (shallow-cloned on `add`/`replace`), never by the
|
|
44
|
+
* caller's original reference — `reorder()` rewrites `order` in place on the
|
|
45
|
+
* registry's own copy. This is deliberate: the same `FilterDefinition` object
|
|
46
|
+
* can legitimately be registered in more than one `FilterRegistry` instance
|
|
47
|
+
* (e.g. two listing pages sharing a base filter set), and without cloning,
|
|
48
|
+
* one registry's `reorder()` would silently mutate `.order` on the shared
|
|
49
|
+
* object and bleed into every other registry (and the caller) holding it.
|
|
50
|
+
*/
|
|
51
|
+
declare class FilterRegistry<TFilters> {
|
|
52
|
+
private readonly defs;
|
|
53
|
+
add<TValue = unknown>(def: FilterDefinition<TFilters, TValue>): this;
|
|
54
|
+
remove(key: string): this;
|
|
55
|
+
replace<TValue = unknown>(key: string, def: FilterDefinition<TFilters, TValue>): this;
|
|
56
|
+
reorder(keys: string[]): this;
|
|
57
|
+
list(): FilterDefinition<TFilters>[];
|
|
58
|
+
has(key: string): boolean;
|
|
59
|
+
toFilters(values: Record<string, unknown>): TFilters;
|
|
60
|
+
activeKeys(filters: TFilters): string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface IListingCardProps {
|
|
64
|
+
item: unknown;
|
|
65
|
+
selected?: boolean;
|
|
66
|
+
onSelect?: () => void;
|
|
67
|
+
}
|
|
68
|
+
interface IListingMarkerProps {
|
|
69
|
+
point: MapPoint<unknown>;
|
|
70
|
+
}
|
|
71
|
+
interface IListingPopupProps {
|
|
72
|
+
entity: unknown;
|
|
73
|
+
onClose?: () => void;
|
|
74
|
+
}
|
|
75
|
+
interface IListingSidebarProps {
|
|
76
|
+
children?: ReactNode;
|
|
77
|
+
}
|
|
78
|
+
interface IListingFilterPanelProps {
|
|
79
|
+
children?: ReactNode;
|
|
80
|
+
}
|
|
81
|
+
interface IListingSearchProps {
|
|
82
|
+
value: string;
|
|
83
|
+
onChange(value: string): void;
|
|
84
|
+
placeholder?: string;
|
|
85
|
+
}
|
|
86
|
+
interface IListingEmptyProps {
|
|
87
|
+
}
|
|
88
|
+
interface IListingLoadingProps {
|
|
89
|
+
}
|
|
90
|
+
interface IListingResultHeaderProps {
|
|
91
|
+
count: number;
|
|
92
|
+
total?: number;
|
|
93
|
+
}
|
|
94
|
+
interface IListingToolbarProps {
|
|
95
|
+
children?: ReactNode;
|
|
96
|
+
}
|
|
97
|
+
interface IListingComponents {
|
|
98
|
+
Card: ComponentType<IListingCardProps>;
|
|
99
|
+
Marker: ComponentType<IListingMarkerProps>;
|
|
100
|
+
Popup: ComponentType<IListingPopupProps>;
|
|
101
|
+
Sidebar: ComponentType<IListingSidebarProps>;
|
|
102
|
+
FilterPanel: ComponentType<IListingFilterPanelProps>;
|
|
103
|
+
Search: ComponentType<IListingSearchProps>;
|
|
104
|
+
Empty: ComponentType<IListingEmptyProps>;
|
|
105
|
+
Loading: ComponentType<IListingLoadingProps>;
|
|
106
|
+
ResultHeader: ComponentType<IListingResultHeaderProps>;
|
|
107
|
+
Toolbar: ComponentType<IListingToolbarProps>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Injection point for custom-component overrides. Every slot is optional —
|
|
111
|
+
* anything not provided falls back to the (unstyled) default. Mirrors
|
|
112
|
+
* `react-wizard-engine`'s `WizardComponentsProvider`: explicit per-slot
|
|
113
|
+
* `provided ?? defaults.X` merge, not a generic/reflective loop.
|
|
114
|
+
*/
|
|
115
|
+
declare function ListingComponentsProvider(props: Partial<IListingComponents> & {
|
|
116
|
+
children: ReactNode;
|
|
117
|
+
}): react.JSX.Element;
|
|
118
|
+
declare function useListingComponents(): IListingComponents;
|
|
119
|
+
|
|
120
|
+
export { type ClusterOptions as C, type DatasetDefinition as D, FilterRegistry as F, type IListingToolbarProps as I, ListingComponentsProvider as L, type MarkerRenderer as M, type FilterControlProps as a, type FilterDefinition as b, type IListingCardProps as c, type IListingComponents as d, type IListingEmptyProps as e, type IListingFilterPanelProps as f, type IListingLoadingProps as g, type IListingMarkerProps as h, type IListingPopupProps as i, type IListingResultHeaderProps as j, type IListingSearchProps as k, type IListingSidebarProps as l, useListingComponents as u };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { a as EntityAdapter, M as MapPoint } from './entity-adapter.interface-BDgbSxhq.js';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ComponentType, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
interface FilterControlProps<TValue> {
|
|
6
|
+
value: TValue;
|
|
7
|
+
onChange(next: TValue): void;
|
|
8
|
+
}
|
|
9
|
+
interface FilterDefinition<TFilters, TValue = unknown> {
|
|
10
|
+
key: string;
|
|
11
|
+
order: number;
|
|
12
|
+
render: string | ComponentType<FilterControlProps<TValue>>;
|
|
13
|
+
toParams(value: TValue): Partial<TFilters>;
|
|
14
|
+
fromParams(filters: TFilters): TValue;
|
|
15
|
+
isActive?(filters: TFilters): boolean;
|
|
16
|
+
/** Optional human-readable label rendered above the control by `ListingFilters`. Omit for an unlabeled filter (backward compatible). */
|
|
17
|
+
label?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ClusterOptions {
|
|
21
|
+
maxZoom?: number;
|
|
22
|
+
radius?: number;
|
|
23
|
+
}
|
|
24
|
+
interface MarkerRenderer<TEntity> {
|
|
25
|
+
iconUrl?(entity: TEntity): string;
|
|
26
|
+
element?(entity: TEntity): HTMLElement;
|
|
27
|
+
onClick?(entity: TEntity): void;
|
|
28
|
+
}
|
|
29
|
+
interface DatasetDefinition<TEntity, TFilters> {
|
|
30
|
+
id: string;
|
|
31
|
+
adapter: EntityAdapter<TEntity, TFilters>;
|
|
32
|
+
marker: MarkerRenderer<TEntity>;
|
|
33
|
+
clustering?: ClusterOptions | false;
|
|
34
|
+
visible?: () => boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Framework-free registry for `FilterDefinition`s: programmatic add / remove /
|
|
39
|
+
* reorder / replace, plus folding raw control values into `TFilters`
|
|
40
|
+
* (`toFilters`) and reporting which registered filters currently affect an
|
|
41
|
+
* applied `TFilters` (`activeKeys`).
|
|
42
|
+
*
|
|
43
|
+
* Defs are stored by VALUE (shallow-cloned on `add`/`replace`), never by the
|
|
44
|
+
* caller's original reference — `reorder()` rewrites `order` in place on the
|
|
45
|
+
* registry's own copy. This is deliberate: the same `FilterDefinition` object
|
|
46
|
+
* can legitimately be registered in more than one `FilterRegistry` instance
|
|
47
|
+
* (e.g. two listing pages sharing a base filter set), and without cloning,
|
|
48
|
+
* one registry's `reorder()` would silently mutate `.order` on the shared
|
|
49
|
+
* object and bleed into every other registry (and the caller) holding it.
|
|
50
|
+
*/
|
|
51
|
+
declare class FilterRegistry<TFilters> {
|
|
52
|
+
private readonly defs;
|
|
53
|
+
add<TValue = unknown>(def: FilterDefinition<TFilters, TValue>): this;
|
|
54
|
+
remove(key: string): this;
|
|
55
|
+
replace<TValue = unknown>(key: string, def: FilterDefinition<TFilters, TValue>): this;
|
|
56
|
+
reorder(keys: string[]): this;
|
|
57
|
+
list(): FilterDefinition<TFilters>[];
|
|
58
|
+
has(key: string): boolean;
|
|
59
|
+
toFilters(values: Record<string, unknown>): TFilters;
|
|
60
|
+
activeKeys(filters: TFilters): string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface IListingCardProps {
|
|
64
|
+
item: unknown;
|
|
65
|
+
selected?: boolean;
|
|
66
|
+
onSelect?: () => void;
|
|
67
|
+
}
|
|
68
|
+
interface IListingMarkerProps {
|
|
69
|
+
point: MapPoint<unknown>;
|
|
70
|
+
}
|
|
71
|
+
interface IListingPopupProps {
|
|
72
|
+
entity: unknown;
|
|
73
|
+
onClose?: () => void;
|
|
74
|
+
}
|
|
75
|
+
interface IListingSidebarProps {
|
|
76
|
+
children?: ReactNode;
|
|
77
|
+
}
|
|
78
|
+
interface IListingFilterPanelProps {
|
|
79
|
+
children?: ReactNode;
|
|
80
|
+
}
|
|
81
|
+
interface IListingSearchProps {
|
|
82
|
+
value: string;
|
|
83
|
+
onChange(value: string): void;
|
|
84
|
+
placeholder?: string;
|
|
85
|
+
}
|
|
86
|
+
interface IListingEmptyProps {
|
|
87
|
+
}
|
|
88
|
+
interface IListingLoadingProps {
|
|
89
|
+
}
|
|
90
|
+
interface IListingResultHeaderProps {
|
|
91
|
+
count: number;
|
|
92
|
+
total?: number;
|
|
93
|
+
}
|
|
94
|
+
interface IListingToolbarProps {
|
|
95
|
+
children?: ReactNode;
|
|
96
|
+
}
|
|
97
|
+
interface IListingComponents {
|
|
98
|
+
Card: ComponentType<IListingCardProps>;
|
|
99
|
+
Marker: ComponentType<IListingMarkerProps>;
|
|
100
|
+
Popup: ComponentType<IListingPopupProps>;
|
|
101
|
+
Sidebar: ComponentType<IListingSidebarProps>;
|
|
102
|
+
FilterPanel: ComponentType<IListingFilterPanelProps>;
|
|
103
|
+
Search: ComponentType<IListingSearchProps>;
|
|
104
|
+
Empty: ComponentType<IListingEmptyProps>;
|
|
105
|
+
Loading: ComponentType<IListingLoadingProps>;
|
|
106
|
+
ResultHeader: ComponentType<IListingResultHeaderProps>;
|
|
107
|
+
Toolbar: ComponentType<IListingToolbarProps>;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Injection point for custom-component overrides. Every slot is optional —
|
|
111
|
+
* anything not provided falls back to the (unstyled) default. Mirrors
|
|
112
|
+
* `react-wizard-engine`'s `WizardComponentsProvider`: explicit per-slot
|
|
113
|
+
* `provided ?? defaults.X` merge, not a generic/reflective loop.
|
|
114
|
+
*/
|
|
115
|
+
declare function ListingComponentsProvider(props: Partial<IListingComponents> & {
|
|
116
|
+
children: ReactNode;
|
|
117
|
+
}): react.JSX.Element;
|
|
118
|
+
declare function useListingComponents(): IListingComponents;
|
|
119
|
+
|
|
120
|
+
export { type ClusterOptions as C, type DatasetDefinition as D, FilterRegistry as F, type IListingToolbarProps as I, ListingComponentsProvider as L, type MarkerRenderer as M, type FilterControlProps as a, type FilterDefinition as b, type IListingCardProps as c, type IListingComponents as d, type IListingEmptyProps as e, type IListingFilterPanelProps as f, type IListingLoadingProps as g, type IListingMarkerProps as h, type IListingPopupProps as i, type IListingResultHeaderProps as j, type IListingSearchProps as k, type IListingSidebarProps as l, useListingComponents as u };
|