choropleth-kit 0.0.0-stage → 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.
@@ -0,0 +1,1186 @@
1
+ import * as react from 'react';
2
+ import { MouseEventHandler, PointerEventHandler, Dispatch, ReactNode, RefObject, CSSProperties, Ref, ReactElement } from 'react';
3
+ import { Feature, FeatureCollection } from 'geojson';
4
+ import { ab as Point, X as GeographyInput, L as DrillContext, aw as VariantConfig, ae as ProjectionName, N as DrillDirection, g as CameraTransform, aH as ZoomTarget, e as CameraOptions, av as TransitionResult, aB as ViewState, M as DrillCrumb, W as GeographyData, l as ChoroplethLabels, a3 as JoinResult, a6 as LonLatBounds, aG as ZoomOptions, am as ResolvedZoom, aa as Padding, a4 as LegendItem, aq as SizeLegendItem, a as Bubble, o as ColorScale, k as ChainLevel, R as DrillMachineState, Q as DrillMachineEvent, ad as ProjectionChain, s as Corner, as as ThemeName, m as ChoroplethTheme } from './drill-chain-DCiuiZKK.js';
5
+ export { ar as THEMES, b5 as defineTheme, ba as extendTheme, bC as themeToCssVars } from './drill-chain-DCiuiZKK.js';
6
+ import { GeoProjection } from 'd3-geo';
7
+ import 'topojson-specification';
8
+ import 'd3-interpolate';
9
+ import 'd3-scale-chromatic';
10
+
11
+ /** Drill-down level an item belongs to. Without drill: always the root. */
12
+ type DrillLevelInfo = {
13
+ id: string;
14
+ depth: number;
15
+ };
16
+ /** Default level while the drill-down does not exist. */
17
+ declare const ROOT_LEVEL: DrillLevelInfo;
18
+ type RegionItem<TDatum> = {
19
+ kind: "region";
20
+ id: string;
21
+ feature: Feature;
22
+ /** `undefined` = region without data. */
23
+ datum: TDatum | undefined;
24
+ level: DrillLevelInfo;
25
+ /** There is a level below for this region. */
26
+ drillable: boolean;
27
+ };
28
+ type PointItem<TDatum> = {
29
+ kind: "point";
30
+ id: string;
31
+ /** The datum's [lon, lat]. */
32
+ coordinates: Point;
33
+ /** The region underneath, if the datum's id joins with one. */
34
+ feature: Feature | undefined;
35
+ datum: TDatum;
36
+ level: DrillLevelInfo;
37
+ drillable: boolean;
38
+ };
39
+ /**
40
+ * ONE type for everything interactive. `kind` discriminates: a store is not a
41
+ * region, but the consumer wants a single handler and a single tooltip.
42
+ */
43
+ type MapItem<TDatum> = RegionItem<TDatum> | PointItem<TDatum>;
44
+ /** Which item it is, without carrying its data: what the `map-store` keeps. */
45
+ type ItemRef = {
46
+ kind: MapItem<unknown>["kind"];
47
+ id: string;
48
+ };
49
+ /** `MapItem` + what the tooltip needs to render. */
50
+ type ItemContext<TDatum> = MapItem<TDatum> & {
51
+ /** Number, category (`categorical`) or `null` if there is no datum. */
52
+ value: number | string | null;
53
+ /** `value` formatted; empty string if there is no value. */
54
+ formattedValue: string;
55
+ /** Color the item is painted with. */
56
+ color: string;
57
+ /** Readable name: the region's `properties.name`, or the id. */
58
+ label: string;
59
+ };
60
+
61
+ /**
62
+ * A drill-down level: geometry + data + visual encoding of ONE depth (world, country,
63
+ * region…). Levels are described ONCE; the library takes care of the flight, the loading, the fade, the
64
+ * breadcrumbs and the keyboard.
65
+ */
66
+ type DrillLevel<TDatum> = {
67
+ /** `"country"`, `"region"`, `"store"`… Unique within the list; it is the level's key. */
68
+ id: string;
69
+ /** Name of the LEVEL ("Countries"): root of the breadcrumbs and accessibility announcements. */
70
+ label?: string;
71
+ geography: GeographyInput | "inherit" | ((ctx: DrillContext) => GeographyInput);
72
+ objectName?: string;
73
+ /** Subset for this parent: a world admin-1 filtered by country. */
74
+ filterFeatures?: (feature: Feature, ctx: DrillContext) => boolean;
75
+ getFeatureId?: (feature: Feature) => string | undefined;
76
+ /** Geometry cache key. Default: `${id}:${parentId}` if `geography` is a function. */
77
+ geographyKey?: (ctx: DrillContext) => string;
78
+ /** Visual encoding of THIS level. If missing, the map's props are used (variant, getValue…). */
79
+ encoding?: VariantConfig<TDatum> & {
80
+ getId?: (datum: TDatum) => string;
81
+ };
82
+ /** Can you drill down from this item? Default: `true` if a next level exists. */
83
+ canDrill?: boolean | ((item: MapItem<TDatum>) => boolean);
84
+ /** `"inherit"` (default): perfect continuity (§11.2.4). Anything else: a fade after the flight. */
85
+ projection?: "inherit" | ProjectionName | (() => GeoProjection);
86
+ /** What stays visible of the parent. Default `"siblings"`. */
87
+ context?: "siblings" | "none";
88
+ /** Explicit framing by id in [lon, lat] (Spain with or without the Canary Islands…). */
89
+ focusBounds?: Readonly<Record<string, [[number, number], [number, number]]>>;
90
+ };
91
+ /**
92
+ * Identity with validation: it exists so TypeScript infers `TDatum` without annotations
93
+ * (`defineDrillLevels<Row>([...])`) and so a malformed list (empty, repeated ids) fails when it is
94
+ * DEFINED, with a message saying what to change, and not in the middle of a click.
95
+ */
96
+ declare const defineDrillLevels: <TDatum>(levels: readonly DrillLevel<TDatum>[]) => readonly DrillLevel<TDatum>[];
97
+ /** What `onDrill` and `onDrillEnd` receive. */
98
+ type DrillEvent<TDatum> = {
99
+ direction: DrillDirection;
100
+ from: DrillContext;
101
+ to: DrillContext;
102
+ /** The clicked item, if any (there is none for the breadcrumb, the keyboard or the URL). */
103
+ item?: MapItem<TDatum>;
104
+ };
105
+ /** The event of going from `from` to `to`, or `null` if the route does not change. */
106
+ declare const buildDrillEvent: <TDatum>({ from, to, item, }: {
107
+ from: readonly string[];
108
+ to: readonly string[];
109
+ item?: MapItem<TDatum>;
110
+ }) => DrillEvent<TDatum> | null;
111
+ /**
112
+ * Can you drill down from this item? (`data-drillable`, cursor, tooltip hint.) It needs a level below
113
+ * the current one, and the level's `canDrill` (boolean or function) must not forbid it.
114
+ */
115
+ declare const isDrillable: <TDatum>(levels: readonly DrillLevel<TDatum>[], depth: number, item: MapItem<TDatum>) => boolean;
116
+ /**
117
+ * Depth whose geometry is drawn at `depth`: an `"inherit"` level draws its parent's, so it climbs up to
118
+ * the first one that does not inherit. Two depths with the same source draw the same regions (stores
119
+ * over a country), and no separate context layer is needed.
120
+ */
121
+ declare const sourceDepth: <TDatum>(levels: readonly DrillLevel<TDatum>[], depth: number) => number;
122
+
123
+ /**
124
+ * What the buttons, the keyboard and (step 26) the imperative handle can ask of the zoom. It is
125
+ * registered by `use-zoom` when installing `d3-zoom`; each move returns a `Promise` that resolves when
126
+ * done (`completed: false` if a user gesture interrupted it).
127
+ */
128
+ type ZoomApi = {
129
+ /**
130
+ * Flies to a region, some bounds or a center with zoom. `silent` (only with `duration: 0`) places the
131
+ * camera without notifying `onViewChange`: it is what `focusId` and `initialView` do on first render.
132
+ */
133
+ zoomTo: (target: ZoomTarget, options?: CameraOptions & {
134
+ silent?: boolean;
135
+ }) => Promise<TransitionResult>;
136
+ /**
137
+ * Flies to an EXACT camera (without `constrain` or `k` limits): the drill-down needs to end exactly at
138
+ * `R` so the projection change is invisible. `duration: 0` places the camera;
139
+ * with `silent` it does not notify `onViewChange`.
140
+ */
141
+ flyTo: (camera: CameraTransform, options?: {
142
+ duration?: number;
143
+ silent?: boolean;
144
+ }) => Promise<TransitionResult>;
145
+ /** Multiplies the current zoom (×2 zooms in, ×0.5 zooms out) centered on the view. */
146
+ zoomBy: (factor: number, options?: {
147
+ duration?: number;
148
+ }) => Promise<TransitionResult>;
149
+ /** Flight to the level's fit. `silent` (only with `duration: 0`) does not notify `onViewChange`. */
150
+ resetView: (options?: {
151
+ duration?: number;
152
+ silent?: boolean;
153
+ }) => Promise<TransitionResult>;
154
+ /** The current view (the real one, also mid-flight). */
155
+ getView: () => ViewState;
156
+ };
157
+ /**
158
+ * What the handle can ask of the drill (step 34). Registered by `useChoropleth` when the map has
159
+ * `drill`; each navigation returns a `Promise` that resolves on returning to rest at the requested
160
+ * route (`completed: false` if it was cancelled, failed, another navigation replaced it or the request was invalid).
161
+ */
162
+ type DrillApi = {
163
+ drillDown: (id: string) => Promise<TransitionResult>;
164
+ drillUp: (steps?: number) => Promise<TransitionResult>;
165
+ drillTo: (path: readonly string[]) => Promise<TransitionResult>;
166
+ };
167
+ type PointerPosition = {
168
+ x: number;
169
+ y: number;
170
+ };
171
+ /** The map's interaction state. It lives OUTSIDE React. */
172
+ type MapState = {
173
+ /** Item (region or point) under the cursor or with the first tap (touch). */
174
+ hovered: ItemRef | null;
175
+ /** Cursor position relative to the container; `null` if outside. */
176
+ pointer: PointerPosition | null;
177
+ /** Type of the last pointer that touched the map: decides whether the drill asks for one or two taps. */
178
+ pointerType: "mouse" | "pen" | "touch";
179
+ /** A zoom/pan gesture is in progress (mouse, touch or wheel). It does not include programmatic flights. */
180
+ interacting: boolean;
181
+ /**
182
+ * "Confirmed" camera: the one from the last time a gesture ENDED. During the gesture `d3-zoom` writes
183
+ * it straight to the DOM and this does not change (nothing per frame in React).
184
+ */
185
+ camera: CameraTransform;
186
+ /** Hint on how to zoom (Ctrl/⌘ + wheel, two fingers). It turns itself off. */
187
+ gestureHint: "wheel" | "touch" | null;
188
+ };
189
+ type MapStore = {
190
+ subscribe: (listener: () => void) => () => void;
191
+ getState: () => MapState;
192
+ /** Merges a patch. It does not notify if no field changes (same reference / same value). */
193
+ setState: (patch: Partial<MapState>) => void;
194
+ /** Shows the gesture hint and removes it on its own after `GESTURE_HINT_MS`. Repeating it extends the hint. */
195
+ showGestureHint: (kind: "wheel" | "touch") => void;
196
+ /** Cancels the hint timer (on unmount). */
197
+ dispose: () => void;
198
+ /** `use-zoom` registers its API here on install and `null` on uninstall. */
199
+ registerZoom: (api: ZoomApi | null) => void;
200
+ /** The zoom API, or `null` if zoom is disabled or not yet installed. */
201
+ getZoom: () => ZoomApi | null;
202
+ /** `useChoropleth` registers the drill API here (`null` without `drill` or on unmount). */
203
+ registerDrill: (api: DrillApi | null) => void;
204
+ /** The drill API, or `null` if the map has no `drill`. */
205
+ getDrill: () => DrillApi | null;
206
+ /** `Root` registers how to compute the view when there is NO zoom (`zoom={false}`): always the fit. */
207
+ registerFallbackView: (read: (() => ViewState) | null) => void;
208
+ /** The fallback view, or `null` if not yet registered. */
209
+ getFallbackView: () => ViewState | null;
210
+ };
211
+ /**
212
+ * Hover, pointer, confirmed camera, `interacting` and the gesture hint. (Keyboard focus arrives with
213
+ * accessibility.)
214
+ *
215
+ * If this were `useState` in `Root`, every region change or every `mousemove` would re-render `Root`
216
+ * and recompute join, scale, legend and bubbles. Here only whoever subscribes with
217
+ * `useMapStore(store, selector)` re-renders, and only when its selector returns a different value.
218
+ */
219
+ declare const createMapStore: () => MapStore;
220
+ /**
221
+ * Reads a slice of the store. The selector must return a stable value (a primitive or the same
222
+ * reference while it does not change): if it creates a new object on every call, React will
223
+ * re-render in a loop.
224
+ */
225
+ declare const useMapStore: <T>(store: MapStore, selector: (state: MapState) => T) => T;
226
+
227
+ /** Choreography phases the map reports to the hook (computed by `drillReducer`). */
228
+ type DrillStatus = "idle" | "flying" | "loading" | "revealing";
229
+ /**
230
+ * Channel through which the MAP writes `status` and the hook reads it with `useSyncExternalStore`. It
231
+ * lives outside React for the same reason as the `map-store`: the consumer's tree must not be
232
+ * rendered on every phase change.
233
+ */
234
+ type DrillBridge = {
235
+ getStatus: () => DrillStatus;
236
+ setStatus: (status: DrillStatus) => void;
237
+ subscribe: (listener: () => void) => () => void;
238
+ };
239
+ declare const createDrillBridge: () => DrillBridge;
240
+ /** Key (symbol) of the channel on the controller: for the map's internal use, not public API. */
241
+ declare const DRILL_BRIDGE: unique symbol;
242
+ type DrillBreadcrumb = DrillCrumb;
243
+ type DrillController<TDatum> = {
244
+ levels: readonly DrillLevel<TDatum>[];
245
+ /** Ids from the root. Already trimmed to the levels that exist. */
246
+ path: readonly string[];
247
+ context: DrillContext;
248
+ /** The current level: `levels[path.length]`. */
249
+ level: DrillLevel<TDatum>;
250
+ breadcrumbs: readonly DrillBreadcrumb[];
251
+ /** Written by the map; while no map is mounted, `"idle"`. */
252
+ status: DrillStatus;
253
+ canDrillUp: boolean;
254
+ /**
255
+ * Goes down one level to child `id`. With no level below, it does nothing (and warns in development).
256
+ * Returns the route that ended up requested (already trimmed): the map uses it to know where it
257
+ * expects to arrive.
258
+ */
259
+ drillDown: (id: string) => readonly string[];
260
+ /** Goes up `steps` levels (1 by default); more than the depth = to the root. */
261
+ drillUp: (steps?: number) => readonly string[];
262
+ /** Any route: lateral, jumps, the URL. */
263
+ drillTo: (path: readonly string[]) => readonly string[];
264
+ /** Back to the root. */
265
+ reset: () => readonly string[];
266
+ /** @internal The channel through which the map publishes `status`. */
267
+ readonly [DRILL_BRIDGE]: DrillBridge;
268
+ };
269
+ type UseDrillDownOptions<TDatum> = {
270
+ levels: readonly DrillLevel<TDatum>[];
271
+ /** Controlled: you are in charge (e.g. from the URL). The hook only notifies via `onPathChange`. */
272
+ path?: readonly string[];
273
+ /** No controlado: ruta inicial. */
274
+ defaultPath?: readonly string[];
275
+ /** Called on every requested route change (click, keyboard, breadcrumbs, `drillTo`), controlled or not. */
276
+ onPathChange?: (path: readonly string[]) => void;
277
+ /**
278
+ * Name of each breadcrumb step. The id by default. (The hook does not see the geometry: the region's
279
+ * name, `properties.name`, is set by the map when it exists; here `feature` and `datum` are empty.)
280
+ */
281
+ getLabel?: (args: {
282
+ id: string;
283
+ depth: number;
284
+ feature?: Feature;
285
+ datum?: TDatum;
286
+ }) => string;
287
+ };
288
+ /**
289
+ * Drill-down navigation state: the route, in controlled or uncontrolled mode, and
290
+ * what is deduced from it (level, context, breadcrumbs). It loads no geometry and moves no camera:
291
+ * the map does that, and it also reports its phase back (`status`).
292
+ *
293
+ * Controlled = `path` present (the `<input value>` pattern). The route is trimmed to the levels that
294
+ * exist: with N levels, N − 1 ids at most.
295
+ */
296
+ declare const useDrillDown: <TDatum>({ levels, path: controlledPath, defaultPath, onPathChange, getLabel, }: UseDrillDownOptions<TDatum>) => DrillController<TDatum>;
297
+ /**
298
+ * The `drill` prop accepts a list of levels (uncontrolled) or a `useDrillDown` controller
299
+ * (controlled). This tells one from the other.
300
+ */
301
+ declare const isDrillController: <TDatum>(drill: readonly DrillLevel<TDatum>[] | DrillController<TDatum>) => drill is DrillController<TDatum>;
302
+
303
+ type GeographyStatus = "loading" | "ready" | "error";
304
+ type UseGeographyResult = {
305
+ geography: GeographyData | undefined;
306
+ status: GeographyStatus;
307
+ };
308
+ /**
309
+ * Resolves the `geography` prop. Synchronous data is returned in the same render (no flicker and no
310
+ * effect); a promise or a loader is awaited in an effect (async loading = a real effect) and in the
311
+ * meantime `geography` is `undefined` and the status is `loading`.
312
+ *
313
+ * If it changes to another geometry that has not loaded yet, the previous one is kept until it arrives.
314
+ */
315
+ declare const useGeography: (input: GeographyInput, key: string | undefined, onError?: (error: Error) => void) => UseGeographyResult;
316
+ /** For tests only: empties the cache between tests. */
317
+ declare const clearGeographyCache: () => void;
318
+
319
+ /** A region ready to paint. */
320
+ type Region<TDatum> = {
321
+ id: string;
322
+ feature: Feature;
323
+ /** `undefined` = region without data. */
324
+ datum: TDatum | undefined;
325
+ /** The `<path>`'s `d` attribute. */
326
+ path: string;
327
+ fill: string;
328
+ /** The datum's value: number, category (`categorical`) or `null` if there is no datum. */
329
+ value: number | string | null;
330
+ /** `value` ready to display ("1.5 %", "Europe"); empty string if there is no value. */
331
+ formattedValue: string;
332
+ /** There is a level below and `canDrill` allows it (always `false` without `drill`). */
333
+ drillable: boolean;
334
+ /** 0…1: how much its entrance is delayed in the reveal (distance from the center). Only while revealing. */
335
+ wave: number | undefined;
336
+ /** From `getRegionStyle`; `undefined` = the CSS one (1). */
337
+ opacity: number | undefined;
338
+ /** From `getRegionStyle`: a class of its own for this region. */
339
+ className: string | undefined;
340
+ /** From `getRegionStyle`: a pattern drawn ON TOP of the color (`"hatch"`, `"dots"` or the name of your own in `<Choropleth.Defs>`). */
341
+ pattern: string | undefined;
342
+ /** Legend class it belongs to (`data-class`), or `null` (no data, continuous scale, `bubble`). */
343
+ classIndex: number | null;
344
+ };
345
+ /**
346
+ * Per-region style: data-based exceptions the scale cannot express. It is evaluated in
347
+ * render and its fields reach the `<path>` as primitives, so it does not break the regions' `memo`.
348
+ */
349
+ type RegionStyle = {
350
+ /** Replaces the scale's color (and `emptyFill`). */
351
+ fill?: string;
352
+ opacity?: number;
353
+ /** A class of its own; it is added to `classNames.region`. */
354
+ className?: string;
355
+ /**
356
+ * Pattern drawn over the color (the datum still shows through): `"hatch"` or `"dots"`, or the name of
357
+ * your own `<pattern>` defined in `<Choropleth.Defs>` with `useDefId(name)`. It needs `<Choropleth.Defs>`
358
+ * and `<Choropleth.Patterns>` (the preset and `Choropleth.Svg` already include them).
359
+ */
360
+ pattern?: "hatch" | "dots" | (string & {});
361
+ };
362
+ /**
363
+ * A drill layer that is NOT the active one. `context`: the levels above, in gray and
364
+ * with no data (the parent's scale and the child's are not comparable). `exiting`: the level that was
365
+ * just left, fading out. The paths are in the pixels of THEIR projection; `transform` carries them to
366
+ * those of the active level (it is the affine map `R` between the two projections), so the cached paths are reused.
367
+ */
368
+ type DrillLayerView = {
369
+ key: string;
370
+ role: "context" | "exiting";
371
+ depth: number;
372
+ transform: CameraTransform;
373
+ paths: {
374
+ id: string | undefined;
375
+ d: string;
376
+ focus: boolean;
377
+ drillable: boolean;
378
+ /** `exiting` only: the color the region had (it fades with it). */
379
+ fill: string | undefined;
380
+ }[];
381
+ };
382
+ /** A crumb of the drill route. */
383
+ type DrillNavItem = {
384
+ depth: number;
385
+ /** `null` at the root. */
386
+ id: string | null;
387
+ label: string;
388
+ /** The last one: the level being gone to (or being at). */
389
+ current: boolean;
390
+ };
391
+ /** What a breadcrumb needs (the preset's or yours). `null` without `drill`. */
392
+ type DrillNav = {
393
+ items: DrillNavItem[];
394
+ canDrillUp: boolean;
395
+ /** Goes to the crumb at that depth (0 = root). */
396
+ drillTo: (depth: number) => void;
397
+ drillUp: (steps?: number) => void;
398
+ };
399
+ type JoinReport = Omit<JoinResult<unknown>, "joined">;
400
+ /** What is dimmed when there is a selection or hover. */
401
+ type DimOthers = "never" | "selection" | "hover-and-selection";
402
+ type BaseOptions<TDatum> = {
403
+ /**
404
+ * TopoJSON or GeoJSON, a promise, or `() => import("…")`. Already-loaded data must have a stable
405
+ * reference (a module constant or `useMemo`).
406
+ */
407
+ geography?: GeographyInput;
408
+ /** Cache key for an async geometry (otherwise the function's identity is used). */
409
+ geographyKey?: string;
410
+ objectName?: string;
411
+ /** Discards regions before anything else (e.g. Antarctica: `(f) => f.id !== "010"`). */
412
+ filterFeatures?: (feature: Feature) => boolean;
413
+ /**
414
+ * `undefined` = loading (the previous data is kept, no warnings); `[]` = loaded and empty. With `drill`
415
+ * it can be a selector for the current level: `({ depth, parentId }) => rows.filter(…)`.
416
+ */
417
+ data: readonly TDatum[] | undefined | ((ctx: DrillContext) => readonly TDatum[] | undefined);
418
+ /**
419
+ * Drill-down: the list of levels (uncontrolled) or a `useDrillDown` (controlled).
420
+ * With `drill`, the geometry comes from the levels and `geography` is ignored.
421
+ */
422
+ drill?: readonly DrillLevel<TDatum>[] | DrillController<TDatum>;
423
+ /** When a level change STARTS (the route is already updated). */
424
+ onDrill?: (event: DrillEvent<TDatum>) => void;
425
+ /** When the new level has finished being shown. */
426
+ onDrillEnd?: (event: DrillEvent<TDatum>) => void;
427
+ /** If a level's geometry fails (or has 0 regions): it goes back to the previous level. */
428
+ onDrillError?: (event: {
429
+ error: unknown;
430
+ ctx: DrillContext;
431
+ }) => void;
432
+ /**
433
+ * The pointer has stayed 150 ms over a region you can drill into (or, on touch, it was tapped once):
434
+ * `ctx` is that of the level it would go down to. Use it to preload data (`queryClient.prefetchQuery`);
435
+ * that level's geometry is already preloaded by the map. Called once per dwell.
436
+ */
437
+ onDrillIntent?: (ctx: DrillContext) => void;
438
+ /** Forces the data loading state (pulse) even if `data` is defined. */
439
+ loading?: boolean;
440
+ getId: (datum: TDatum) => string;
441
+ /** The region's id if it is not `feature.id` (e.g. `(f) => f.properties?.iso_a2`). */
442
+ getFeatureId?: (feature: Feature) => string | undefined;
443
+ projection?: ProjectionName;
444
+ /**
445
+ * Width / height of the map. `"auto"` derives it from the geometry's bounds (Chile comes out tall,
446
+ * Norway wide). 960 / 500 by default, the world's proportion.
447
+ */
448
+ aspectRatio?: number | "auto";
449
+ emptyFill?: string;
450
+ formatValue?: (value: number) => string;
451
+ /** If given, the legend includes a "no data" item with this text. */
452
+ emptyLabel?: string;
453
+ /** UI texts (i18n). English by default; `es` is exported. */
454
+ labels?: Partial<ChoroplethLabels>;
455
+ /**
456
+ * Microstates that do not exist in the geometry (Andorra, Monaco…): `{ AD: [1.52, 42.51] }`. A datum
457
+ * whose id joins no region but is listed here is painted as a dot, with the datum's color. In
458
+ * `bubble`, as a bubble.
459
+ */
460
+ fallbackPoints?: Readonly<Record<string, Point>>;
461
+ /** Fixed size. If missing, the container (`containerRef`) is measured. */
462
+ width?: number;
463
+ height?: number;
464
+ /** Controlled: you are in charge. `null` = nothing selected. Without this, the library holds the state. */
465
+ selectedId?: string | null;
466
+ /** Uncontrolled: the initial selection. */
467
+ defaultSelectedId?: string | null;
468
+ /** Called when the selection changes (click on a region; click on the selected one = `null`). */
469
+ onSelectedIdChange?: (id: string | null) => void;
470
+ /** Dims the rest of the map. Defaults to `"selection"`. */
471
+ dimOthers?: DimOthers;
472
+ /** Click on a region or a point (`item.kind`). */
473
+ onItemClick?: (item: MapItem<TDatum>) => void;
474
+ /** Hover (or first tap, on touch). `null` on leaving. Only called when the item changes. */
475
+ onItemHover?: (item: MapItem<TDatum> | null) => void;
476
+ /**
477
+ * Per-region style based on its data: `(r) => r.datum?.estimated ? { opacity: 0.6 } : undefined`. Priority:
478
+ * `getRegionStyle` > scale color > `emptyFill`. Do not define `fill` on `.cm-region` in CSS: it would beat the datum.
479
+ */
480
+ getRegionStyle?: (region: Region<TDatum>) => RegionStyle | undefined;
481
+ /** Called every time the data↔regions join result changes (also in production). */
482
+ onJoinReport?: (report: JoinReport) => void;
483
+ /**
484
+ * Flies to this region (id) when it changes; with `null` it returns to the fit. On the **first render**
485
+ * it does not fly: it places the camera directly. It is the declarative form of `zoomTo`.
486
+ */
487
+ focusId?: string | null;
488
+ /**
489
+ * Explicit framings per region, in [[lonW, latS], [lonE, latN]]. They are merged with the library's
490
+ * (`DEFAULT_FOCUS_BOUNDS`: Spain **with the Canary Islands**). `{ ES: null }` returns to the geometric
491
+ * framing (main body) for that region.
492
+ */
493
+ focusBounds?: Readonly<Record<string, LonLatBounds | null>>;
494
+ /**
495
+ * Zoom and pan. On by default; `false` = static map. The wheel zooms with the mouse over
496
+ * the map (`wheel: "always"`); for maps inside a long page use `wheel: "cooperative"`.
497
+ */
498
+ zoom?: boolean | ZoomOptions;
499
+ /** Called if the async geometry load fails. Without this, a console warning is issued. */
500
+ onGeographyError?: (error: Error) => void;
501
+ /** Console warning about ids that did not join. Outside production only. Defaults to `true`. */
502
+ diagnostics?: boolean;
503
+ };
504
+ /** Common options + those of each `variant` (a discriminated union, see `core/variants.ts`). */
505
+ type UseChoroplethOptions<TDatum> = BaseOptions<TDatum> & VariantConfig<TDatum>;
506
+ /** Props ready to spread onto a region's `<path>` (`<path {...getRegionProps(r)} />`). */
507
+ type RegionProps = {
508
+ "data-region-id": string;
509
+ "data-selected": true | undefined;
510
+ onClick: MouseEventHandler;
511
+ onPointerEnter: PointerEventHandler;
512
+ onPointerLeave: PointerEventHandler;
513
+ };
514
+ /** Props for the `<circle>` of a bubble with its own coordinates (a point). */
515
+ type PointProps = {
516
+ "data-point-id": string;
517
+ onClick: MouseEventHandler;
518
+ onPointerEnter: PointerEventHandler;
519
+ onPointerLeave: PointerEventHandler;
520
+ };
521
+ /** A microstate painted as a dot (`fallbackPoints`) outside `bubble`. */
522
+ type FallbackPoint<TDatum> = {
523
+ id: string;
524
+ x: number;
525
+ y: number;
526
+ r: number;
527
+ fill: string;
528
+ datum: TDatum;
529
+ value: number | string | null;
530
+ formattedValue: string;
531
+ coordinates: Point;
532
+ };
533
+ /** Radius in pixels of a fallback point. */
534
+ declare const FALLBACK_POINT_RADIUS = 4;
535
+ type UseChoroplethResult<TDatum> = {
536
+ /** Put this ref on the container so the map adapts to its width. */
537
+ containerRef: React.RefObject<HTMLDivElement | null>;
538
+ width: number;
539
+ height: number;
540
+ /** Phase of the drill choreography (`null` without `drill`). Published as `data-drill-phase`. */
541
+ drillPhase: "idle" | "flying" | "loading" | "revealing" | null;
542
+ /** Region being drilled into while the flight / load lasts (the rest is dimmed). */
543
+ drillFocusId: string | null;
544
+ /** Context layers (levels above, in gray) and the one that leaves. Empty without `drill`. */
545
+ drillLayers: DrillLayerView[];
546
+ /** Click on a region of a context layer (lateral navigation or jump). */
547
+ selectContextItem: (depth: number, id: string) => void;
548
+ /** Drill breadcrumbs and navigation (`null` without `drill`). */
549
+ drillNav: DrillNav | null;
550
+ /** `loading` while the async geometry arrives; `error` if it failed. */
551
+ status: GeographyStatus;
552
+ /** `true` while `data` is `undefined` (or `loading`): the last data is kept. */
553
+ dataLoading: boolean;
554
+ regions: Region<TDatum>[];
555
+ /** `d` of the regions WITHOUT an id: they are painted with `emptyFill`, with no interaction (Kosovo in world-atlas). */
556
+ anonymousPaths: string[];
557
+ /** Already-resolved "no data" color (`emptyFill` or the CSS variable). */
558
+ emptyFill: string;
559
+ /** Microstates painted as a dot (empty in `bubble`: there they are bubbles). */
560
+ fallbackPoints: FallbackPoint<TDatum>[];
561
+ labels: ChoroplethLabels;
562
+ /** Already-resolved zoom options; `null` if `zoom={false}`. */
563
+ zoom: ResolvedZoom | null;
564
+ /** The user's `onViewChange` (not part of `zoom`: it must not reinstall the behavior). */
565
+ onViewChange: ZoomOptions["onViewChange"];
566
+ /** Canvas pixels → [lon, lat] (`projection.invert`); `null` if it falls outside the globe. */
567
+ invert: (point: Point) => Point | null;
568
+ /** [lon, lat] → canvas pixels; `null` if it does not project. */
569
+ project: (coordinates: Point) => Point | null;
570
+ /** `focusId` as is, so `useZoom` applies it (fly or place the camera). */
571
+ focusId: string | null | undefined;
572
+ /**
573
+ * `ZoomTarget` → camera (already limited by `constrain`), or `null` if it cannot be resolved
574
+ * (nonexistent region). It is what `zoomTo`, `focusId` and `initialView` use.
575
+ */
576
+ resolveCamera: (target: ZoomTarget, options?: {
577
+ padding?: number | Padding;
578
+ }) => CameraTransform | null;
579
+ /** Interior borders (a `<path>`'s `d`). `null` with GeoJSON: use `stroke` on each region. */
580
+ borders: string | null;
581
+ /** Color legend (swatches with counts). Empty in `bubble` without `getColorValue`. */
582
+ legend: LegendItem[];
583
+ /** Size reference circles (`bubble` only). */
584
+ sizeLegend: SizeLegendItem[];
585
+ /** Already-positioned bubbles, from largest to smallest (`bubble` only). */
586
+ bubbles: Bubble<TDatum>[];
587
+ /** Numeric color scale. `null` in `categorical` and in `bubble` without `getColorValue`. */
588
+ scale: ColorScale | null;
589
+ joinReport: JoinReport;
590
+ /** Effective formatter (the user's or the default), for tooltip and legend. */
591
+ formatValue: (value: number) => string;
592
+ /** `true` if there is `onItemClick`: components set a `pointer` cursor. */
593
+ clickable: boolean;
594
+ /**
595
+ * Interaction state (hover, pointer) outside React. Read it with `useMapStore(store, s => s.hoveredId)`:
596
+ * that way changing region does not re-render the component that calls `useChoropleth`.
597
+ */
598
+ store: MapStore;
599
+ selectedId: string | null;
600
+ /** Selects a region (`null` clears it). It respects controlled mode: it only notifies. */
601
+ setSelectedId: (id: string | null) => void;
602
+ /** Hover of an item (or `null`). Updates the store and calls `onItemHover` only if it changes. */
603
+ hoverItem: (ref: ItemRef | null) => void;
604
+ /** Clic en un item: llama a `onItemClick`. */
605
+ clickItem: (ref: ItemRef) => void;
606
+ /** The complete item (with value, color and name) for the tooltip; `undefined` if it no longer exists. */
607
+ getItemContext: (ref: ItemRef) => ItemContext<TDatum> | undefined;
608
+ getRegionProps: (region: Region<TDatum>) => RegionProps;
609
+ getPointProps: (point: {
610
+ id: string;
611
+ }) => PointProps;
612
+ };
613
+ /**
614
+ * Level 3 (headless) and the engine of the other two: `<Choropleth.Root>` and `<ChoroplethMap>` call it
615
+ * and distribute its result. It returns data, not elements: you decide how to paint them.
616
+ *
617
+ * What is memoized and why: only what is expensive and what must have a stable identity.
618
+ * - Geometry (`features`, `borders`): depends only on `geography` (and on the filter, by its RESULT).
619
+ * - SVG paths: depend on geometry, projection and size. NOT on the data, so changing indicator or
620
+ * `variant` recomputes not a single path (an acceptance criterion).
621
+ * The join and the scale are O(regions) and are recomputed on every render: memoizing them would
622
+ * require the consumer to stabilize `getId`/`getValue`/`classification`, and almost nobody does.
623
+ */
624
+ declare const useChoropleth: <TDatum>(rawOptions: UseChoroplethOptions<TDatum>) => UseChoroplethResult<TDatum>;
625
+
626
+ /**
627
+ * The drill-down engine inside the map. To the rest of the `useChoropleth` hook, a map
628
+ * with `drill` is a ONE-level map (the one being shown) with different options: this engine decides
629
+ * which level that is, loads its geometry (and that of its ancestors, for the projection chain) and
630
+ * hands over the effective options. That way the join, the scale, the bubbles and the legend know nothing about it.
631
+ *
632
+ * **The choreography is governed by `drillReducer`** (`core/drill.ts`, pure). This hook is the glue with
633
+ * the real world: it translates what happens (the consumer changes the route, geometry arrives, a load
634
+ * fails) into reducer events, and renders the resulting state. The camera flight and the reveal timer
635
+ * live in `use-drill-choreography.ts`, because they need the size and the projections.
636
+ *
637
+ * - **Down / lateral:** the flight starts NOW (optimistic) and the load runs in parallel; the new
638
+ * level is not shown until both are done.
639
+ * - **Up / jump:** they are not requested until geometry and data are available (usually immediate: it is cached).
640
+ * - **While loading** the previous level keeps showing with ITS data (never new data over old geometry).
641
+ */
642
+ type DrillEngineParams<TDatum> = {
643
+ drill: readonly DrillLevel<TDatum>[] | DrillController<TDatum> | undefined;
644
+ /** `data` as is: a list, `undefined` (loading) or a level selector. */
645
+ data: readonly TDatum[] | undefined | ((ctx: DrillContext) => readonly TDatum[] | undefined);
646
+ /** The map's filter (Antarctica…). Applied only to the root geometry and whatever inherits it. */
647
+ filterFeatures: ((feature: Feature) => boolean) | undefined;
648
+ /** To read the camera when starting to go down (saved to return to it when going up). */
649
+ store: MapStore;
650
+ onDrill: ((event: DrillEvent<TDatum>) => void) | undefined;
651
+ onDrillEnd: ((event: DrillEvent<TDatum>) => void) | undefined;
652
+ onDrillError: ((event: {
653
+ error: unknown;
654
+ ctx: DrillContext;
655
+ }) => void) | undefined;
656
+ };
657
+ /** What was drawn before the last level change: the exiting layers (`exiting`) need it. */
658
+ type DrillPrevious = {
659
+ path: readonly string[];
660
+ chain: readonly ChainLevel[];
661
+ /** Color each region was painted with: the exiting layer fades WITH its color, not in gray. */
662
+ fills: ReadonlyMap<string, string>;
663
+ };
664
+ type DrillEngine<TDatum> = {
665
+ /** `false` without the `drill` prop: the hook behaves as always. */
666
+ active: boolean;
667
+ controller: DrillController<TDatum> | null;
668
+ levels: readonly DrillLevel<TDatum>[];
669
+ /** The route of what is being shown (not the requested one, if the new one is still loading or the flight has not ended). */
670
+ shownPath: readonly string[];
671
+ /** Changes only when what is shown changes: serves as a key for effects. */
672
+ shownKey: string;
673
+ depth: number;
674
+ level: DrillLevel<TDatum> | null;
675
+ /** The geometry of the shown level, already resolved. */
676
+ geography: GeographyData | undefined;
677
+ status: GeographyStatus;
678
+ objectName: string | undefined;
679
+ /** Features of the shown level, already filtered. */
680
+ features: FeatureCollection | undefined;
681
+ /** Features of EACH depth `0..depth` of what is shown (the context layers). */
682
+ levelFeatures: readonly (FeatureCollection | undefined)[];
683
+ getFeatureId: ((feature: Feature) => string | undefined) | undefined;
684
+ /** What is drawn at each depth `0..depth`: for the projection chain. */
685
+ chain: readonly ChainLevel[];
686
+ /** Changes only if the content of `chain` changes (use it as a dependency instead of `chain`). */
687
+ chainSig: string;
688
+ /** The composite filter of the shown level: the borders (`createBorders`) need it. */
689
+ filterFeatures: ((feature: Feature) => boolean) | undefined;
690
+ /** The root geometry (for the `"auto"` aspect ratio, which must not change when going down). */
691
+ rootFeatures: FeatureCollection | undefined;
692
+ /** The shown level's data (`undefined` = loading). */
693
+ data: readonly TDatum[] | undefined;
694
+ /** Notes which item caused the next route change (received by `onDrill`). */
695
+ noteItem: (item: MapItem<TDatum>) => void;
696
+ /** The state machine (phase, layers, pending flight…). */
697
+ machine: DrillMachineState;
698
+ dispatch: Dispatch<DrillMachineEvent>;
699
+ /** What was shown before the last change (to draw the exiting layer). */
700
+ previous: DrillPrevious | null;
701
+ /** Records the colors of what is being shown; they are read on a level change (`previous.fills`). */
702
+ rememberFills: (fills: ReadonlyMap<string, string>) => void;
703
+ /** The chain of levels of a route (to compute where the camera flies); `null` if any are missing. */
704
+ chainLevelsFor: (path: readonly string[]) => ChainLevel[] | null;
705
+ /** The REQUESTED route (the controller's), not the shown one. */
706
+ target: readonly string[];
707
+ /**
708
+ * Loads the geometry of `path` (and its ancestors) into the cache without showing it: intent prefetch
709
+ *. Idempotent, silent and with no effect if it is already there or already being requested.
710
+ */
711
+ prefetch: (path: readonly string[]) => void;
712
+ /**
713
+ * Navigates with `run` (which calls the controller and returns the route that ended up requested) and
714
+ * returns a `Promise` that resolves when it returns to rest on it (`completed: false` if it was cut
715
+ * short or never got applied).
716
+ */
717
+ navigate: (run: (controller: DrillController<TDatum>) => readonly string[]) => Promise<TransitionResult>;
718
+ };
719
+ declare const useDrillEngine: <TDatum>({ drill, data, filterFeatures, store, onDrill, onDrillEnd, onDrillError, }: DrillEngineParams<TDatum>) => DrillEngine<TDatum>;
720
+
721
+ /**
722
+ * Duration of the reveal: 200 ms of fade + up to 250 ms of staggering by distance from
723
+ * the click point. With "reduce motion" it is 0: the new level appears without animating.
724
+ */
725
+ declare const REVEAL_MS = 450;
726
+ type UseDrillChoreographyParams<TDatum> = {
727
+ engine: DrillEngine<TDatum>;
728
+ store: MapStore;
729
+ /** The projection chain of what is shown (or `null` if there is none yet). */
730
+ shownChain: ProjectionChain | null;
731
+ /** Builds a route's chain with the current size, padding and framings. */
732
+ chainFor: (levels: ChainLevel[], path: readonly string[]) => ProjectionChain;
733
+ };
734
+ /**
735
+ * The part of the choreography that needs the size and the projections: it moves the
736
+ * camera and marks the reveal as done. It reads from the reducer (`engine.machine`) WHAT to do and
737
+ * reports back WHAT happened (`FLIGHT_END`, `REVEAL_END`).
738
+ *
739
+ * - **Down / lateral:** flies to `R`, the affine map separating the current level's projection from
740
+ * the target's (`relativeTransform`). That way, when the flight ends, switching projection with an
741
+ * identity camera paints the same pixels and the rebase is invisible. It does NOT go through
742
+ * `constrain`: it has to end exactly at `R`.
743
+ * - **Up / backing out:** flies to the camera that existed before going down (or to the fit).
744
+ * - **No zoom** (`zoom={false}`) or no possible framing: there is no flight, the phase passes at once.
745
+ */
746
+ declare const useDrillChoreography: <TDatum>({ engine, store, shownChain, chainFor, }: UseDrillChoreographyParams<TDatum>) => void;
747
+
748
+ /**
749
+ * The imperative handle: what you get with `ref` on `<ChoroplethMap>` / `<Choropleth.Root>`
750
+ * and with `useChoroplethApi()` inside the compound. **Everything animated returns a `Promise`** that
751
+ * resolves when done, or when interrupted (`completed: false`); that way flights can be chained
752
+ * without timers: `await map.zoomTo("ES"); await map.zoomTo("FR");`.
753
+ *
754
+ * `drillDown`, `drillUp` and `drillTo` (step 34) navigate the `drill` levels; they resolve on returning
755
+ * to rest at the requested route. (`toSVGString` and `toPNG` are planned and arrive with export.)
756
+ */
757
+ type ChoroplethMapHandle = {
758
+ /** Flies to a region (id), some `{ bounds }` or a center with zoom `{ center, zoom }`. */
759
+ zoomTo: (target: ZoomTarget, options?: CameraOptions) => Promise<TransitionResult>;
760
+ /** Multiplies the current zoom (2 = zoom in to double, 0.5 = zoom out to half), centered on the view. */
761
+ zoomBy: (factor: number, options?: Pick<CameraOptions, "duration">) => Promise<TransitionResult>;
762
+ /** Flight to the level's fit. */
763
+ resetView: (options?: Pick<CameraOptions, "duration">) => Promise<TransitionResult>;
764
+ /** The current view (also mid-flight). */
765
+ getView: () => ViewState;
766
+ /**
767
+ * Goes down to child `id` of what is being requested (not of what is shown: chaining `await` is safe).
768
+ * Resolves `completed: true` when the choreography ends at that route; `false` if something cut it
769
+ * short (a gesture, another navigation, a load error) or if the request was not valid (nonexistent id,
770
+ * `canDrill: false`, a map without `drill`): in those cases it also warns on the console outside production.
771
+ */
772
+ drillDown: (id: string) => Promise<TransitionResult>;
773
+ /** Goes up `steps` levels (1 by default; more = to the root). At the root it resolves `completed: true` and does nothing. */
774
+ drillUp: (steps?: number) => Promise<TransitionResult>;
775
+ /** Goes to any route (lateral, jump, the URL): `["ES", "ES-MD"]`; `[]` = the root. */
776
+ drillTo: (path: readonly string[]) => Promise<TransitionResult>;
777
+ };
778
+
779
+ /**
780
+ * `classNames` slots: a class of yours (Tailwind, CSS Modules) on each piece of the map,
781
+ * ADDED to the library's `cm-*` one (it never replaces it, so selectors like `.cm-region[data-drillable]`
782
+ * and the base styles keep working; to remove the base ones there is `unstyled`).
783
+ *
784
+ * Only the pieces that exist today are here. `sphere`, `graticule` and `labels` are planned and arrive with their layers.
785
+ */
786
+ declare const CLASS_SLOTS: readonly ["root", "svg", "layer", "regions", "region", "borders", "bubbles", "bubble", "selection", "legend", "legendItem", "tooltip", "controls", "controlButton", "breadcrumb", "breadcrumbItem", "gestureHint"];
787
+ type ClassSlot = (typeof CLASS_SLOTS)[number];
788
+ type ChoroplethClassNames = Partial<Record<ClassSlot, string>>;
789
+
790
+ /**
791
+ * The imperative handle from INSIDE the compound: a button that is a child of
792
+ * `<Choropleth.Root>` can call `zoomTo` without needing a `ref`.
793
+ */
794
+ declare const useChoroplethApi: () => ChoroplethMapHandle;
795
+ /**
796
+ * The id of an SVG definition (`<pattern>`, `<linearGradient>`, `<filter>`) of THIS map. With two maps
797
+ * on the page, `url(#hatch)` would always point to the first one: ids carry a per-instance prefix
798
+ * (`useId`). Use it to define your own inside `<Choropleth.Defs>` (`<pattern id={useDefId("stripes")}>`)
799
+ * and to reference them: `fill="url(#…)"`. The built-in ones are `"hatch"` and `"dots"`.
800
+ */
801
+ declare const useDefId: (name: string) => string;
802
+
803
+ type DefsProps = {
804
+ /** Your own definitions (`<pattern>`, `<linearGradient>`, `<filter>`), with `useDefId` ids. */
805
+ children?: ReactNode;
806
+ };
807
+ /**
808
+ * The map's `<defs>`: the built-in patterns (`hatch` and `dots`) plus yours. Ids carry a per-instance
809
+ * prefix (`useDefId`): with two maps on the page, each one references ITS definitions.
810
+ *
811
+ * The patterns draw with `currentColor` and the `<pattern>`'s own `color` is `--cm-pattern-color` (by
812
+ * default, the text color at 45 %), so they respect the theme (dark too).
813
+ */
814
+ declare const Defs: ({ children }: DefsProps) => react.JSX.Element;
815
+
816
+ /**
817
+ * Patterns, drawn ON TOP of each region's color (the datum still shows through underneath): those of
818
+ * `getRegionStyle().pattern` and that of regions without data (`emptyPattern` or the theme's). It goes
819
+ * between the regions and the borders. No events: it does not steal the mouse from what is underneath.
820
+ * It needs `<Choropleth.Defs>`.
821
+ */
822
+ declare const Patterns: () => react.JSX.Element | null;
823
+
824
+ /**
825
+ * Selector subscription to the CONFIRMED camera: it is re-evaluated when a gesture, a
826
+ * flight or a level change ends, **never per frame** (during the gesture `d3-zoom` writes to the DOM
827
+ * and React is unaware). Use it inside `<Choropleth.Root>`:
828
+ *
829
+ * ```tsx
830
+ * const zoomedIn = useCamera((view) => view.zoom > 3);
831
+ * ```
832
+ *
833
+ * The component only re-renders if the selector's result changes: primitives by `Object.is`, and
834
+ * objects or arrays (`(v) => v.center`) by shallow equality, so there is no need to memoize the
835
+ * selector or return a stable reference.
836
+ */
837
+ declare const useCamera: <T>(selector: (view: ViewState) => T) => T;
838
+
839
+ type UseZoomParams = {
840
+ svgRef: RefObject<SVGSVGElement | null>;
841
+ viewportRef: RefObject<SVGGElement | null>;
842
+ /** The `.cm-root` container: `--cm-k` and the `data-*` attributes are written here. */
843
+ rootRef: RefObject<HTMLElement | null>;
844
+ store: MapStore;
845
+ /** Canvas size (px). If it changes, the behavior is reinstalled and the camera restored. */
846
+ width: number;
847
+ height: number;
848
+ /** `null` = zoom disabled. */
849
+ options: ResolvedZoom | null;
850
+ /** To turn the camera into a geographic `ViewState`. */
851
+ invert: (point: Point) => Point | null;
852
+ /** [lon, lat] → CURRENT canvas pixels; with the previous canvas's `invert`, it preserves the view on resize. */
853
+ project: (coordinates: Point) => Point | null;
854
+ /** Called at the END of a gesture; never per frame. */
855
+ onViewChange: ((view: ViewState) => void) | undefined;
856
+ /** `ZoomTarget` → camera (already limited by `constrain`), or `null` if it cannot be resolved. */
857
+ resolveCamera: (target: ZoomTarget, options?: {
858
+ padding?: number | Padding;
859
+ }) => CameraTransform | null;
860
+ /** Flies to this region when it changes; `null` = back to the fit; the first value is placed without flying. */
861
+ focusId: string | null | undefined;
862
+ /** When it goes from "loading" to "ready" the `<svg>` appears: it has to hook up again. */
863
+ ready: boolean;
864
+ };
865
+ /**
866
+ * Hooks `d3-zoom` to the `<svg>`. It is the ONLY piece that uses `d3-selection`.
867
+ *
868
+ * Rules that make this smooth and keep it from fighting React:
869
+ * - **React never writes the viewport's `transform`.** `d3-zoom` writes it every frame with
870
+ * `setAttribute`. If React wrote it, any render (new data mid-pan) would overwrite it and the map
871
+ * would jump.
872
+ * - **Nothing per frame in React**: no `setState` in `zoom`. Per frame only the `transform`
873
+ * attribute, `--cm-k` and `data-zoomed` are written to the DOM. The store is touched only at the
874
+ * START and END of the gesture.
875
+ * - **Primitive dependencies**: an inline `zoom={{ max: 20 }}` creates a new object on every render;
876
+ * if the effect depended on it, `d3-zoom` would be reinstalled on every render.
877
+ */
878
+ declare const useZoom: ({ svgRef, viewportRef, rootRef, store, width, height, options, invert, project, onViewChange, resolveCamera, focusId, ready, }: UseZoomParams) => void;
879
+
880
+ type ContainerSize = {
881
+ width: number;
882
+ height: number;
883
+ };
884
+ type UseContainerSizeOptions = {
885
+ /** Size while there is no measurement (first render, SSR, jsdom). 960 wide by default. */
886
+ initial?: ContainerSize;
887
+ /** width / height. 960 / 500 by default, the world's proportion in Equal Earth. */
888
+ aspectRatio?: number;
889
+ /**
890
+ * Use the container's REAL height (which must have its own height, e.g. `height: 400px`).
891
+ * By default the height is derived from the width: if the `<svg>` set the height of a container with no
892
+ * height of its own, narrowing it would never lower the height and the map would lose its proportion.
893
+ */
894
+ useContainerHeight?: boolean;
895
+ };
896
+ /**
897
+ * Measures an element with `ResizeObserver`. Observing the DOM is a real side effect, so `useEffect`
898
+ * is the right tool here; whatever can be derived is derived while rendering.
899
+ *
900
+ * Only what was MEASURED is stored (real width and height); the height that comes from the ratio is
901
+ * computed while rendering. That way, if `aspectRatio` changes (e.g. `"auto"` when the geometry
902
+ * finishes loading) the height is corrected without waiting for another measurement.
903
+ *
904
+ * It never returns 0: a hidden or not-yet-measured container would give `width = 0` and
905
+ * `createProjection` throws on non-positive sizes. Until there is a valid measurement the previous one is kept.
906
+ */
907
+ declare const useContainerSize: <T extends HTMLElement = HTMLDivElement>({ initial, aspectRatio, useContainerHeight, }?: UseContainerSizeOptions) => ContainerSize & {
908
+ ref: RefObject<T | null>;
909
+ };
910
+
911
+ /** Level 2 (compound): `<Choropleth.Root>` + the pieces you want, in the order you want. */
912
+ declare const Choropleth: {
913
+ Root: <TDatum>(props: RootProps<TDatum> & {
914
+ ref?: react.Ref<ChoroplethMapHandle>;
915
+ }) => react.ReactElement | null;
916
+ Svg: ({ children, className, ariaLabel }: SvgProps) => react.JSX.Element;
917
+ Regions: () => react.JSX.Element;
918
+ Borders: () => react.JSX.Element | null;
919
+ Defs: ({ children }: DefsProps) => react.JSX.Element;
920
+ Patterns: () => react.JSX.Element | null;
921
+ Breadcrumb: ({ position, className, children }: BreadcrumbProps) => react.JSX.Element | null;
922
+ Bubbles: () => react.JSX.Element | null;
923
+ DrillLayers: ({ role }: {
924
+ role: "context" | "exiting";
925
+ }) => react.JSX.Element | null;
926
+ FallbackPoints: () => react.JSX.Element | null;
927
+ Selection: () => react.JSX.Element;
928
+ Viewport: ({ children }: {
929
+ children?: react.ReactNode;
930
+ }) => react.JSX.Element;
931
+ ZoomControls: ({ position, reset, className, }: ZoomControlsProps) => react.JSX.Element | null;
932
+ GestureHint: ({ className }: {
933
+ className?: string;
934
+ }) => react.JSX.Element;
935
+ Legend: ({ title, showCount, className, variant, interactive, children, }: LegendProps) => react.JSX.Element;
936
+ Tooltip: <TDatum>({ className, children }: TooltipProps<TDatum>) => react.JSX.Element | null;
937
+ };
938
+
939
+ /** Interior borders (a single path). Not drawn with GeoJSON: there each region carries its own stroke. */
940
+ declare const Borders: () => react.JSX.Element | null;
941
+
942
+ type BreadcrumbRenderContext = DrillNav;
943
+ type BreadcrumbProps = {
944
+ /** Corner of the map. Without it, it is not positioned: you place it yourself (useful outside the map). */
945
+ position?: Corner;
946
+ className?: string;
947
+ /** Draws the breadcrumbs with your design system: `({ items, drillTo, drillUp }) => …`. */
948
+ children?: (context: BreadcrumbRenderContext) => ReactNode;
949
+ };
950
+ /**
951
+ * Breadcrumbs for the drill route: `<nav>` + `<ol>`, root with the first level's label
952
+ * (or `labels.breadcrumbRoot`), the last step with `aria-current="page"` and a "←" button if you can go
953
+ * up. They are real `<button>`s: focus and keyboard for free. Each earlier crumb goes up to that level.
954
+ *
955
+ * It reflects the REQUESTED route, not the shown one: as soon as you click, the new crumb is there
956
+ * (while the camera flies). Without `drill` it draws nothing. With `children` (a render prop) the look is yours.
957
+ */
958
+ declare const Breadcrumb: ({ position, className, children }: BreadcrumbProps) => react.JSX.Element | null;
959
+
960
+ /**
961
+ * Bubbles (`bubble` variant). They already come sorted from largest to smallest, so the small ones end
962
+ * up on top.
963
+ *
964
+ * Two kinds, with different interaction:
965
+ * - **At a region's centroid** (`coordinates: null`): they do not capture the mouse; hover and click
966
+ * are handled by the region underneath (a `region` item).
967
+ * - **With their own coordinates** (stores): they are `point` items, with their own hover, click and tooltip.
968
+ */
969
+ declare const Bubbles: () => react.JSX.Element | null;
970
+
971
+ /**
972
+ * The drill layers that are NOT the active one, inside the camera:
973
+ * - `context`: the levels above, in gray and with no datum color. Cartographic reason: the parent's
974
+ * scale and the child's differ; seeing both in color invites comparing what is not comparable. Their
975
+ * drillable regions can be clicked (lateral navigation).
976
+ * - `exiting`: the level that was just left, fading out.
977
+ *
978
+ * The paths are each level's CACHED ones, in the pixels of its projection; the `<g>`'s `transform` (the
979
+ * affine map `R`, which is NOT the camera's) carries them to those of the active level: zero recomputation.
980
+ *
981
+ * `role="context"` goes BELOW the active regions and `role="exiting"` ABOVE (it fades over them).
982
+ */
983
+ declare const DrillLayers: ({ role }: {
984
+ role: "context" | "exiting";
985
+ }) => react.JSX.Element | null;
986
+
987
+ /**
988
+ * Microstates the geometry does not draw (Andorra, Monaco, Singapore at 1:110m), painted as a dot with
989
+ * their datum's color (`fallbackPoints`). They are `point` items: they have their own
990
+ * hover, click and tooltip. Not used in `bubble`: there they are bubbles.
991
+ */
992
+ declare const FallbackPoints: () => react.JSX.Element | null;
993
+
994
+ type LegendRenderContext = {
995
+ items: LegendItem[];
996
+ /** Size reference circles (`bubble` only). */
997
+ sizeItems: SizeLegendItem[];
998
+ /** `null` in `categorical` and in `bubble` without `getColorValue`. */
999
+ scale: UseChoroplethResult<unknown>["scale"];
1000
+ formatValue: (value: number) => string;
1001
+ };
1002
+ type LegendProps = {
1003
+ title?: string;
1004
+ /** Show the number of regions in each class. */
1005
+ showCount?: boolean;
1006
+ className?: string;
1007
+ /**
1008
+ * `"swatches"` (default): one row per class. `"bar"`: a bar with one segment per class and the breaks
1009
+ * underneath (newspaper style; more compact). `"bar"` only works with numeric classes: with categories
1010
+ * or a continuous scale it is drawn as usual.
1011
+ */
1012
+ variant?: "swatches" | "bar";
1013
+ /**
1014
+ * Hovering a class highlights its regions and dims the rest (default `true`). Without re-rendering
1015
+ * regions: the legend writes `data-highlight-class` on the root and the CSS does the rest.
1016
+ */
1017
+ interactive?: boolean;
1018
+ /** Headless legend: you receive the items and draw it yourself with your design system. */
1019
+ children?: (context: LegendRenderContext) => ReactNode;
1020
+ };
1021
+ /** HTML legend. By default: swatches with ranges (classes) or a gradient bar (`linear`). */
1022
+ declare const Legend: ({ title, showCount, className, variant, interactive, children, }: LegendProps) => react.JSX.Element;
1023
+
1024
+ /**
1025
+ * The colored regions. ONE listener per event type on the `<g>` (delegation) instead of one
1026
+ * per `<path>`: with hundreds of regions it is less work and no closures are recreated.
1027
+ * Pointer Events: the same code for mouse, pen and touch.
1028
+ */
1029
+ declare const Regions: () => react.JSX.Element;
1030
+
1031
+ type RootProps<TDatum> = UseChoroplethOptions<TDatum> & {
1032
+ children?: ReactNode;
1033
+ className?: string;
1034
+ style?: CSSProperties;
1035
+ /** Does not apply the base styles of `styles.css` (the `--cm-*` variables you set still work). */
1036
+ unstyled?: boolean;
1037
+ /**
1038
+ * Theme: a name (`"light"`, `"dark"`, `"minimal"`, `"contrast"`) or an object (`defineTheme`, `extendTheme`).
1039
+ * It is translated into inline `--cm-*` variables on `.cm-root` (`themeToCssVars`), so they beat those you
1040
+ * put in the CSS; to override one by hand, use `style`.
1041
+ */
1042
+ theme?: ThemeName | ChoroplethTheme;
1043
+ /** Pattern over regions without data. It beats the theme's (`empty.pattern`); `"none"` removes it. */
1044
+ emptyPattern?: "none" | "hatch" | "dots";
1045
+ /** One class of yours per piece, ADDED to the `cm-*` one (Tailwind, CSS Modules). */
1046
+ classNames?: ChoroplethClassNames;
1047
+ };
1048
+ /**
1049
+ * Level 2: calls `useChoropleth` ONCE and distributes the result by Context to its children.
1050
+ * It is the measured container (`containerRef`) and the tooltip's position reference.
1051
+ *
1052
+ * `ref` gives the imperative handle (`zoomTo`, `zoomBy`, `resetView`, `getView`). `forwardRef` is used
1053
+ * (and not `ref` as a prop, which only exists in React 19) so that it also works in React 18.
1054
+ */
1055
+ declare const Root: <TDatum>(props: RootProps<TDatum> & {
1056
+ ref?: Ref<ChoroplethMapHandle>;
1057
+ }) => ReactElement | null;
1058
+
1059
+ /**
1060
+ * Highlights the hovered item and the selected region, ON TOP of the borders: if their outline were
1061
+ * painted in `Regions`, the neighbors (painted later) would half-cover it. `pointerEvents: none` so
1062
+ * this layer does not steal the mouse from what is underneath.
1063
+ *
1064
+ * A region is highlighted with a copy of its outline; a point, with a ring around its bubble.
1065
+ */
1066
+ declare const Selection: () => react.JSX.Element;
1067
+
1068
+ type SvgProps = {
1069
+ /**
1070
+ * Map layers, bottom to top. By default, inside a `Viewport` (the camera): `Regions`, `Borders`,
1071
+ * `Bubbles`, `FallbackPoints` and `Selection`. If you pass your own layers, wrap them in
1072
+ * `<Choropleth.Viewport>` so they move with the zoom.
1073
+ */
1074
+ children?: ReactNode;
1075
+ className?: string;
1076
+ ariaLabel?: string;
1077
+ };
1078
+ /**
1079
+ * The `<svg>`. It exists as its own piece because `Root` mixes SVG (regions, borders) and HTML
1080
+ * (legend, tooltip), and an HTML element cannot live inside an `<svg>`.
1081
+ * (It is the only difference from the documented example.)
1082
+ *
1083
+ * While the asynchronous geometry has not arrived, it draws a skeleton with the same proportion the
1084
+ * map will have: the page does not jump when it appears.
1085
+ */
1086
+ declare const Svg: ({ children, className, ariaLabel }: SvgProps) => react.JSX.Element;
1087
+
1088
+ /**
1089
+ * The "Use Ctrl + scroll to zoom" hint (⌘ on macOS) or "Use two fingers to move the map"
1090
+ *. It only appears with `wheel: "cooperative"`, when a gesture does NOT zoom but can
1091
+ * be taught how: with `"always"` there is nothing to teach. It lasts 1.5 s.
1092
+ *
1093
+ * It is `aria-hidden`: a visual aid, not a message for screen readers (keyboard zoom and the buttons
1094
+ * are already accessible). It is always in the DOM and shown with an attribute + CSS: that way it
1095
+ * fades in and out, and the text does not disappear before it finishes fading.
1096
+ */
1097
+ declare const GestureHint: ({ className }: {
1098
+ className?: string;
1099
+ }) => react.JSX.Element;
1100
+
1101
+ /**
1102
+ * The camera: the `<g>` to which `d3-zoom` applies the zoom and pan `transform`.
1103
+ * Everything that must move with the map (regions, borders, bubbles, selection) goes inside.
1104
+ *
1105
+ * ⚠️ **React never writes this `<g>`'s `transform` attribute**; that is why no prop touches it. It is
1106
+ * written by `d3-zoom` every frame. If React wrote it, any render would overwrite d3's
1107
+ * internal state and the map would jump.
1108
+ */
1109
+ declare const Viewport: ({ children }: {
1110
+ children?: ReactNode;
1111
+ }) => react.JSX.Element;
1112
+
1113
+ type ZoomControlsProps = {
1114
+ /** Corner of the map. Defaults to `"bottom-right"`. */
1115
+ position?: Corner;
1116
+ /** Show "reset view". Defaults to `true`. */
1117
+ reset?: boolean;
1118
+ className?: string;
1119
+ };
1120
+ /**
1121
+ * `+` / `−` / `⟲` buttons. They are real `<button>`s (focus, keyboard and screen
1122
+ * reader for free), with `aria-label` from `labels`.
1123
+ *
1124
+ * They are disabled according to the confirmed camera: `−` at minimum zoom, `+` at maximum and `⟲` if
1125
+ * you are already at the fit (it does not promise what it cannot do). They subscribe to the camera
1126
+ * through the store: a gesture does not re-render the map, only these buttons and only when the gesture ends.
1127
+ *
1128
+ * Opt-in: in the preset, `zoom={{ controls: true }}`; in the compound, put it wherever you like.
1129
+ */
1130
+ declare const ZoomControls: ({ position, reset, className, }: ZoomControlsProps) => react.JSX.Element | null;
1131
+
1132
+ /** What the tooltip receives: the `MapItem` (region or point) + value, color and name. */
1133
+ type TooltipContext<TDatum> = ItemContext<TDatum>;
1134
+ type TooltipProps<TDatum> = {
1135
+ className?: string;
1136
+ /** Tooltip content. By default: the region's name and formatted value. */
1137
+ children?: (context: TooltipContext<TDatum>) => ReactNode;
1138
+ };
1139
+ /**
1140
+ * HTML tooltip next to the cursor. The library decides WHAT (typed context) and WHERE; you, the look.
1141
+ *
1142
+ * It subscribes by itself to the hover and the cursor (`useMapStore`): moving it re-renders neither
1143
+ * `Root` nor the regions. Without measuring its size, it flips to the other side of the cursor in the
1144
+ * right / bottom half of the map so as not to overflow the container.
1145
+ */
1146
+ declare const Tooltip: <TDatum>({ className, children }: TooltipProps<TDatum>) => react.JSX.Element | null;
1147
+
1148
+ type LegendPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
1149
+ type LegendOptions = {
1150
+ title?: string;
1151
+ /** Corner of the map where it is overlaid. Defaults to `bottom-left`. */
1152
+ position?: LegendPosition;
1153
+ showCount?: boolean;
1154
+ /** `"swatches"` (default) or `"bar"`: a bar with one segment per class and the breaks underneath. */
1155
+ variant?: "swatches" | "bar";
1156
+ /** Hovering a class highlights its regions (default `true`). */
1157
+ interactive?: boolean;
1158
+ };
1159
+ type ChoroplethMapProps<TDatum> = UseChoroplethOptions<TDatum> & {
1160
+ /** `false` hides the legend. */
1161
+ legend?: LegendOptions | false;
1162
+ /** Drill breadcrumbs (only with `drill`). `false` hides them; top-left by default. */
1163
+ breadcrumb?: {
1164
+ position?: LegendPosition;
1165
+ } | false;
1166
+ /** Tooltip content. Without this: the region's name and formatted value. */
1167
+ renderTooltip?: (context: TooltipContext<TDatum>) => ReactNode;
1168
+ className?: string;
1169
+ style?: CSSProperties;
1170
+ unstyled?: boolean;
1171
+ /** `"light"` · `"dark"` · `"minimal"` · `"contrast"` or an object (`defineTheme`, `extendTheme`). */
1172
+ theme?: ThemeName | ChoroplethTheme;
1173
+ /** Pattern over regions without data (`"hatch"`, `"dots"`); it beats the theme's. */
1174
+ emptyPattern?: "none" | "hatch" | "dots";
1175
+ /** One class of yours per piece, ADDED to the `cm-*` one (Tailwind, CSS Modules). */
1176
+ classNames?: ChoroplethClassNames;
1177
+ ariaLabel?: string;
1178
+ };
1179
+ /**
1180
+ * Level 1 (preset). `ref` gives the imperative handle: `ref.current.zoomTo("ES")`.
1181
+ */
1182
+ declare const ChoroplethMap: <TDatum>(props: ChoroplethMapProps<TDatum> & {
1183
+ ref?: Ref<ChoroplethMapHandle>;
1184
+ }) => ReactElement | null;
1185
+
1186
+ export { Borders, Breadcrumb, type BreadcrumbProps, type BreadcrumbRenderContext, Bubbles, CameraOptions, Choropleth, type ChoroplethClassNames, ChoroplethMap, type ChoroplethMapHandle, type ChoroplethMapProps, ChoroplethTheme, type ClassSlot, type ContainerSize, DRILL_BRIDGE, Defs, type DefsProps, type DimOthers, type DrillBreadcrumb, type DrillBridge, DrillContext, type DrillController, type DrillEngine, type DrillEngineParams, type DrillEvent, type DrillLayerView, DrillLayers, type DrillLevel, type DrillLevelInfo, type DrillNav, type DrillNavItem, type DrillStatus, FALLBACK_POINT_RADIUS, type FallbackPoint, FallbackPoints, type GeographyStatus, GestureHint, type ItemContext, type ItemRef, type JoinReport, Legend, type LegendOptions, type LegendPosition, type LegendProps, type LegendRenderContext, type MapItem, type MapState, type MapStore, Patterns, type PointItem, type PointProps, type PointerPosition, REVEAL_MS, ROOT_LEVEL, type Region, type RegionItem, type RegionProps, type RegionStyle, Regions, Root, type RootProps, Selection, Svg, type SvgProps, ThemeName, Tooltip, type TooltipContext, type TooltipProps, TransitionResult, type UseChoroplethOptions, type UseChoroplethResult, type UseContainerSizeOptions, type UseDrillDownOptions, type UseGeographyResult, type UseZoomParams, ViewState, Viewport, type ZoomApi, ZoomControls, type ZoomControlsProps, ZoomOptions, ZoomTarget, buildDrillEvent, clearGeographyCache, createDrillBridge, createMapStore, defineDrillLevels, isDrillController, isDrillable, sourceDepth, useCamera, useChoropleth, useChoroplethApi, useContainerSize, useDefId, useDrillChoreography, useDrillDown, useDrillEngine, useGeography, useMapStore, useZoom };