react-listing-engine 0.6.5 → 0.6.7

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.
@@ -1,213 +0,0 @@
1
- import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { L as LatLng, a as MapProvider } from './map-provider.interface-DT-v1plm.cjs';
4
- import { D as DatasetDefinition, F as FilterRegistry, e as IListingComponents, I as IListingConfigOptions } from './components-provider-FinfaqUT.cjs';
5
-
6
- type BottomNavView = 'list' | 'map';
7
- interface IBottomNavAction {
8
- label: string;
9
- icon?: ReactNode;
10
- onClick(): void;
11
- }
12
- interface IBottomNavProps {
13
- view: BottomNavView;
14
- onViewChange(view: BottomNavView): void;
15
- }
16
- /**
17
- * Mobile-only bottom navigation (`.rle-bottom-nav`, hidden at 768px+ by CSS --
18
- * see `styles.css`): a floating **List | Map** segmented toggle
19
- * (`.rle-viewtoggle`) that drives which of `StyledListingLayout`'s two
20
- * full-area panels is visible. The **Filters** button and the optional caller
21
- * action (e.g. "Save") live in the mobile header (`MobileHeader`) instead, so
22
- * the footer pill carries the view toggle alone. No lucide/icon library
23
- * dependency -- every icon here is a small inline SVG, matching the rest of
24
- * `/styled`'s zero-extra-dependency policy.
25
- */
26
- declare function BottomNav({ view, onViewChange }: IBottomNavProps): react.JSX.Element;
27
-
28
- interface IStyledListingLayoutProps {
29
- /**
30
- * Optional header search box, LIBRARY-wired (unlike `/shadcn`'s
31
- * consumer-managed `search`): name the `TFilters` field it drives via
32
- * `filterKey`, and the layout reads the current value from the engine's
33
- * filters and writes edits back with `applyFilters({ [filterKey]: value ||
34
- * undefined })`. Rendered in BOTH the desktop filter bar and the mobile
35
- * header. Do NOT also register `filterKey` as a `ListingFilters` control --
36
- * it would then render twice. Omitted entirely when not passed.
37
- */
38
- search?: {
39
- filterKey: string;
40
- placeholder?: string;
41
- };
42
- /** Extra content rendered in `.rle-list-header` (above the list), to the right of `ListingResultHeader` (e.g. a sort control + save-search). */
43
- toolbarEnd?: ReactNode;
44
- /** Optional bottom-nav action button (e.g. "Add"), forwarded verbatim to `<BottomNav action={...} />`. Omit to render just Filters + the List|Map toggle. */
45
- mobileAction?: IBottomNavAction;
46
- /**
47
- * Whether a map is configured. When `false`, the map region is dropped and
48
- * the results list fills the full width as a multi-column grid, and the
49
- * mobile List|Map toggle is omitted (nothing to toggle to). Defaults to
50
- * `true` (the split list/map view). `ListingApp` passes `map != null`.
51
- */
52
- hasMap?: boolean;
53
- /** Whether the layout fetches the first page itself on mount (`engine.applyFilters({})`). Defaults to `true`. */
54
- autoFetch?: boolean;
55
- /** Forwarded verbatim to `<ListingMap center={mapCenter} />` -- see that component's "Auto-fit" doc comment. */
56
- mapCenter?: LatLng;
57
- /** Forwarded verbatim to `<ListingMap zoom={mapZoom} />`. */
58
- mapZoom?: number;
59
- className?: string;
60
- }
61
- /**
62
- * Full, responsive, Tailwind-free listing experience -- the `/styled`
63
- * counterpart to `/shadcn`'s `ListingLayout`, built from the same
64
- * structure-only compound components (`~/react`) plus the injected
65
- * `Styled*` slot components, but with a richer mobile experience: a bottom
66
- * nav (`BottomNav`) and a bottom sheet (`BottomSheet`) for filters, instead
67
- * of `/shadcn`'s inline mobile toggle bar. Every class used here is one of
68
- * the `.rle-*` layout classes added to `styles.css` alongside this file --
69
- * no Tailwind, no inline styles, no other stylesheet required.
70
- *
71
- * STRUCTURE (`.rle-app`):
72
- * - `.rle-filter-bar` (desktop only, hidden below 1024px by CSS): the
73
- * optional `Search` slot and `<ListingFilters>` laid out as a single
74
- * horizontally-scrolling row (`className="rle-filters-row"`,
75
- * `groupClassName="rle-filter-group"`) with edge fades. The result header +
76
- * `toolbarEnd` are NOT here -- they sit in `.rle-list-header` above the list.
77
- * - `.rle-list-header` (top of `.rle-list`): `<ListingResultHeader>` (title +
78
- * count) at the left, `toolbarEnd` (sort control, save-search, ...) at the
79
- * right -- a heading for the results, on every viewport.
80
- * - `.rle-body.rle-split`: a CSS grid from 768px up (list column floors at
81
- * 340px, caps at 42%; map takes the rest); below that, a single full-area
82
- * panel with exactly one of `.rle-list`/`.rle-map` visible at a time via
83
- * `data-mobile-view` (see `styles.css`'s mobile media query). Both regions
84
- * stay mounted at ALL times regardless of viewport/toggle state -- only
85
- * their visibility flips -- so neither `ListingList` nor `ListingMap`
86
- * remounts (and re-triggers its own mount effects) on toggle or resize.
87
- * - `<BottomNav>`: mobile-only (CSS-hidden at 768px+). Wired to the same
88
- * `mobileView` state as the CSS toggle, `mobileAction`, and opens the
89
- * filters `<BottomSheet>`.
90
- * - `<BottomSheet title="Filters">`: the SAME `<ListingFilters>` component,
91
- * stacked vertically (`className="rle-filter-stack"`) for the sheet's
92
- * narrower body, plus a footer with "Clear all" (resets every registered
93
- * filter to `undefined` via `engine.filters.list()`'s keys, routed through
94
- * `engine.applyFilters` -- the one bulk mutator, same as `ListingFilters`
95
- * itself uses) and "Show N results" (just closes the sheet -- every filter
96
- * control already applies live via its own `onChange`, so there is nothing
97
- * left to commit).
98
- * - Fetches the first page itself on mount (`engine.applyFilters({})`) by
99
- * default, exactly like `/shadcn`'s `ListingLayout` -- pass `autoFetch={false}`
100
- * to opt out and drive the first fetch yourself.
101
- */
102
- declare function StyledListingLayout({ search, toolbarEnd, mobileAction, autoFetch, hasMap, mapCenter, mapZoom, className, }: IStyledListingLayoutProps): react.JSX.Element;
103
-
104
- /**
105
- * Same two-shape `map` prop as `/shadcn`'s `ListingApp` (see that file's
106
- * `useResolvedMap` doc comment for why the `{ apiKey }` shorthand is resolved
107
- * via a dynamic `import()` rather than a static one -- the reasoning is
108
- * identical here). Re-declared rather than imported from `~/shadcn`: the two
109
- * styled adapters are SIBLINGS, not a hierarchy -- `/styled` never depends on
110
- * `/shadcn` (or vice versa), each is self-contained against `~/core`/`~/react`.
111
- */
112
- type ListingAppMapProp = {
113
- provider: MapProvider;
114
- center?: LatLng;
115
- zoom?: number;
116
- } | {
117
- apiKey: string;
118
- mapId?: string;
119
- center?: LatLng;
120
- zoom?: number;
121
- };
122
- interface ListingAppProps<TFilters> {
123
- /**
124
- * One or more marker-layer datasets; the FIRST entry is the primary
125
- * dataset (drives the results list + pagination) -- same "insertion
126
- * order" rule `composeListingProviders`/`withDataset` already follow.
127
- * Entity-erased (`any`, not `TEntity`) for the same reason
128
- * `DatasetRegistry<unknown, TFilters>` is: one array can legitimately hold
129
- * heterogeneous layers (a properties dataset and a businesses dataset have
130
- * different entity types) -- see `compose-listing-providers.ts`'s
131
- * `withDataset` doc comment.
132
- */
133
- datasets: DatasetDefinition<any, TFilters>[];
134
- /** A `(reg) => { reg.add(...); }` callback that registers your filters -- forwarded verbatim to `withFilters`. */
135
- filters?: (reg: FilterRegistry<TFilters>) => void;
136
- /** A ready `MapProvider`, or `{ apiKey, mapId? }` to build a `googleProvider` internally. Omit for no map (the styled layout shows a "Map unavailable" fallback). */
137
- map?: ListingAppMapProp;
138
- /** Slot overrides, merged OVER the `/styled` defaults -- see this file's doc comment for how the merge works. */
139
- components?: Partial<IListingComponents>;
140
- /**
141
- * Hydrates the engine's initial filters on mount -- typically read from
142
- * the CONSUMER's own URL (e.g. `rentalFiltersFromQuery(new
143
- * URLSearchParams(window.location.search))`), but any source works. Half
144
- * of the event-based URL API this component exposes: `ListingApp` never
145
- * reads `window.location` itself, it only accepts filters as a prop.
146
- */
147
- initialFilters?: TFilters;
148
- /**
149
- * EVENT OUT: fires with the engine's current filters (`TFilters`, not the
150
- * store's `DeepReadonly` wrapper) every time `ListingEventType.FiltersChanged`
151
- * fires on the underlying engine -- i.e. on every `engine.applyFilters(...)`
152
- * call, INCLUDING the one `StyledListingLayout` makes on mount when
153
- * `autoFetch` is on (a `{}` patch that still round-trips through
154
- * `currentFilters()` and re-emits the very `initialFilters` this component
155
- * was given). That first call is intentionally NOT suppressed: a
156
- * consumer's handler is expected to write filters back to a URL via
157
- * `history.replaceState` (or a router's equivalent), and replacing the URL
158
- * with the SAME query string it already hydrated from is a no-op, not a
159
- * visible echo -- so "just always emit" is simpler than tracking a
160
- * first-call flag for no behavioral gain. This is the OTHER half of the
161
- * event-based URL API: `ListingApp` (and the engine underneath it) never
162
- * touches `window.history` -- the CONSUMER owns routing, driven by this
163
- * callback plus `initialFilters` above. `UrlSyncController`/`BrowserHistoryPort`
164
- * (`~/core`, `~/react`) remain exported as optional, lower-level helpers
165
- * for consumers who want the library to own `window.history` directly, but
166
- * they are no longer the primary/recommended path for new integrations.
167
- */
168
- onFiltersChange?: (filters: TFilters) => void;
169
- /** The mobile bottom-nav "Add" action -- forwarded verbatim to `StyledListingLayout`/`BottomNav`. */
170
- mobileAction?: IBottomNavAction;
171
- search?: IStyledListingLayoutProps['search'];
172
- toolbarEnd?: IStyledListingLayoutProps['toolbarEnd'];
173
- config?: Partial<IListingConfigOptions>;
174
- autoFetch?: boolean;
175
- className?: string;
176
- }
177
- /**
178
- * Turnkey, batteries-included, Tailwind-free entry point -- the `/styled`
179
- * counterpart to `/shadcn`'s `ListingApp`, and (per `src/index.ts`) the
180
- * package's MAIN-ENTRY default: `import { ListingApp } from
181
- * 'react-listing-engine'` gives you this component. Pass datasets + filters +
182
- * a map + component overrides, and `ListingApp` composes
183
- * `composeListingProviders(...)`, `<ListingProvider>`, the `/styled` defaults,
184
- * and `<StyledListingLayout>` for you.
185
- *
186
- * `TEntity` still can't be inferred from these props (`datasets` narrows it,
187
- * but a multi-dataset array widens back to the union/`unknown` in practice)
188
- * -- annotate the call site when entity-level typing matters, exactly like
189
- * `<ListingProvider<TEntity, TFilters>>` itself.
190
- *
191
- * COMPONENTS MERGE: `components` overrides are applied ON TOP of the
192
- * `/styled` defaults via ONE explicit `{ ...styledDefaultComponents,
193
- * ...components }` object, passed to a single `<ListingComponentsProvider>`
194
- * -- never as a nested `<ListingComponentsProvider>` inside
195
- * `StyledComponentsProviderWithDefaults`. `ListingComponentsProvider` merges
196
- * `provided ?? ITS OWN private, unstyled fallbacks` per slot (it does not
197
- * read the parent context -- see `src/react/components-provider.tsx`), so
198
- * nesting would silently discard every un-overridden `/styled` default
199
- * instead of keeping it.
200
- *
201
- * URL SYNC IS EVENT-BASED, NOT INTERNAL: unlike `/shadcn`'s `ListingApp`,
202
- * this component takes no `urlSync` prop and never constructs/starts a
203
- * `UrlSyncController` -- it never touches `window.history`. Instead it
204
- * accepts `initialFilters` (hydrate FROM the consumer's URL) and emits
205
- * `onFiltersChange` (write TO the consumer's URL/router) -- see both props'
206
- * doc comments for the full contract. This keeps the library's surface
207
- * router-agnostic (a plain `history.replaceState`, a Next.js `router.replace`,
208
- * a React Router `setSearchParams`, etc. all work identically from the
209
- * consumer's side) instead of assuming `window.history` is the right target.
210
- */
211
- declare function ListingApp<TEntity, TFilters>(props: ListingAppProps<TFilters>): react.JSX.Element | null;
212
-
213
- export { BottomNav as B, type IBottomNavAction as I, ListingApp as L, StyledListingLayout as S, type ListingAppProps as a, type BottomNavView as b, type IBottomNavProps as c, type IStyledListingLayoutProps as d, type ListingAppMapProp as e };
@@ -1,213 +0,0 @@
1
- import * as react from 'react';
2
- import { ReactNode } from 'react';
3
- import { L as LatLng, a as MapProvider } from './map-provider.interface-DT-v1plm.js';
4
- import { D as DatasetDefinition, F as FilterRegistry, e as IListingComponents, I as IListingConfigOptions } from './components-provider-cWVWEDXC.js';
5
-
6
- type BottomNavView = 'list' | 'map';
7
- interface IBottomNavAction {
8
- label: string;
9
- icon?: ReactNode;
10
- onClick(): void;
11
- }
12
- interface IBottomNavProps {
13
- view: BottomNavView;
14
- onViewChange(view: BottomNavView): void;
15
- }
16
- /**
17
- * Mobile-only bottom navigation (`.rle-bottom-nav`, hidden at 768px+ by CSS --
18
- * see `styles.css`): a floating **List | Map** segmented toggle
19
- * (`.rle-viewtoggle`) that drives which of `StyledListingLayout`'s two
20
- * full-area panels is visible. The **Filters** button and the optional caller
21
- * action (e.g. "Save") live in the mobile header (`MobileHeader`) instead, so
22
- * the footer pill carries the view toggle alone. No lucide/icon library
23
- * dependency -- every icon here is a small inline SVG, matching the rest of
24
- * `/styled`'s zero-extra-dependency policy.
25
- */
26
- declare function BottomNav({ view, onViewChange }: IBottomNavProps): react.JSX.Element;
27
-
28
- interface IStyledListingLayoutProps {
29
- /**
30
- * Optional header search box, LIBRARY-wired (unlike `/shadcn`'s
31
- * consumer-managed `search`): name the `TFilters` field it drives via
32
- * `filterKey`, and the layout reads the current value from the engine's
33
- * filters and writes edits back with `applyFilters({ [filterKey]: value ||
34
- * undefined })`. Rendered in BOTH the desktop filter bar and the mobile
35
- * header. Do NOT also register `filterKey` as a `ListingFilters` control --
36
- * it would then render twice. Omitted entirely when not passed.
37
- */
38
- search?: {
39
- filterKey: string;
40
- placeholder?: string;
41
- };
42
- /** Extra content rendered in `.rle-list-header` (above the list), to the right of `ListingResultHeader` (e.g. a sort control + save-search). */
43
- toolbarEnd?: ReactNode;
44
- /** Optional bottom-nav action button (e.g. "Add"), forwarded verbatim to `<BottomNav action={...} />`. Omit to render just Filters + the List|Map toggle. */
45
- mobileAction?: IBottomNavAction;
46
- /**
47
- * Whether a map is configured. When `false`, the map region is dropped and
48
- * the results list fills the full width as a multi-column grid, and the
49
- * mobile List|Map toggle is omitted (nothing to toggle to). Defaults to
50
- * `true` (the split list/map view). `ListingApp` passes `map != null`.
51
- */
52
- hasMap?: boolean;
53
- /** Whether the layout fetches the first page itself on mount (`engine.applyFilters({})`). Defaults to `true`. */
54
- autoFetch?: boolean;
55
- /** Forwarded verbatim to `<ListingMap center={mapCenter} />` -- see that component's "Auto-fit" doc comment. */
56
- mapCenter?: LatLng;
57
- /** Forwarded verbatim to `<ListingMap zoom={mapZoom} />`. */
58
- mapZoom?: number;
59
- className?: string;
60
- }
61
- /**
62
- * Full, responsive, Tailwind-free listing experience -- the `/styled`
63
- * counterpart to `/shadcn`'s `ListingLayout`, built from the same
64
- * structure-only compound components (`~/react`) plus the injected
65
- * `Styled*` slot components, but with a richer mobile experience: a bottom
66
- * nav (`BottomNav`) and a bottom sheet (`BottomSheet`) for filters, instead
67
- * of `/shadcn`'s inline mobile toggle bar. Every class used here is one of
68
- * the `.rle-*` layout classes added to `styles.css` alongside this file --
69
- * no Tailwind, no inline styles, no other stylesheet required.
70
- *
71
- * STRUCTURE (`.rle-app`):
72
- * - `.rle-filter-bar` (desktop only, hidden below 1024px by CSS): the
73
- * optional `Search` slot and `<ListingFilters>` laid out as a single
74
- * horizontally-scrolling row (`className="rle-filters-row"`,
75
- * `groupClassName="rle-filter-group"`) with edge fades. The result header +
76
- * `toolbarEnd` are NOT here -- they sit in `.rle-list-header` above the list.
77
- * - `.rle-list-header` (top of `.rle-list`): `<ListingResultHeader>` (title +
78
- * count) at the left, `toolbarEnd` (sort control, save-search, ...) at the
79
- * right -- a heading for the results, on every viewport.
80
- * - `.rle-body.rle-split`: a CSS grid from 768px up (list column floors at
81
- * 340px, caps at 42%; map takes the rest); below that, a single full-area
82
- * panel with exactly one of `.rle-list`/`.rle-map` visible at a time via
83
- * `data-mobile-view` (see `styles.css`'s mobile media query). Both regions
84
- * stay mounted at ALL times regardless of viewport/toggle state -- only
85
- * their visibility flips -- so neither `ListingList` nor `ListingMap`
86
- * remounts (and re-triggers its own mount effects) on toggle or resize.
87
- * - `<BottomNav>`: mobile-only (CSS-hidden at 768px+). Wired to the same
88
- * `mobileView` state as the CSS toggle, `mobileAction`, and opens the
89
- * filters `<BottomSheet>`.
90
- * - `<BottomSheet title="Filters">`: the SAME `<ListingFilters>` component,
91
- * stacked vertically (`className="rle-filter-stack"`) for the sheet's
92
- * narrower body, plus a footer with "Clear all" (resets every registered
93
- * filter to `undefined` via `engine.filters.list()`'s keys, routed through
94
- * `engine.applyFilters` -- the one bulk mutator, same as `ListingFilters`
95
- * itself uses) and "Show N results" (just closes the sheet -- every filter
96
- * control already applies live via its own `onChange`, so there is nothing
97
- * left to commit).
98
- * - Fetches the first page itself on mount (`engine.applyFilters({})`) by
99
- * default, exactly like `/shadcn`'s `ListingLayout` -- pass `autoFetch={false}`
100
- * to opt out and drive the first fetch yourself.
101
- */
102
- declare function StyledListingLayout({ search, toolbarEnd, mobileAction, autoFetch, hasMap, mapCenter, mapZoom, className, }: IStyledListingLayoutProps): react.JSX.Element;
103
-
104
- /**
105
- * Same two-shape `map` prop as `/shadcn`'s `ListingApp` (see that file's
106
- * `useResolvedMap` doc comment for why the `{ apiKey }` shorthand is resolved
107
- * via a dynamic `import()` rather than a static one -- the reasoning is
108
- * identical here). Re-declared rather than imported from `~/shadcn`: the two
109
- * styled adapters are SIBLINGS, not a hierarchy -- `/styled` never depends on
110
- * `/shadcn` (or vice versa), each is self-contained against `~/core`/`~/react`.
111
- */
112
- type ListingAppMapProp = {
113
- provider: MapProvider;
114
- center?: LatLng;
115
- zoom?: number;
116
- } | {
117
- apiKey: string;
118
- mapId?: string;
119
- center?: LatLng;
120
- zoom?: number;
121
- };
122
- interface ListingAppProps<TFilters> {
123
- /**
124
- * One or more marker-layer datasets; the FIRST entry is the primary
125
- * dataset (drives the results list + pagination) -- same "insertion
126
- * order" rule `composeListingProviders`/`withDataset` already follow.
127
- * Entity-erased (`any`, not `TEntity`) for the same reason
128
- * `DatasetRegistry<unknown, TFilters>` is: one array can legitimately hold
129
- * heterogeneous layers (a properties dataset and a businesses dataset have
130
- * different entity types) -- see `compose-listing-providers.ts`'s
131
- * `withDataset` doc comment.
132
- */
133
- datasets: DatasetDefinition<any, TFilters>[];
134
- /** A `(reg) => { reg.add(...); }` callback that registers your filters -- forwarded verbatim to `withFilters`. */
135
- filters?: (reg: FilterRegistry<TFilters>) => void;
136
- /** A ready `MapProvider`, or `{ apiKey, mapId? }` to build a `googleProvider` internally. Omit for no map (the styled layout shows a "Map unavailable" fallback). */
137
- map?: ListingAppMapProp;
138
- /** Slot overrides, merged OVER the `/styled` defaults -- see this file's doc comment for how the merge works. */
139
- components?: Partial<IListingComponents>;
140
- /**
141
- * Hydrates the engine's initial filters on mount -- typically read from
142
- * the CONSUMER's own URL (e.g. `rentalFiltersFromQuery(new
143
- * URLSearchParams(window.location.search))`), but any source works. Half
144
- * of the event-based URL API this component exposes: `ListingApp` never
145
- * reads `window.location` itself, it only accepts filters as a prop.
146
- */
147
- initialFilters?: TFilters;
148
- /**
149
- * EVENT OUT: fires with the engine's current filters (`TFilters`, not the
150
- * store's `DeepReadonly` wrapper) every time `ListingEventType.FiltersChanged`
151
- * fires on the underlying engine -- i.e. on every `engine.applyFilters(...)`
152
- * call, INCLUDING the one `StyledListingLayout` makes on mount when
153
- * `autoFetch` is on (a `{}` patch that still round-trips through
154
- * `currentFilters()` and re-emits the very `initialFilters` this component
155
- * was given). That first call is intentionally NOT suppressed: a
156
- * consumer's handler is expected to write filters back to a URL via
157
- * `history.replaceState` (or a router's equivalent), and replacing the URL
158
- * with the SAME query string it already hydrated from is a no-op, not a
159
- * visible echo -- so "just always emit" is simpler than tracking a
160
- * first-call flag for no behavioral gain. This is the OTHER half of the
161
- * event-based URL API: `ListingApp` (and the engine underneath it) never
162
- * touches `window.history` -- the CONSUMER owns routing, driven by this
163
- * callback plus `initialFilters` above. `UrlSyncController`/`BrowserHistoryPort`
164
- * (`~/core`, `~/react`) remain exported as optional, lower-level helpers
165
- * for consumers who want the library to own `window.history` directly, but
166
- * they are no longer the primary/recommended path for new integrations.
167
- */
168
- onFiltersChange?: (filters: TFilters) => void;
169
- /** The mobile bottom-nav "Add" action -- forwarded verbatim to `StyledListingLayout`/`BottomNav`. */
170
- mobileAction?: IBottomNavAction;
171
- search?: IStyledListingLayoutProps['search'];
172
- toolbarEnd?: IStyledListingLayoutProps['toolbarEnd'];
173
- config?: Partial<IListingConfigOptions>;
174
- autoFetch?: boolean;
175
- className?: string;
176
- }
177
- /**
178
- * Turnkey, batteries-included, Tailwind-free entry point -- the `/styled`
179
- * counterpart to `/shadcn`'s `ListingApp`, and (per `src/index.ts`) the
180
- * package's MAIN-ENTRY default: `import { ListingApp } from
181
- * 'react-listing-engine'` gives you this component. Pass datasets + filters +
182
- * a map + component overrides, and `ListingApp` composes
183
- * `composeListingProviders(...)`, `<ListingProvider>`, the `/styled` defaults,
184
- * and `<StyledListingLayout>` for you.
185
- *
186
- * `TEntity` still can't be inferred from these props (`datasets` narrows it,
187
- * but a multi-dataset array widens back to the union/`unknown` in practice)
188
- * -- annotate the call site when entity-level typing matters, exactly like
189
- * `<ListingProvider<TEntity, TFilters>>` itself.
190
- *
191
- * COMPONENTS MERGE: `components` overrides are applied ON TOP of the
192
- * `/styled` defaults via ONE explicit `{ ...styledDefaultComponents,
193
- * ...components }` object, passed to a single `<ListingComponentsProvider>`
194
- * -- never as a nested `<ListingComponentsProvider>` inside
195
- * `StyledComponentsProviderWithDefaults`. `ListingComponentsProvider` merges
196
- * `provided ?? ITS OWN private, unstyled fallbacks` per slot (it does not
197
- * read the parent context -- see `src/react/components-provider.tsx`), so
198
- * nesting would silently discard every un-overridden `/styled` default
199
- * instead of keeping it.
200
- *
201
- * URL SYNC IS EVENT-BASED, NOT INTERNAL: unlike `/shadcn`'s `ListingApp`,
202
- * this component takes no `urlSync` prop and never constructs/starts a
203
- * `UrlSyncController` -- it never touches `window.history`. Instead it
204
- * accepts `initialFilters` (hydrate FROM the consumer's URL) and emits
205
- * `onFiltersChange` (write TO the consumer's URL/router) -- see both props'
206
- * doc comments for the full contract. This keeps the library's surface
207
- * router-agnostic (a plain `history.replaceState`, a Next.js `router.replace`,
208
- * a React Router `setSearchParams`, etc. all work identically from the
209
- * consumer's side) instead of assuming `window.history` is the right target.
210
- */
211
- declare function ListingApp<TEntity, TFilters>(props: ListingAppProps<TFilters>): react.JSX.Element | null;
212
-
213
- export { BottomNav as B, type IBottomNavAction as I, ListingApp as L, StyledListingLayout as S, type ListingAppProps as a, type BottomNavView as b, type IBottomNavProps as c, type IStyledListingLayoutProps as d, type ListingAppMapProp as e };
@@ -1,2 +0,0 @@
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 _chunk2VUHLHHXcjs = require('../chunk-2VUHLHHX.cjs');var _chunkEYXV5OMYcjs = require('../chunk-EYXV5OMY.cjs');var _clsx = require('clsx');var _tailwindmerge = require('tailwind-merge');function s(...e){return _tailwindmerge.twMerge.call(void 0, _clsx.clsx.call(void 0, e))}var _jsxruntime = require('react/jsx-runtime');function ae(e){return typeof e!="number"?e:new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",maximumFractionDigits:0}).format(e)}function w({item:e,selected:o,onSelect:t}){let r=_nullishCoalesce(e, () => ({})),l=s("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",o&&"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:ae(r.price)})]})]});return t?_jsxruntime.jsx.call(void 0, "button",{type:"button",onClick:t,"aria-pressed":_nullishCoalesce(o, () => (!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((o,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 o=_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(o.price, () => (""))})}function F({entity:e,onClose:o}){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:o,"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:o}){let t=o!=null&&o!==e?`${e} of ${o} 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:o,placeholder:t}){return _jsxruntime.jsx.call(void 0, "input",{type:"search",value:e,placeholder:t,onChange:r=>o(r.target.value),"aria-label":_nullishCoalesce(t, () => ("Search")),className:s("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 ge({children:e}){return _jsxruntime.jsx.call(void 0, _chunkEYXV5OMYcjs.p,{...g,children:e})}var _react = require('react');var Le=_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:o,toolbarEnd:t,autoFetch:r=!0,mapCenter:l,mapZoom:a}){let p=_chunkEYXV5OMYcjs.s.call(void 0, ),{Search:u}=_chunkEYXV5OMYcjs.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:s("flex h-full min-h-0 w-full flex-col bg-background text-foreground",o),"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, _chunkEYXV5OMYcjs.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, _chunkEYXV5OMYcjs.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:s("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:s("min-h-0 overflow-y-auto p-3",h),"data-slot":"listing-layout-list",children:[_jsxruntime.jsx.call(void 0, _chunkEYXV5OMYcjs.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, _chunkEYXV5OMYcjs.z,{})})]}),_jsxruntime.jsx.call(void 0, "div",{className:s("min-h-0 md:sticky md:top-0",P),"data-slot":"listing-layout-map",children:_jsxruntime.jsx.call(void 0, _chunkEYXV5OMYcjs.y,{center:l,zoom:a,fallback:Le})})]})]})}function ee(e){return"provider"in e}function Pe(e){let o=e!=null&&!ee(e),t=o?e.apiKey:void 0,r=o?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 Ne(e){let{datasets:o,filters:t,map:r,components:l,urlSync:a,initialFilters:p,config:u,className:f,search:y,autoFetch:h}=e,{ready:P,provider:d}=Pe(r);if(!P)return null;let n=[];for(let oe of o)n.push(_chunkEYXV5OMYcjs.k.call(void 0, oe));t&&n.push(_chunkEYXV5OMYcjs.l.call(void 0, t)),d&&n.push(_chunkEYXV5OMYcjs.j.call(void 0, d)),a instanceof _chunk2VUHLHHXcjs.a&&n.push(_chunkEYXV5OMYcjs.m.call(void 0, a)),p&&n.push(_chunkEYXV5OMYcjs.n.call(void 0, p)),u&&n.push(_chunkEYXV5OMYcjs.i.call(void 0, u));let te=_chunkEYXV5OMYcjs.h.call(void 0, ...n),re={...g,...l};return _jsxruntime.jsx.call(void 0, _chunkEYXV5OMYcjs.B,{...te,children:_jsxruntime.jsx.call(void 0, _chunkEYXV5OMYcjs.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 = Ne; exports.ListingComponentsProviderWithDefaults = ge; exports.ListingLayout = E; exports.cn = s; exports.shadcnDefaultComponents = g;