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.
- package/README.md +33 -41
- package/dist/chunk-4CZJODMK.cjs +2 -0
- package/dist/chunk-GSWGCJCO.js +2 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +93 -11
- package/dist/index.d.ts +93 -11
- package/dist/index.js +1 -1
- package/dist/listing-app-BtNNEkX1.d.cts +347 -0
- package/dist/listing-app-IG1awue3.d.ts +347 -0
- package/dist/styled/index.cjs +1 -1
- package/dist/styled/index.d.cts +6 -7
- package/dist/styled/index.d.ts +6 -7
- package/dist/styled/index.js +1 -1
- package/dist/styles.css +31 -99
- package/package.json +12 -27
- package/dist/chunk-2VUHLHHX.cjs +0 -2
- package/dist/chunk-35KYEBAC.js +0 -2
- package/dist/chunk-CGT3RHJ7.js +0 -2
- package/dist/chunk-EYXV5OMY.cjs +0 -2
- package/dist/chunk-R3FX2V3N.js +0 -2
- package/dist/chunk-RXHR66FS.cjs +0 -2
- package/dist/components-provider-FinfaqUT.d.cts +0 -131
- package/dist/components-provider-cWVWEDXC.d.ts +0 -131
- package/dist/listing-app-BMoMKD4c.d.cts +0 -213
- package/dist/listing-app-BSJKGh7k.d.ts +0 -213
- package/dist/shadcn/index.cjs +0 -2
- package/dist/shadcn/index.d.cts +0 -259
- package/dist/shadcn/index.d.ts +0 -259
- package/dist/shadcn/index.js +0 -2
- package/dist/url-sync.controller-DK5W_0OZ.d.ts +0 -89
- package/dist/url-sync.controller-DXSIWgTa.d.cts +0 -89
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { b as EntityAdapter, M as MapPoint, L as LatLng, a as MapProvider } from './map-provider.interface-DT-v1plm.js';
|
|
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
|
+
declare enum PaginationMode {
|
|
38
|
+
Paged = "paged",
|
|
39
|
+
Infinite = "infinite"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface IListingConfigOptions {
|
|
43
|
+
pagination: PaginationMode;
|
|
44
|
+
pageSize: number;
|
|
45
|
+
debounceMs: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Framework-free registry for `FilterDefinition`s: programmatic add / remove /
|
|
50
|
+
* reorder / replace, plus folding raw control values into `TFilters`
|
|
51
|
+
* (`toFilters`), reporting which registered filters currently affect an
|
|
52
|
+
* applied `TFilters` (`activeKeys`), and building the reset-everything patch
|
|
53
|
+
* (`clearedParams`).
|
|
54
|
+
*
|
|
55
|
+
* Defs are stored by VALUE (shallow-cloned on `add`/`replace`), never by the
|
|
56
|
+
* caller's original reference — `reorder()` rewrites `order` in place on the
|
|
57
|
+
* registry's own copy. This is deliberate: the same `FilterDefinition` object
|
|
58
|
+
* can legitimately be registered in more than one `FilterRegistry` instance
|
|
59
|
+
* (e.g. two listing pages sharing a base filter set), and without cloning,
|
|
60
|
+
* one registry's `reorder()` would silently mutate `.order` on the shared
|
|
61
|
+
* object and bleed into every other registry (and the caller) holding it.
|
|
62
|
+
*/
|
|
63
|
+
declare class FilterRegistry<TFilters> {
|
|
64
|
+
private readonly defs;
|
|
65
|
+
add<TValue = unknown>(def: FilterDefinition<TFilters, TValue>): this;
|
|
66
|
+
remove(key: string): this;
|
|
67
|
+
replace<TValue = unknown>(key: string, def: FilterDefinition<TFilters, TValue>): this;
|
|
68
|
+
reorder(keys: string[]): this;
|
|
69
|
+
list(): FilterDefinition<TFilters>[];
|
|
70
|
+
has(key: string): boolean;
|
|
71
|
+
toFilters(values: Record<string, unknown>): TFilters;
|
|
72
|
+
activeKeys(filters: TFilters): string[];
|
|
73
|
+
clearedParams(): Partial<TFilters>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface IListingCardProps {
|
|
77
|
+
item: unknown;
|
|
78
|
+
selected?: boolean;
|
|
79
|
+
onSelect?: () => void;
|
|
80
|
+
}
|
|
81
|
+
interface IListingMarkerProps {
|
|
82
|
+
point: MapPoint<unknown>;
|
|
83
|
+
}
|
|
84
|
+
interface IListingPopupProps {
|
|
85
|
+
entity: unknown;
|
|
86
|
+
onClose?: () => void;
|
|
87
|
+
}
|
|
88
|
+
interface IListingSidebarProps {
|
|
89
|
+
children?: ReactNode;
|
|
90
|
+
}
|
|
91
|
+
interface IListingFilterPanelProps {
|
|
92
|
+
children?: ReactNode;
|
|
93
|
+
}
|
|
94
|
+
interface IListingSearchProps {
|
|
95
|
+
value: string;
|
|
96
|
+
onChange(value: string): void;
|
|
97
|
+
placeholder?: string;
|
|
98
|
+
}
|
|
99
|
+
interface IListingEmptyProps {
|
|
100
|
+
}
|
|
101
|
+
interface IListingLoadingProps {
|
|
102
|
+
}
|
|
103
|
+
interface IListingResultHeaderProps {
|
|
104
|
+
count: number;
|
|
105
|
+
total?: number;
|
|
106
|
+
}
|
|
107
|
+
interface IListingToolbarProps {
|
|
108
|
+
children?: ReactNode;
|
|
109
|
+
}
|
|
110
|
+
interface IListingComponents {
|
|
111
|
+
Card: ComponentType<IListingCardProps>;
|
|
112
|
+
Marker: ComponentType<IListingMarkerProps>;
|
|
113
|
+
Popup: ComponentType<IListingPopupProps>;
|
|
114
|
+
Sidebar: ComponentType<IListingSidebarProps>;
|
|
115
|
+
FilterPanel: ComponentType<IListingFilterPanelProps>;
|
|
116
|
+
Search: ComponentType<IListingSearchProps>;
|
|
117
|
+
Empty: ComponentType<IListingEmptyProps>;
|
|
118
|
+
Loading: ComponentType<IListingLoadingProps>;
|
|
119
|
+
ResultHeader: ComponentType<IListingResultHeaderProps>;
|
|
120
|
+
Toolbar: ComponentType<IListingToolbarProps>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Injection point for custom-component overrides. Every slot is optional —
|
|
124
|
+
* anything not provided falls back to the (unstyled) default. Mirrors
|
|
125
|
+
* `react-wizard-engine`'s `WizardComponentsProvider`: explicit per-slot
|
|
126
|
+
* `provided ?? defaults.X` merge, not a generic/reflective loop.
|
|
127
|
+
*/
|
|
128
|
+
declare function ListingComponentsProvider(props: Partial<IListingComponents> & {
|
|
129
|
+
children: ReactNode;
|
|
130
|
+
}): react.JSX.Element;
|
|
131
|
+
declare function useListingComponents(): IListingComponents;
|
|
132
|
+
|
|
133
|
+
type BottomNavView = 'list' | 'map';
|
|
134
|
+
/**
|
|
135
|
+
* An action button in the mobile chrome (e.g. "Add"/"Save"). Despite the
|
|
136
|
+
* name (kept for API compatibility), it renders in `MobileHeader`, not here.
|
|
137
|
+
*/
|
|
138
|
+
interface IBottomNavAction {
|
|
139
|
+
label: string;
|
|
140
|
+
icon?: ReactNode;
|
|
141
|
+
onClick(): void;
|
|
142
|
+
}
|
|
143
|
+
interface IBottomNavProps {
|
|
144
|
+
view: BottomNavView;
|
|
145
|
+
onViewChange(view: BottomNavView): void;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Mobile-only bottom navigation (`.rle-bottom-nav`, CSS-hidden above the
|
|
149
|
+
* mobile breakpoint -- see `styles.css`'s layout-shell section, the single
|
|
150
|
+
* source of truth for it): a floating **List | Map** segmented toggle
|
|
151
|
+
* (`.rle-viewtoggle`) that drives which of `StyledListingLayout`'s two
|
|
152
|
+
* full-area panels is visible. The **Filters** button and the optional caller
|
|
153
|
+
* action (e.g. "Save") live in the mobile header (`MobileHeader`) instead, so
|
|
154
|
+
* the footer pill carries the view toggle alone. No lucide/icon library
|
|
155
|
+
* dependency -- every icon here is a small inline SVG, matching the rest of
|
|
156
|
+
* `/styled`'s zero-extra-dependency policy.
|
|
157
|
+
*/
|
|
158
|
+
declare function BottomNav({ view, onViewChange }: IBottomNavProps): react.JSX.Element;
|
|
159
|
+
|
|
160
|
+
interface IStyledListingLayoutProps {
|
|
161
|
+
/**
|
|
162
|
+
* Optional header search box, LIBRARY-wired: name the `TFilters` field it
|
|
163
|
+
* drives via
|
|
164
|
+
* `filterKey`, and the layout reads the current value from the engine's
|
|
165
|
+
* filters and writes edits back with `applyFilters({ [filterKey]: value ||
|
|
166
|
+
* undefined })`. Rendered in BOTH the desktop filter bar and the mobile
|
|
167
|
+
* header. Do NOT also register `filterKey` as a `ListingFilters` control --
|
|
168
|
+
* it would then render twice. Omitted entirely when not passed.
|
|
169
|
+
*/
|
|
170
|
+
search?: {
|
|
171
|
+
filterKey: string;
|
|
172
|
+
placeholder?: string;
|
|
173
|
+
};
|
|
174
|
+
/** Extra content rendered in `.rle-list-header` (above the list), to the right of `ListingResultHeader` (e.g. a sort control + save-search). */
|
|
175
|
+
toolbarEnd?: ReactNode;
|
|
176
|
+
/** Optional mobile-header action button (e.g. "Save"), forwarded verbatim to `<MobileHeader action={...} />`. Omit to render just the search + Filters button there. */
|
|
177
|
+
mobileAction?: IBottomNavAction;
|
|
178
|
+
/**
|
|
179
|
+
* Whether a map is configured. When `false`, the map region is dropped and
|
|
180
|
+
* the results list fills the full width as a multi-column grid, and the
|
|
181
|
+
* mobile List|Map toggle is omitted (nothing to toggle to). Defaults to
|
|
182
|
+
* `engine.map != null` -- the engine is the source of truth, so this only
|
|
183
|
+
* needs passing to override that (e.g. to hide a configured map).
|
|
184
|
+
*/
|
|
185
|
+
hasMap?: boolean;
|
|
186
|
+
/** Whether the layout fetches the first page itself on mount (`engine.applyFilters({})`). Defaults to `true`. */
|
|
187
|
+
autoFetch?: boolean;
|
|
188
|
+
/** Forwarded verbatim to `<ListingMap center={mapCenter} />` -- see that component's "Auto-fit" doc comment. */
|
|
189
|
+
mapCenter?: LatLng;
|
|
190
|
+
/** Forwarded verbatim to `<ListingMap zoom={mapZoom} />`. */
|
|
191
|
+
mapZoom?: number;
|
|
192
|
+
className?: string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Full, responsive, Tailwind-free listing experience -- built from the
|
|
196
|
+
* structure-only compound components (`~/react`) plus the injected
|
|
197
|
+
* `Styled*` slot components, with a rich mobile experience: a bottom
|
|
198
|
+
* nav (`BottomNav`) and a bottom sheet (`BottomSheet`) for filters. Every
|
|
199
|
+
* class used here is one of
|
|
200
|
+
* the `.rle-*` layout classes added to `styles.css` alongside this file --
|
|
201
|
+
* no Tailwind, no inline styles, no other stylesheet required.
|
|
202
|
+
*
|
|
203
|
+
* STRUCTURE (`.rle-app`) -- the mobile/desktop breakpoint and the grid's
|
|
204
|
+
* column sizing live in ONE place, `styles.css`'s layout-shell section; the
|
|
205
|
+
* bullets below say only which side of it each piece renders on:
|
|
206
|
+
* - `.rle-filter-bar` (desktop only, CSS-hidden below the breakpoint): the
|
|
207
|
+
* optional `Search` slot and `<ListingFilters>` laid out as a single
|
|
208
|
+
* horizontally-scrolling row (`className="rle-filters-row"`,
|
|
209
|
+
* `groupClassName="rle-filter-group"`) with edge fades. The result header +
|
|
210
|
+
* `toolbarEnd` are NOT here -- they sit in `.rle-list-header` above the list.
|
|
211
|
+
* - `.rle-list-header` (top of `.rle-list`): `<ListingResultHeader>` (title +
|
|
212
|
+
* count) at the left, `toolbarEnd` (sort control, save-search, ...) at the
|
|
213
|
+
* right -- a heading for the results, on every viewport.
|
|
214
|
+
* - `.rle-body.rle-split`: a list-majority list|map CSS grid from the
|
|
215
|
+
* breakpoint up; below it, a single full-area panel with exactly one of
|
|
216
|
+
* `.rle-list`/`.rle-map` visible at a time via `data-mobile-view` (see
|
|
217
|
+
* `styles.css`'s mobile media query). Both regions stay mounted at ALL
|
|
218
|
+
* times regardless of viewport/toggle state -- only their visibility flips
|
|
219
|
+
* -- so neither `ListingList` nor `ListingMap` remounts (and re-triggers
|
|
220
|
+
* its own mount effects) on toggle or resize.
|
|
221
|
+
* - `<MobileHeader>` (mobile only): the same search box, a **Filters** button
|
|
222
|
+
* (opens the `<BottomSheet>`, with an applied-filter count badge) and the
|
|
223
|
+
* optional `mobileAction`.
|
|
224
|
+
* - `<BottomNav>` (mobile only): the floating List|Map view toggle, wired to
|
|
225
|
+
* the same `mobileView` state as the CSS toggle. Omitted when there is no
|
|
226
|
+
* map (nothing to toggle to).
|
|
227
|
+
* - `<BottomSheet title="Filters">`: the SAME `<ListingFilters>` component,
|
|
228
|
+
* stacked vertically (`className="rle-filter-stack"`) for the sheet's
|
|
229
|
+
* narrower body, plus a footer with "Clear all" (applies
|
|
230
|
+
* `engine.filters.clearedParams()` -- see that method's doc for why a reset
|
|
231
|
+
* round-trips each def's to/fromParams) and "Show N results" (just closes
|
|
232
|
+
* the sheet -- every filter control already applies live via its own
|
|
233
|
+
* `onChange`, so there is nothing left to commit).
|
|
234
|
+
* - Fetches the first page itself on mount (`engine.applyFilters({})`) by
|
|
235
|
+
* default -- pass `autoFetch={false}`
|
|
236
|
+
* to opt out and drive the first fetch yourself.
|
|
237
|
+
*/
|
|
238
|
+
declare function StyledListingLayout({ search, toolbarEnd, mobileAction, autoFetch, hasMap: hasMapProp, mapCenter, mapZoom, className, }: IStyledListingLayoutProps): react.JSX.Element;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Two-shape `map` prop: pass a ready `MapProvider`, or the `{ apiKey, mapId? }`
|
|
242
|
+
* shorthand. The shorthand is resolved via a DYNAMIC `import()` of
|
|
243
|
+
* `~/maps/google` (see `useResolvedMap` below) rather than a static one, so an
|
|
244
|
+
* app that never sets `map` doesn't pull `@googlemaps/js-api-loader` into its
|
|
245
|
+
* bundle.
|
|
246
|
+
*/
|
|
247
|
+
type ListingAppMapProp = {
|
|
248
|
+
provider: MapProvider;
|
|
249
|
+
center?: LatLng;
|
|
250
|
+
zoom?: number;
|
|
251
|
+
} | {
|
|
252
|
+
apiKey: string;
|
|
253
|
+
mapId?: string;
|
|
254
|
+
center?: LatLng;
|
|
255
|
+
zoom?: number;
|
|
256
|
+
};
|
|
257
|
+
interface ListingAppProps<TFilters> {
|
|
258
|
+
/**
|
|
259
|
+
* One or more marker-layer datasets; the FIRST entry is the primary
|
|
260
|
+
* dataset (drives the results list + pagination) -- same "insertion
|
|
261
|
+
* order" rule `composeListingProviders`/`withDataset` already follow.
|
|
262
|
+
* Entity-erased (`any`, not `TEntity`) for the same reason
|
|
263
|
+
* `DatasetRegistry<unknown, TFilters>` is: one array can legitimately hold
|
|
264
|
+
* heterogeneous layers (a properties dataset and a businesses dataset have
|
|
265
|
+
* different entity types) -- see `compose-listing-providers.ts`'s
|
|
266
|
+
* `withDataset` doc comment.
|
|
267
|
+
*/
|
|
268
|
+
datasets: DatasetDefinition<any, TFilters>[];
|
|
269
|
+
/** A `(reg) => { reg.add(...); }` callback that registers your filters -- forwarded verbatim to `withFilters`. */
|
|
270
|
+
filters?: (reg: FilterRegistry<TFilters>) => void;
|
|
271
|
+
/** A ready `MapProvider`, or `{ apiKey, mapId? }` to build a `googleProvider` internally. Omit for no map -- the layout drops the map region and renders the list full-width. */
|
|
272
|
+
map?: ListingAppMapProp;
|
|
273
|
+
/** Slot overrides, merged OVER the `/styled` defaults -- see this file's doc comment for how the merge works. */
|
|
274
|
+
components?: Partial<IListingComponents>;
|
|
275
|
+
/**
|
|
276
|
+
* Hydrates the engine's initial filters on mount -- typically parsed from
|
|
277
|
+
* the CONSUMER's own URL (your own query-string parser over `new
|
|
278
|
+
* URLSearchParams(window.location.search)`), but any source works. Half
|
|
279
|
+
* of the event-based URL API this component exposes: `ListingApp` never
|
|
280
|
+
* reads `window.location` itself, it only accepts filters as a prop.
|
|
281
|
+
*/
|
|
282
|
+
initialFilters?: TFilters;
|
|
283
|
+
/**
|
|
284
|
+
* EVENT OUT: fires with the engine's current filters (`TFilters`, not the
|
|
285
|
+
* store's `DeepReadonly` wrapper) every time `ListingEventType.FiltersChanged`
|
|
286
|
+
* fires on the underlying engine -- i.e. on every `engine.applyFilters(...)`
|
|
287
|
+
* call, INCLUDING the one `StyledListingLayout` makes on mount when
|
|
288
|
+
* `autoFetch` is on (a `{}` patch that still round-trips through
|
|
289
|
+
* `currentFilters()` and re-emits the very `initialFilters` this component
|
|
290
|
+
* was given). That first call is intentionally NOT suppressed: a
|
|
291
|
+
* consumer's handler is expected to write filters back to a URL via
|
|
292
|
+
* `history.replaceState` (or a router's equivalent), and replacing the URL
|
|
293
|
+
* with the SAME query string it already hydrated from is a no-op, not a
|
|
294
|
+
* visible echo -- so "just always emit" is simpler than tracking a
|
|
295
|
+
* first-call flag for no behavioral gain. This is the OTHER half of the
|
|
296
|
+
* event-based URL API: `ListingApp` (and the engine underneath it) never
|
|
297
|
+
* touches `window.history` -- the CONSUMER owns routing, driven by this
|
|
298
|
+
* callback plus `initialFilters` above. `UrlSyncController`/`BrowserHistoryPort`
|
|
299
|
+
* (`~/core`, `~/react`) remain exported as optional, lower-level helpers
|
|
300
|
+
* for consumers who want the library to own `window.history` directly, but
|
|
301
|
+
* they are no longer the primary/recommended path for new integrations.
|
|
302
|
+
*/
|
|
303
|
+
onFiltersChange?: (filters: TFilters) => void;
|
|
304
|
+
/** The mobile header action (e.g. "Save"/"Add") -- forwarded verbatim to `StyledListingLayout`/`MobileHeader`. */
|
|
305
|
+
mobileAction?: IBottomNavAction;
|
|
306
|
+
search?: IStyledListingLayoutProps['search'];
|
|
307
|
+
toolbarEnd?: IStyledListingLayoutProps['toolbarEnd'];
|
|
308
|
+
config?: Partial<IListingConfigOptions>;
|
|
309
|
+
autoFetch?: boolean;
|
|
310
|
+
className?: string;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Turnkey, batteries-included, Tailwind-free entry point -- and (per
|
|
314
|
+
* `src/index.ts`) the package's MAIN-ENTRY default: `import { ListingApp } from
|
|
315
|
+
* 'react-listing-engine'` gives you this component. Pass datasets + filters +
|
|
316
|
+
* a map + component overrides, and `ListingApp` composes
|
|
317
|
+
* `composeListingProviders(...)`, `<ListingProvider>`, the `/styled` defaults,
|
|
318
|
+
* and `<StyledListingLayout>` for you.
|
|
319
|
+
*
|
|
320
|
+
* `TEntity` still can't be inferred from these props (`datasets` narrows it,
|
|
321
|
+
* but a multi-dataset array widens back to the union/`unknown` in practice)
|
|
322
|
+
* -- annotate the call site when entity-level typing matters, exactly like
|
|
323
|
+
* `<ListingProvider<TEntity, TFilters>>` itself.
|
|
324
|
+
*
|
|
325
|
+
* COMPONENTS MERGE: `components` overrides are applied ON TOP of the
|
|
326
|
+
* `/styled` defaults via ONE explicit `{ ...styledDefaultComponents,
|
|
327
|
+
* ...components }` object, passed to a single `<ListingComponentsProvider>`
|
|
328
|
+
* -- never as a nested `<ListingComponentsProvider>` inside
|
|
329
|
+
* `StyledComponentsProviderWithDefaults`. `ListingComponentsProvider` merges
|
|
330
|
+
* `provided ?? ITS OWN private, unstyled fallbacks` per slot (it does not
|
|
331
|
+
* read the parent context -- see `src/react/components-provider.tsx`), so
|
|
332
|
+
* nesting would silently discard every un-overridden `/styled` default
|
|
333
|
+
* instead of keeping it.
|
|
334
|
+
*
|
|
335
|
+
* URL SYNC IS EVENT-BASED, NOT INTERNAL: this component takes no `urlSync`
|
|
336
|
+
* prop and never constructs/starts a
|
|
337
|
+
* `UrlSyncController` -- it never touches `window.history`. Instead it
|
|
338
|
+
* accepts `initialFilters` (hydrate FROM the consumer's URL) and emits
|
|
339
|
+
* `onFiltersChange` (write TO the consumer's URL/router) -- see both props'
|
|
340
|
+
* doc comments for the full contract. This keeps the library's surface
|
|
341
|
+
* router-agnostic (a plain `history.replaceState`, a Next.js `router.replace`,
|
|
342
|
+
* a React Router `setSearchParams`, etc. all work identically from the
|
|
343
|
+
* consumer's side) instead of assuming `window.history` is the right target.
|
|
344
|
+
*/
|
|
345
|
+
declare function ListingApp<TEntity, TFilters>(props: ListingAppProps<TFilters>): react.JSX.Element | null;
|
|
346
|
+
|
|
347
|
+
export { BottomNav as B, type ClusterOptions as C, type DatasetDefinition as D, FilterRegistry as F, type IListingConfigOptions as I, ListingApp as L, type MarkerRenderer as M, PaginationMode as P, StyledListingLayout as S, type IListingToolbarProps as a, type FilterControlProps as b, type FilterDefinition as c, type IListingCardProps as d, type IListingComponents as e, type IListingEmptyProps as f, type IListingFilterPanelProps as g, type IListingLoadingProps as h, type IListingMarkerProps as i, type IListingPopupProps as j, type IListingResultHeaderProps as k, type IListingSearchProps as l, type IListingSidebarProps as m, type ListingAppProps as n, ListingComponentsProvider as o, type BottomNavView as p, type IBottomNavAction as q, type IBottomNavProps as r, type IStyledListingLayoutProps as s, type ListingAppMapProp as t, useListingComponents as u };
|
package/dist/styled/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
|
|
2
|
-
var
|
|
2
|
+
var _chunk4CZJODMKcjs = require('../chunk-4CZJODMK.cjs');var _jsxruntime = require('react/jsx-runtime');function L({children:o}){return _jsxruntime.jsx.call(void 0, _chunk4CZJODMKcjs.p,{..._chunk4CZJODMKcjs.N,children:o})}exports.BottomNav = _chunk4CZJODMKcjs.O; exports.BottomSheet = _chunk4CZJODMKcjs.P; exports.ListingApp = _chunk4CZJODMKcjs.R; exports.StyledCard = _chunk4CZJODMKcjs.D; exports.StyledComponentsProviderWithDefaults = L; exports.StyledEmpty = _chunk4CZJODMKcjs.E; exports.StyledFilterPanel = _chunk4CZJODMKcjs.F; exports.StyledListingLayout = _chunk4CZJODMKcjs.Q; exports.StyledLoading = _chunk4CZJODMKcjs.G; exports.StyledMarker = _chunk4CZJODMKcjs.H; exports.StyledPopup = _chunk4CZJODMKcjs.I; exports.StyledResultHeader = _chunk4CZJODMKcjs.J; exports.StyledSearch = _chunk4CZJODMKcjs.K; exports.StyledSidebar = _chunk4CZJODMKcjs.L; exports.StyledToolbar = _chunk4CZJODMKcjs.M; exports.styledDefaultComponents = _chunk4CZJODMKcjs.N;
|
package/dist/styled/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { d as IListingCardProps, f as IListingEmptyProps, g as IListingFilterPanelProps, h as IListingLoadingProps, i as IListingMarkerProps, j as IListingPopupProps, k as IListingResultHeaderProps, l as IListingSearchProps, m as IListingSidebarProps, a as IListingToolbarProps, e as IListingComponents } from '../
|
|
4
|
-
export { B as BottomNav,
|
|
3
|
+
import { d as IListingCardProps, f as IListingEmptyProps, g as IListingFilterPanelProps, h as IListingLoadingProps, i as IListingMarkerProps, j as IListingPopupProps, k as IListingResultHeaderProps, l as IListingSearchProps, m as IListingSidebarProps, a as IListingToolbarProps, e as IListingComponents } from '../listing-app-BtNNEkX1.cjs';
|
|
4
|
+
export { B as BottomNav, p as BottomNavView, q as IBottomNavAction, r as IBottomNavProps, s as IStyledListingLayoutProps, L as ListingApp, t as ListingAppMapProp, n as ListingAppProps, S as StyledListingLayout } from '../listing-app-BtNNEkX1.cjs';
|
|
5
5
|
import '../map-provider.interface-DT-v1plm.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -33,7 +33,7 @@ declare function StyledMarker({ point }: IListingMarkerProps): react.JSX.Element
|
|
|
33
33
|
* accessible close button. `role="group"` + `aria-label` (rather than
|
|
34
34
|
* `role="dialog"`) since this is a non-modal, non-focus-trapped popup
|
|
35
35
|
* anchored to a map marker -- `dialog` without modality/focus management
|
|
36
|
-
* would misrepresent it to AT users.
|
|
36
|
+
* would misrepresent it to AT users.
|
|
37
37
|
*/
|
|
38
38
|
declare function StyledPopup({ entity, onClose }: IListingPopupProps): react.JSX.Element;
|
|
39
39
|
|
|
@@ -56,7 +56,7 @@ declare function StyledToolbar({ children }: IListingToolbarProps): react.JSX.El
|
|
|
56
56
|
* of the box -- pair it with `import 'react-listing-engine/styles.css'`; use
|
|
57
57
|
* `ListingComponentsProvider` directly (with your own components for some or
|
|
58
58
|
* all slots) otherwise -- the two compose fine since `ListingComponentsProvider`
|
|
59
|
-
* falls back per-slot.
|
|
59
|
+
* falls back per-slot.
|
|
60
60
|
*/
|
|
61
61
|
declare function StyledComponentsProviderWithDefaults({ children }: {
|
|
62
62
|
children: ReactNode;
|
|
@@ -64,8 +64,7 @@ declare function StyledComponentsProviderWithDefaults({ children }: {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Every `/styled` default, keyed by slot -- the single source of truth for
|
|
67
|
-
* `StyledComponentsProviderWithDefaults
|
|
68
|
-
* `shadcnDefaultComponents`.
|
|
67
|
+
* `StyledComponentsProviderWithDefaults`.
|
|
69
68
|
*
|
|
70
69
|
* Kept as a plain object, not JSX, so a caller can merge it with
|
|
71
70
|
* `{ ...styledDefaultComponents, ...overrides }` and hand the result to ONE
|
|
@@ -89,7 +88,7 @@ interface IBottomSheetProps {
|
|
|
89
88
|
/**
|
|
90
89
|
* Dependency-free mobile bottom sheet -- the `/styled` adapter's own modal
|
|
91
90
|
* primitive, used by `StyledListingLayout` for the mobile filters panel.
|
|
92
|
-
* Deliberately
|
|
91
|
+
* Deliberately dependency-free (no Radix or other UI-library sheet): `/styled`
|
|
93
92
|
* ships zero UI dependencies beyond `react`/`react-dom`, so this is a small
|
|
94
93
|
* hand-rolled implementation covering just what a filters sheet needs --
|
|
95
94
|
* portal, backdrop-click/Escape-to-close, a body-scroll lock, and the
|
package/dist/styled/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { d as IListingCardProps, f as IListingEmptyProps, g as IListingFilterPanelProps, h as IListingLoadingProps, i as IListingMarkerProps, j as IListingPopupProps, k as IListingResultHeaderProps, l as IListingSearchProps, m as IListingSidebarProps, a as IListingToolbarProps, e as IListingComponents } from '../
|
|
4
|
-
export { B as BottomNav,
|
|
3
|
+
import { d as IListingCardProps, f as IListingEmptyProps, g as IListingFilterPanelProps, h as IListingLoadingProps, i as IListingMarkerProps, j as IListingPopupProps, k as IListingResultHeaderProps, l as IListingSearchProps, m as IListingSidebarProps, a as IListingToolbarProps, e as IListingComponents } from '../listing-app-IG1awue3.js';
|
|
4
|
+
export { B as BottomNav, p as BottomNavView, q as IBottomNavAction, r as IBottomNavProps, s as IStyledListingLayoutProps, L as ListingApp, t as ListingAppMapProp, n as ListingAppProps, S as StyledListingLayout } from '../listing-app-IG1awue3.js';
|
|
5
5
|
import '../map-provider.interface-DT-v1plm.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -33,7 +33,7 @@ declare function StyledMarker({ point }: IListingMarkerProps): react.JSX.Element
|
|
|
33
33
|
* accessible close button. `role="group"` + `aria-label` (rather than
|
|
34
34
|
* `role="dialog"`) since this is a non-modal, non-focus-trapped popup
|
|
35
35
|
* anchored to a map marker -- `dialog` without modality/focus management
|
|
36
|
-
* would misrepresent it to AT users.
|
|
36
|
+
* would misrepresent it to AT users.
|
|
37
37
|
*/
|
|
38
38
|
declare function StyledPopup({ entity, onClose }: IListingPopupProps): react.JSX.Element;
|
|
39
39
|
|
|
@@ -56,7 +56,7 @@ declare function StyledToolbar({ children }: IListingToolbarProps): react.JSX.El
|
|
|
56
56
|
* of the box -- pair it with `import 'react-listing-engine/styles.css'`; use
|
|
57
57
|
* `ListingComponentsProvider` directly (with your own components for some or
|
|
58
58
|
* all slots) otherwise -- the two compose fine since `ListingComponentsProvider`
|
|
59
|
-
* falls back per-slot.
|
|
59
|
+
* falls back per-slot.
|
|
60
60
|
*/
|
|
61
61
|
declare function StyledComponentsProviderWithDefaults({ children }: {
|
|
62
62
|
children: ReactNode;
|
|
@@ -64,8 +64,7 @@ declare function StyledComponentsProviderWithDefaults({ children }: {
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Every `/styled` default, keyed by slot -- the single source of truth for
|
|
67
|
-
* `StyledComponentsProviderWithDefaults
|
|
68
|
-
* `shadcnDefaultComponents`.
|
|
67
|
+
* `StyledComponentsProviderWithDefaults`.
|
|
69
68
|
*
|
|
70
69
|
* Kept as a plain object, not JSX, so a caller can merge it with
|
|
71
70
|
* `{ ...styledDefaultComponents, ...overrides }` and hand the result to ONE
|
|
@@ -89,7 +88,7 @@ interface IBottomSheetProps {
|
|
|
89
88
|
/**
|
|
90
89
|
* Dependency-free mobile bottom sheet -- the `/styled` adapter's own modal
|
|
91
90
|
* primitive, used by `StyledListingLayout` for the mobile filters panel.
|
|
92
|
-
* Deliberately
|
|
91
|
+
* Deliberately dependency-free (no Radix or other UI-library sheet): `/styled`
|
|
93
92
|
* ships zero UI dependencies beyond `react`/`react-dom`, so this is a small
|
|
94
93
|
* hand-rolled implementation covering just what a filters sheet needs --
|
|
95
94
|
* portal, backdrop-click/Escape-to-close, a body-scroll lock, and the
|
package/dist/styled/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import{
|
|
2
|
+
import{D as r,E as p,F as i,G as d,H as y,I as l,J as n,K as m,L as s,M as a,N as t,O as S,P as f,Q as P,R as u,p as e}from"../chunk-GSWGCJCO.js";import{jsx as g}from"react/jsx-runtime";function L({children:o}){return g(e,{...t,children:o})}export{S as BottomNav,f as BottomSheet,u as ListingApp,r as StyledCard,L as StyledComponentsProviderWithDefaults,p as StyledEmpty,i as StyledFilterPanel,P as StyledListingLayout,d as StyledLoading,y as StyledMarker,l as StyledPopup,n as StyledResultHeader,m as StyledSearch,s as StyledSidebar,a as StyledToolbar,t as styledDefaultComponents};
|
package/dist/styles.css
CHANGED
|
@@ -70,33 +70,14 @@
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/* -----------------------------------------------------------------------
|
|
73
|
-
* Base -- box-sizing + font-family for every element this stylesheet owns
|
|
73
|
+
* Base -- box-sizing + font-family for every element this stylesheet owns,
|
|
74
|
+
* scoped by the `rle-` class prefix (the adapter's namespace) instead of an
|
|
75
|
+
* enumerated class list, so new components are covered automatically --
|
|
76
|
+
* including elements portaled outside `.rle-app` (sheet, backdrop).
|
|
74
77
|
* --------------------------------------------------------------------- */
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.rle-card-body,
|
|
79
|
-
.rle-marker,
|
|
80
|
-
.rle-pin,
|
|
81
|
-
.rle-popup,
|
|
82
|
-
.rle-popup-close,
|
|
83
|
-
.rle-empty,
|
|
84
|
-
.rle-empty-icon,
|
|
85
|
-
.rle-loading,
|
|
86
|
-
.rle-loading-item,
|
|
87
|
-
.rle-skeleton,
|
|
88
|
-
.rle-result-header,
|
|
89
|
-
.rle-toolbar,
|
|
90
|
-
.rle-sidebar,
|
|
91
|
-
.rle-filter-panel,
|
|
92
|
-
.rle-input,
|
|
93
|
-
.rle-range,
|
|
94
|
-
.rle-select,
|
|
95
|
-
.rle-dropdown,
|
|
96
|
-
.rle-dropdown-panel,
|
|
97
|
-
.rle-checkbox,
|
|
98
|
-
.rle-label,
|
|
99
|
-
.rle-btn {
|
|
79
|
+
[class^='rle-'],
|
|
80
|
+
[class*=' rle-'] {
|
|
100
81
|
box-sizing: border-box;
|
|
101
82
|
font-family: var(--rle-font-sans);
|
|
102
83
|
}
|
|
@@ -576,26 +557,6 @@
|
|
|
576
557
|
font-size: var(--rle-fs-sm);
|
|
577
558
|
}
|
|
578
559
|
|
|
579
|
-
/* -----------------------------------------------------------------------
|
|
580
|
-
* Motion
|
|
581
|
-
* --------------------------------------------------------------------- */
|
|
582
|
-
|
|
583
|
-
@media (prefers-reduced-motion: reduce) {
|
|
584
|
-
.rle-card[type='button'],
|
|
585
|
-
.rle-popup-close,
|
|
586
|
-
.rle-skeleton,
|
|
587
|
-
.rle-input,
|
|
588
|
-
.rle-select,
|
|
589
|
-
.rle-dropdown,
|
|
590
|
-
.rle-btn,
|
|
591
|
-
.rle-viewtoggle__btn,
|
|
592
|
-
.rle-sheet,
|
|
593
|
-
.rle-sheet-backdrop {
|
|
594
|
-
transition: none;
|
|
595
|
-
animation: none;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
560
|
/* -----------------------------------------------------------------------
|
|
600
561
|
* Layout shell -- the responsive "filter bar + list/map split" chrome
|
|
601
562
|
* `StyledListingLayout` composes the `~/react` structure-only components
|
|
@@ -606,32 +567,6 @@
|
|
|
606
567
|
* the same property, so there is exactly one source of truth per viewport.
|
|
607
568
|
* --------------------------------------------------------------------- */
|
|
608
569
|
|
|
609
|
-
.rle-app,
|
|
610
|
-
.rle-filter-bar,
|
|
611
|
-
.rle-filter-bar__end,
|
|
612
|
-
.rle-filters-row,
|
|
613
|
-
.rle-filter-stack,
|
|
614
|
-
.rle-filter-group,
|
|
615
|
-
.rle-body,
|
|
616
|
-
.rle-split,
|
|
617
|
-
.rle-list,
|
|
618
|
-
.rle-list-grid,
|
|
619
|
-
.rle-map,
|
|
620
|
-
.rle-bottom-nav,
|
|
621
|
-
.rle-viewtoggle,
|
|
622
|
-
.rle-viewtoggle__btn,
|
|
623
|
-
.rle-sheet-backdrop,
|
|
624
|
-
.rle-sheet,
|
|
625
|
-
.rle-sheet__handle,
|
|
626
|
-
.rle-sheet__header,
|
|
627
|
-
.rle-sheet__title,
|
|
628
|
-
.rle-sheet__close,
|
|
629
|
-
.rle-sheet__body,
|
|
630
|
-
.rle-sheet__footer {
|
|
631
|
-
box-sizing: border-box;
|
|
632
|
-
font-family: var(--rle-font-sans);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
570
|
.rle-app {
|
|
636
571
|
display: flex;
|
|
637
572
|
flex-direction: column;
|
|
@@ -641,11 +576,16 @@
|
|
|
641
576
|
}
|
|
642
577
|
|
|
643
578
|
/* -----------------------------------------------------------------------
|
|
644
|
-
* Desktop filter bar -- hidden below 1024px in favor of the
|
|
579
|
+
* Desktop filter bar -- hidden below 1024px in favor of the mobile header's
|
|
645
580
|
* Filters button + bottom sheet.
|
|
646
581
|
* --------------------------------------------------------------------- */
|
|
647
582
|
|
|
648
|
-
|
|
583
|
+
/* Sticky top chrome shared with `.rle-mobile-header` -- the two
|
|
584
|
+
viewport-exclusive top bars. (`display: none` on whichever is inactive
|
|
585
|
+
makes the sticky properties inert, so sharing them unconditionally is
|
|
586
|
+
safe.) */
|
|
587
|
+
.rle-filter-bar,
|
|
588
|
+
.rle-mobile-header {
|
|
649
589
|
position: sticky;
|
|
650
590
|
top: 0;
|
|
651
591
|
z-index: 10;
|
|
@@ -751,15 +691,10 @@
|
|
|
751
691
|
|
|
752
692
|
@media (max-width: 1023px) {
|
|
753
693
|
.rle-mobile-header {
|
|
754
|
-
position: sticky;
|
|
755
|
-
top: 0;
|
|
756
|
-
z-index: 10;
|
|
757
694
|
display: flex;
|
|
758
695
|
align-items: center;
|
|
759
696
|
gap: 8px;
|
|
760
697
|
padding: var(--rle-space);
|
|
761
|
-
background: var(--rle-surface);
|
|
762
|
-
border-bottom: 1px solid var(--rle-border);
|
|
763
698
|
}
|
|
764
699
|
}
|
|
765
700
|
|
|
@@ -768,25 +703,16 @@
|
|
|
768
703
|
min-width: 0;
|
|
769
704
|
}
|
|
770
705
|
|
|
706
|
+
/* Modifier over `.rle-btn` (the shared button primitive -- hover, focus
|
|
707
|
+
ring, transition and reduced-motion behavior all come from there):
|
|
708
|
+
slightly taller, tighter header buttons. */
|
|
771
709
|
.rle-mobile-header__btn {
|
|
772
|
-
display: inline-flex;
|
|
773
710
|
flex-shrink: 0;
|
|
774
|
-
align-items: center;
|
|
775
|
-
justify-content: center;
|
|
776
711
|
gap: 6px;
|
|
777
712
|
height: 40px;
|
|
778
713
|
padding: 0 12px;
|
|
779
|
-
background: var(--rle-surface);
|
|
780
|
-
border: 1px solid var(--rle-border);
|
|
781
|
-
border-radius: var(--rle-radius-sm);
|
|
782
|
-
color: var(--rle-fg);
|
|
783
714
|
font-size: var(--rle-fs-sm);
|
|
784
|
-
font-weight: 500;
|
|
785
715
|
white-space: nowrap;
|
|
786
|
-
cursor: pointer;
|
|
787
|
-
transition:
|
|
788
|
-
background-color 150ms ease,
|
|
789
|
-
border-color 150ms ease;
|
|
790
716
|
}
|
|
791
717
|
|
|
792
718
|
.rle-mobile-header__btn svg {
|
|
@@ -794,15 +720,6 @@
|
|
|
794
720
|
height: 18px;
|
|
795
721
|
}
|
|
796
722
|
|
|
797
|
-
.rle-mobile-header__btn:hover {
|
|
798
|
-
background: var(--rle-muted-bg);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
.rle-mobile-header__btn:focus-visible {
|
|
802
|
-
outline: none;
|
|
803
|
-
box-shadow: 0 0 0 3px var(--rle-ring);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
723
|
.rle-mobile-header__btn--icon {
|
|
807
724
|
width: 40px;
|
|
808
725
|
padding: 0;
|
|
@@ -1097,3 +1014,18 @@
|
|
|
1097
1014
|
padding: var(--rle-space);
|
|
1098
1015
|
border-top: 1px solid var(--rle-border);
|
|
1099
1016
|
}
|
|
1017
|
+
|
|
1018
|
+
/* -----------------------------------------------------------------------
|
|
1019
|
+
* Motion -- LAST on purpose: prefix-scoped like the base reset (every
|
|
1020
|
+
* `rle-` element goes still, so a new component with a transition can't be
|
|
1021
|
+
* forgotten here), and every transition/animation above shares this rule's
|
|
1022
|
+
* single-class specificity, so it must come later in the file to win.
|
|
1023
|
+
* --------------------------------------------------------------------- */
|
|
1024
|
+
|
|
1025
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1026
|
+
[class^='rle-'],
|
|
1027
|
+
[class*=' rle-'] {
|
|
1028
|
+
transition: none;
|
|
1029
|
+
animation: none;
|
|
1030
|
+
}
|
|
1031
|
+
}
|