@d3-maps/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Georgii Bukharov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.css ADDED
@@ -0,0 +1,9 @@
1
+ .d3-map {
2
+ height: 100%;
3
+ width: auto;
4
+ max-width: 100%;
5
+ }
6
+ .d3-map-zoom path {
7
+ vector-effect: non-scaling-stroke;
8
+ }
9
+
@@ -0,0 +1,501 @@
1
+ import { GeoPath, GeoProjection } from "d3-geo";
2
+ import { D3ZoomEvent, D3ZoomEvent as D3ZoomEvent$1, ZoomBehavior, ZoomBehavior as ZoomBehavior$1, ZoomTransform, ZoomTransform as ZoomTransform$1 } from "d3-zoom";
3
+
4
+ //#region ../../node_modules/.pnpm/@types+geojson@7946.0.16/node_modules/@types/geojson/index.d.ts
5
+
6
+ /**
7
+ * The valid values for the "type" property of GeoJSON geometry objects.
8
+ * https://tools.ietf.org/html/rfc7946#section-1.4
9
+ */
10
+ type GeoJsonGeometryTypes = Geometry["type"];
11
+ /**
12
+ * The value values for the "type" property of GeoJSON Objects.
13
+ * https://tools.ietf.org/html/rfc7946#section-1.4
14
+ */
15
+ type GeoJsonTypes = GeoJSON["type"];
16
+ /**
17
+ * Bounding box
18
+ * https://tools.ietf.org/html/rfc7946#section-5
19
+ */
20
+ type BBox = [number, number, number, number] | [number, number, number, number, number, number];
21
+ /**
22
+ * A Position is an array of coordinates.
23
+ * https://tools.ietf.org/html/rfc7946#section-3.1.1
24
+ * Array should contain between two and three elements.
25
+ * The previous GeoJSON specification allowed more elements (e.g., which could be used to represent M values),
26
+ * but the current specification only allows X, Y, and (optionally) Z to be defined.
27
+ *
28
+ * Note: the type will not be narrowed down to `[number, number] | [number, number, number]` due to
29
+ * marginal benefits and the large impact of breaking change.
30
+ *
31
+ * See previous discussions on the type narrowing:
32
+ * - {@link https://github.com/DefinitelyTyped/DefinitelyTyped/pull/21590|Nov 2017}
33
+ * - {@link https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/67773|Dec 2023}
34
+ * - {@link https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/71441| Dec 2024}
35
+ *
36
+ * One can use a
37
+ * {@link https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates|user-defined type guard that returns a type predicate}
38
+ * to determine if a position is a 2D or 3D position.
39
+ *
40
+ * @example
41
+ * import type { Position } from 'geojson';
42
+ *
43
+ * type StrictPosition = [x: number, y: number] | [x: number, y: number, z: number]
44
+ *
45
+ * function isStrictPosition(position: Position): position is StrictPosition {
46
+ * return position.length === 2 || position.length === 3
47
+ * };
48
+ *
49
+ * let position: Position = [-116.91, 45.54];
50
+ *
51
+ * let x: number;
52
+ * let y: number;
53
+ * let z: number | undefined;
54
+ *
55
+ * if (isStrictPosition(position)) {
56
+ * // `tsc` would throw an error if we tried to destructure a fourth parameter
57
+ * [x, y, z] = position;
58
+ * } else {
59
+ * throw new TypeError("Position is not a 2D or 3D point");
60
+ * }
61
+ */
62
+ type Position = number[];
63
+ /**
64
+ * The base GeoJSON object.
65
+ * https://tools.ietf.org/html/rfc7946#section-3
66
+ * The GeoJSON specification also allows foreign members
67
+ * (https://tools.ietf.org/html/rfc7946#section-6.1)
68
+ * Developers should use "&" type in TypeScript or extend the interface
69
+ * to add these foreign members.
70
+ */
71
+ interface GeoJsonObject {
72
+ // Don't include foreign members directly into this type def.
73
+ // in order to preserve type safety.
74
+ // [key: string]: any;
75
+ /**
76
+ * Specifies the type of GeoJSON object.
77
+ */
78
+ type: GeoJsonTypes;
79
+ /**
80
+ * Bounding box of the coordinate range of the object's Geometries, Features, or Feature Collections.
81
+ * The value of the bbox member is an array of length 2*n where n is the number of dimensions
82
+ * represented in the contained geometries, with all axes of the most southwesterly point
83
+ * followed by all axes of the more northeasterly point.
84
+ * The axes order of a bbox follows the axes order of geometries.
85
+ * https://tools.ietf.org/html/rfc7946#section-5
86
+ */
87
+ bbox?: BBox | undefined;
88
+ }
89
+ /**
90
+ * Union of GeoJSON objects.
91
+ */
92
+ type GeoJSON<G extends Geometry | null = Geometry, P$1 = GeoJsonProperties> = G | Feature<G, P$1> | FeatureCollection<G, P$1>;
93
+ /**
94
+ * Geometry object.
95
+ * https://tools.ietf.org/html/rfc7946#section-3
96
+ */
97
+ type Geometry = Point$1 | MultiPoint$1 | LineString$1 | MultiLineString$1 | Polygon$1 | MultiPolygon$1 | GeometryCollection$1;
98
+ /**
99
+ * Point geometry object.
100
+ * https://tools.ietf.org/html/rfc7946#section-3.1.2
101
+ */
102
+ interface Point$1 extends GeoJsonObject {
103
+ type: "Point";
104
+ coordinates: Position;
105
+ }
106
+ /**
107
+ * MultiPoint geometry object.
108
+ * https://tools.ietf.org/html/rfc7946#section-3.1.3
109
+ */
110
+ interface MultiPoint$1 extends GeoJsonObject {
111
+ type: "MultiPoint";
112
+ coordinates: Position[];
113
+ }
114
+ /**
115
+ * LineString geometry object.
116
+ * https://tools.ietf.org/html/rfc7946#section-3.1.4
117
+ */
118
+ interface LineString$1 extends GeoJsonObject {
119
+ type: "LineString";
120
+ coordinates: Position[];
121
+ }
122
+ /**
123
+ * MultiLineString geometry object.
124
+ * https://tools.ietf.org/html/rfc7946#section-3.1.5
125
+ */
126
+ interface MultiLineString$1 extends GeoJsonObject {
127
+ type: "MultiLineString";
128
+ coordinates: Position[][];
129
+ }
130
+ /**
131
+ * Polygon geometry object.
132
+ * https://tools.ietf.org/html/rfc7946#section-3.1.6
133
+ */
134
+ interface Polygon$1 extends GeoJsonObject {
135
+ type: "Polygon";
136
+ coordinates: Position[][];
137
+ }
138
+ /**
139
+ * MultiPolygon geometry object.
140
+ * https://tools.ietf.org/html/rfc7946#section-3.1.7
141
+ */
142
+ interface MultiPolygon$1 extends GeoJsonObject {
143
+ type: "MultiPolygon";
144
+ coordinates: Position[][][];
145
+ }
146
+ /**
147
+ * Geometry Collection
148
+ * https://tools.ietf.org/html/rfc7946#section-3.1.8
149
+ */
150
+ interface GeometryCollection$1<G extends Geometry = Geometry> extends GeoJsonObject {
151
+ type: "GeometryCollection";
152
+ geometries: G[];
153
+ }
154
+ type GeoJsonProperties = {
155
+ [name: string]: any;
156
+ } | null;
157
+ /**
158
+ * A feature object which contains a geometry and associated properties.
159
+ * https://tools.ietf.org/html/rfc7946#section-3.2
160
+ */
161
+ interface Feature<G extends Geometry | null = Geometry, P$1 = GeoJsonProperties> extends GeoJsonObject {
162
+ type: "Feature";
163
+ /**
164
+ * The feature's geometry
165
+ */
166
+ geometry: G;
167
+ /**
168
+ * A value that uniquely identifies this feature in a
169
+ * https://tools.ietf.org/html/rfc7946#section-3.2.
170
+ */
171
+ id?: string | number | undefined;
172
+ /**
173
+ * Properties associated with this feature.
174
+ */
175
+ properties: P$1;
176
+ }
177
+ /**
178
+ * A collection of feature objects.
179
+ * https://tools.ietf.org/html/rfc7946#section-3.3
180
+ */
181
+ interface FeatureCollection<G extends Geometry | null = Geometry, P$1 = GeoJsonProperties> extends GeoJsonObject {
182
+ type: "FeatureCollection";
183
+ features: Array<Feature<G, P$1>>;
184
+ }
185
+ //#endregion
186
+ //#region src/lib/mapObject.d.ts
187
+ type MapObject = Point$1 | Feature;
188
+ type MapObjectFocusEventType = 'focus' | 'blur';
189
+ type MapObjectMouseEventType = 'mouseenter' | 'mouseleave' | 'mousedown' | 'mouseup';
190
+ type MapObjectEventType = MapObjectFocusEventType | MapObjectMouseEventType;
191
+ type MapObjectEvent<E> = E extends MapObjectFocusEventType ? FocusEvent : MouseEvent;
192
+ /**
193
+ * Supported interaction states for map objects.
194
+ */
195
+ declare const mapObjectState: readonly ["default", "hover", "active"];
196
+ type MapObjectState = typeof mapObjectState[number];
197
+ type MapObjectStyles<TStyle> = Partial<Record<MapObjectState, TStyle>>;
198
+ /**
199
+ * Maps DOM event names to interaction state updates.
200
+ */
201
+ declare function getObjectStateUpdate(event: MapObjectEventType): MapObjectState;
202
+ /**
203
+ * Resolves a style value for the current state (falls back to `default`).
204
+ */
205
+ declare function resolveObjectStyle<TStyle>(state: MapObjectState, styles?: MapObjectStyles<TStyle>): TStyle | undefined;
206
+ //#endregion
207
+ //#region src/lib/feature.d.ts
208
+ /**
209
+ * A GeoJSON Feature used by d3-maps.
210
+ *
211
+ * This type allows extra top-level fields to be attached in `dataTransformer` (e.g. choropleth colors).
212
+ */
213
+ type MapFeature = (Feature & Record<string, unknown>) | Feature;
214
+ /**
215
+ * Shared props contract for a single rendered feature.
216
+ */
217
+ interface MapFeatureProps<TStyle = unknown> {
218
+ data: MapFeature;
219
+ styles?: MapObjectStyles<TStyle>;
220
+ fill?: string;
221
+ stroke?: string;
222
+ }
223
+ /**
224
+ * Resolves a stable key for a feature.
225
+ *
226
+ * Checks:
227
+ * 1) `feature[idKey]`
228
+ * 2) `feature.properties[idKey]`
229
+ * 3) fallback to the list index
230
+ */
231
+ declare function getFeatureKey(feature: MapFeature, idKey: string | undefined, index: number): string | number;
232
+ //#endregion
233
+ //#region ../../node_modules/.pnpm/@types+topojson-specification@1.0.5/node_modules/@types/topojson-specification/index.d.ts
234
+ // ---------------------------------------------------------------
235
+ // TopoJSON Format Specification
236
+ // ---------------------------------------------------------------
237
+
238
+ // See: https://github.com/topojson/topojson-specification/
239
+
240
+ // 2. TopoJSON Objects
241
+ interface TopoJSON {
242
+ type: "Topology" | GeoJsonGeometryTypes | null;
243
+ bbox?: BBox | undefined;
244
+ }
245
+ // 2.1. Topology Objects
246
+ interface Topology<T extends Objects<Properties> = Objects<Properties>> extends TopoJSON {
247
+ type: "Topology";
248
+ objects: T;
249
+ arcs: Arc[];
250
+ transform?: Transform | undefined;
251
+ }
252
+ // 2.1.1. Positions
253
+ type Positions = number[];
254
+ // at least two elements
255
+
256
+ // 2.1.2. Transforms
257
+ interface Transform {
258
+ scale: [number, number];
259
+ translate: [number, number];
260
+ }
261
+ // 2.1.3. Arcs
262
+ type Arc = Positions[];
263
+ // at least two elements
264
+
265
+ // 2.1.4. Arc Indexes
266
+ type ArcIndexes = number[];
267
+ // 2.1.5. Objects
268
+ type Properties = GeoJsonProperties;
269
+ interface Objects<P$1 extends Properties = {}> {
270
+ [key: string]: GeometryObject<P$1>;
271
+ }
272
+ // 2.2. Geometry Objects
273
+ interface GeometryObjectA<P$1 extends Properties = {}> extends TopoJSON {
274
+ type: GeoJsonGeometryTypes | null;
275
+ id?: number | string | undefined;
276
+ properties?: P$1 | undefined;
277
+ }
278
+ type GeometryObject<P$1 extends Properties = {}> = Point<P$1> | MultiPoint<P$1> | LineString<P$1> | MultiLineString<P$1> | Polygon<P$1> | MultiPolygon<P$1> | GeometryCollection<P$1> | NullObject;
279
+ // 2.2.1. Point
280
+ interface Point<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
281
+ type: "Point";
282
+ coordinates: Positions;
283
+ }
284
+ // 2.2.2. MultiPoint
285
+ interface MultiPoint<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
286
+ type: "MultiPoint";
287
+ coordinates: Positions[];
288
+ }
289
+ // 2.2.3. LineString
290
+ interface LineString<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
291
+ type: "LineString";
292
+ arcs: ArcIndexes;
293
+ }
294
+ // 2.2.4. MultiLineString
295
+ interface MultiLineString<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
296
+ type: "MultiLineString";
297
+ arcs: ArcIndexes[];
298
+ }
299
+ // 2.2.5. Polygon
300
+ interface Polygon<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
301
+ type: "Polygon";
302
+ arcs: ArcIndexes[];
303
+ }
304
+ // 2.2.6. MultiPolygon
305
+ interface MultiPolygon<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
306
+ type: "MultiPolygon";
307
+ arcs: ArcIndexes[][];
308
+ }
309
+ // 2.2.7. Geometry Collection
310
+ interface GeometryCollection<P$1 extends Properties = {}> extends GeometryObjectA<P$1> {
311
+ type: "GeometryCollection";
312
+ geometries: Array<GeometryObject<P$1>>;
313
+ }
314
+ // More
315
+ interface NullObject extends GeometryObjectA {
316
+ type: null;
317
+ }
318
+ //#endregion
319
+ //#region src/lib/map.d.ts
320
+ type MapData = FeatureCollection | Topology;
321
+ type DataTransformer = (features: MapFeature[]) => MapFeature[];
322
+ /**
323
+ * Configuration for a d3-geo projection.
324
+ *
325
+ * d3-maps applies these options (if provided) before fitting the geometry to the map size.
326
+ */
327
+ interface ProjectionConfig {
328
+ center?: [number, number];
329
+ rotate?: [number, number, number];
330
+ scale?: number;
331
+ }
332
+ /**
333
+ * Input configuration for creating a map context.
334
+ *
335
+ * In adapters, this is usually passed as component props.
336
+ */
337
+ interface MapConfig {
338
+ width?: number;
339
+ height?: number;
340
+ aspectRatio?: number;
341
+ /**
342
+ * Projection factory from d3-geo (or a compatible implementation).
343
+ *
344
+ * Example: `geoEqualEarth`.
345
+ */
346
+ projection?: () => GeoProjection;
347
+ projectionConfig?: ProjectionConfig;
348
+ /**
349
+ * TopoJSON or GeoJSON input.
350
+ *
351
+ * TopoJSON is automatically converted to GeoJSON features.
352
+ */
353
+ data: MapData;
354
+ /**
355
+ * Optional feature transformer (filter/augment/normalize features).
356
+ */
357
+ dataTransformer?: DataTransformer;
358
+ }
359
+ /**
360
+ * Fully computed, framework-agnostic map context.
361
+ *
362
+ * Adapters provide this context to child layers (features, markers, custom SVG).
363
+ */
364
+ interface MapContext {
365
+ width: number;
366
+ height: number;
367
+ projection?: GeoProjection;
368
+ features: MapFeature[];
369
+ path: GeoPath;
370
+ renderPath: (feature: Feature) => ReturnType<GeoPath>;
371
+ }
372
+ /**
373
+ * Creates a configured projection and fits it to the provided GeoJSON (if present).
374
+ */
375
+ declare function makeProjection({
376
+ width,
377
+ height,
378
+ config,
379
+ projection,
380
+ geoJson
381
+ }: {
382
+ width: number;
383
+ height: number;
384
+ config?: ProjectionConfig;
385
+ projection: () => GeoProjection;
386
+ geoJson?: FeatureCollection;
387
+ }): GeoProjection;
388
+ /**
389
+ * Normalizes input map data to GeoJSON features.
390
+ *
391
+ * - TopoJSON is converted via `topojson-client`.
392
+ * - If provided, `dataTransformer` is applied to the feature array.
393
+ */
394
+ declare function makeFeatures(geoData: MapData, dataTransformer?: DataTransformer): [features: MapFeature[], geoJson: FeatureCollection];
395
+ declare const makePathFn: (mapProjection: GeoProjection) => GeoPath;
396
+ /**
397
+ * Creates a full {@link MapContext} from a {@link MapConfig}.
398
+ */
399
+ declare function makeMapContext({
400
+ width,
401
+ height: passedHeight,
402
+ aspectRatio,
403
+ data,
404
+ dataTransformer,
405
+ projection: providedProjection,
406
+ projectionConfig
407
+ }: MapConfig): MapContext;
408
+ /**
409
+ * Type guard for TopoJSON topology inputs.
410
+ */
411
+ declare function isTopology(data: MapData): data is Topology;
412
+ //#endregion
413
+ //#region src/lib/marker.d.ts
414
+ type MapMarkerCoordinates = [number, number];
415
+ /**
416
+ * Shared props contract for marker layers.
417
+ */
418
+ interface MapMarkerProps<TStyle = unknown> {
419
+ coordinates?: MapMarkerCoordinates;
420
+ styles?: MapObjectStyles<TStyle>;
421
+ }
422
+ /**
423
+ * Computes an SVG transform (`translate(x, y)`) for the given coordinates using the active projection.
424
+ *
425
+ * Coordinates must be `[longitude, latitude]`.
426
+ */
427
+ declare function getMarkerTransform(context: MapContext | undefined, coordinates: MapMarkerCoordinates, fallback?: string): string;
428
+ //#endregion
429
+ //#region src/lib/utils.d.ts
430
+ declare function isString(value: unknown): value is string;
431
+ declare function isDefined<T>(value: T | null | undefined): value is T;
432
+ declare const isNullish: (value: unknown) => value is null | undefined;
433
+ declare const isNumber: (value: unknown) => value is number;
434
+ declare const isStringOrNumber: (value: unknown) => value is string | number;
435
+ declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
436
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
437
+ declare function get<T>(url: string): Promise<T>;
438
+ declare const makeTransform: (x: number, y: number, k?: number) => string;
439
+ //#endregion
440
+ //#region src/lib/zoom.d.ts
441
+ type Extent = [[number, number], [number, number]];
442
+ interface ZoomConfigOptions {
443
+ minZoom?: number;
444
+ maxZoom?: number;
445
+ translateExtent?: Extent;
446
+ }
447
+ interface ZoomConfig {
448
+ scaleExtent: [number, number];
449
+ translateExtent: Extent;
450
+ }
451
+ type ZoomBehaviorOwnMethodName<TElement extends Element, TDatum> = Exclude<keyof ZoomBehavior$1<TElement, TDatum>, keyof Function>;
452
+ type ZoomBehaviorMethodName<TElement extends Element, TDatum> = Extract<{ [K in ZoomBehaviorOwnMethodName<TElement, TDatum>]: ZoomBehavior$1<TElement, TDatum>[K] extends ((...args: unknown[]) => unknown) ? K : never }[ZoomBehaviorOwnMethodName<TElement, TDatum>], string>;
453
+ type ZoomBehaviorMethodArgs<TElement extends Element, TDatum, TMethod extends ZoomBehaviorMethodName<TElement, TDatum>> = ZoomBehavior$1<TElement, TDatum>[TMethod] extends ((...args: infer TArgs) => unknown) ? TArgs : never;
454
+ type ZoomBehaviorSingleArg<TElement extends Element, TDatum, TMethod extends ZoomBehaviorMethodName<TElement, TDatum>> = ZoomBehaviorMethodArgs<TElement, TDatum, TMethod> extends [infer TArg] ? TArg : never;
455
+ type ZoomModifierValue<TElement extends Element, TDatum, TMethod extends ZoomBehaviorMethodName<TElement, TDatum>> = ZoomBehaviorMethodArgs<TElement, TDatum, TMethod> | ZoomBehaviorSingleArg<TElement, TDatum, TMethod>;
456
+ type ZoomModifiers<TElement extends Element = SVGSVGElement, TDatum = unknown> = Partial<{ [K in ZoomBehaviorMethodName<TElement, TDatum>]: ZoomModifierValue<TElement, TDatum, K> }>;
457
+ interface ZoomProps<TElement extends Element = SVGSVGElement, TDatum = unknown> {
458
+ center?: [number, number];
459
+ zoom?: number;
460
+ minZoom?: number;
461
+ maxZoom?: number;
462
+ translateExtent?: Extent;
463
+ modifiers?: ZoomModifiers<TElement, TDatum>;
464
+ }
465
+ interface ZoomEvent extends D3ZoomEvent$1<SVGSVGElement, unknown> {}
466
+ interface ZoomEvents {
467
+ onZoomStart?: (event: ZoomEvent) => void;
468
+ onZoom?: (event: ZoomEvent) => void;
469
+ onZoomEnd?: (event: ZoomEvent) => void;
470
+ }
471
+ interface ZoomBehaviorOptions<TElement extends Element = SVGSVGElement, TDatum = unknown> extends ZoomProps<TElement, TDatum>, ZoomEvents {}
472
+ type ZoomScaleSource = number | ZoomTransform$1 | {
473
+ transform: ZoomTransform$1;
474
+ };
475
+ type ZoomTargetElement = SVGSVGElement | SVGGElement;
476
+ declare const ZOOM_DEFAULTS: {
477
+ center: [number, number];
478
+ zoom: number;
479
+ minZoom: number;
480
+ maxZoom: number;
481
+ extent: Extent;
482
+ };
483
+ interface ApplyZoomOptions {
484
+ element: ZoomTargetElement | null | undefined;
485
+ behavior: ZoomBehavior$1<SVGSVGElement, unknown>;
486
+ center?: [number, number];
487
+ zoom?: number;
488
+ }
489
+ interface SetupZoomOptions extends ApplyZoomOptions {}
490
+ declare function getDefaultTranslateExtent(context?: MapContext): Extent;
491
+ declare function createZoomTransform(center: [number, number], zoomLevel: number): ZoomTransform$1;
492
+ declare function createZoomConfig(options: ZoomConfigOptions): ZoomConfig;
493
+ declare function createZoomBehavior<TElement extends Element = SVGSVGElement, TDatum = unknown>(context?: MapContext, options?: ZoomBehaviorOptions<TElement, TDatum>): ZoomBehavior$1<TElement, TDatum>;
494
+ declare function attachZoomBehavior(element: ZoomTargetElement | null | undefined, behavior: ZoomBehavior$1<SVGSVGElement, unknown>): void;
495
+ declare function applyZoomBehaviorTransform(element: ZoomTargetElement | null | undefined, behavior: ZoomBehavior$1<SVGSVGElement, unknown>, transform: ZoomTransform$1): void;
496
+ declare function applyZoomTransform(options: ApplyZoomOptions): void;
497
+ declare function setupZoom(options: SetupZoomOptions): void;
498
+ declare function getZoomScale(source: ZoomScaleSource): number;
499
+ declare function getInverseZoomScale(source: ZoomScaleSource, fallback?: number): number;
500
+ //#endregion
501
+ export { ApplyZoomOptions, type D3ZoomEvent, DataTransformer, Extent, MapConfig, MapContext, MapData, MapFeature, MapFeatureProps, MapMarkerCoordinates, MapMarkerProps, MapObject, MapObjectEvent, MapObjectEventType, MapObjectFocusEventType, MapObjectMouseEventType, MapObjectState, MapObjectStyles, ProjectionConfig, SetupZoomOptions, ZOOM_DEFAULTS, type ZoomBehavior, ZoomBehaviorMethodArgs, ZoomBehaviorMethodName, ZoomBehaviorOptions, ZoomBehaviorOwnMethodName, ZoomBehaviorSingleArg, ZoomConfig, ZoomConfigOptions, ZoomEvent, ZoomEvents, ZoomModifierValue, ZoomModifiers, ZoomProps, ZoomScaleSource, ZoomTargetElement, type ZoomTransform, applyZoomBehaviorTransform, applyZoomTransform, attachZoomBehavior, createZoomBehavior, createZoomConfig, createZoomTransform, get, getDefaultTranslateExtent, getFeatureKey, getInverseZoomScale, getMarkerTransform, getObjectStateUpdate, getZoomScale, isDefined, isFunction, isNullish, isNumber, isPlainObject, isString, isStringOrNumber, isTopology, makeFeatures, makeMapContext, makePathFn, makeProjection, makeTransform, mapObjectState, resolveObjectStyle, setupZoom };
@@ -0,0 +1 @@
1
+ (function(e,t,n,r,i){function a(e){return typeof e==`string`}function o(e){return e!==`undefined`}let s=e=>e==null,c=e=>Number.isFinite(e),l=e=>a(e)||c(e);function u(e){return typeof e==`function`}function d(e){if(Object.prototype.toString.call(e)!==`[object Object]`)return!1;let t=Object.getPrototypeOf(e);return t===null||t===Object.prototype}async function f(e){return(await fetch(e)).json()}let p=(e,t,n)=>`translate(${e}, ${t}) scale(${n??1})`;function m(e,t=`id`,n){let r=e[t];if(l(r))return r;let i=e.properties?.[t];return l(i)?i:n}function h({width:e,height:t,config:n,projection:r,geoJson:i}){let a=r();if(n?.center){let[e,t]=n.center;c(e)&&c(t)&&a.center([e,t])}if(n?.rotate){let[e,t,r]=n.rotate;c(e)&&c(t)&&a.rotate([e,t,c(r)?r:0])}return n&&c(n.scale)&&a.scale(n.scale),i?a.fitSize([e,t],i):a.translate([e/2,t/2]),a}function g(e,t){let r;if(y(e)){let t=Object.keys(e.objects)[0];if(t){let i=e.objects[t],a=(0,n.feature)(e,i);r=a.type===`FeatureCollection`?a:{type:`FeatureCollection`,features:[a]}}else r={type:`FeatureCollection`,features:[]}}else r=e;return[t?t(r.features):r.features,r]}let _=e=>(0,t.geoPath)().projection(e);function v({width:e=600,height:n,aspectRatio:r=16/9,data:i,dataTransformer:a,projection:o=t.geoEqualEarth,projectionConfig:s}){let[c,l]=g(i,a),u=n||e/r,d=h({width:e,height:u,projection:o,config:s,geoJson:l}),f=_(d);return{width:e,height:u,projection:d,features:c,path:f,renderPath:e=>f(e)}}function y(e){return e?.type===`Topology`}let b=[`default`,`hover`,`active`];function x(e){switch(e){case`focus`:case`mouseenter`:case`mouseup`:return`hover`;case`blur`:case`mouseleave`:return`default`;case`mousedown`:return`active`;default:return`default`}}function S(e,t){return t?.[e]??t?.default}function C(e,t,n=`translate(0, 0)`){let r=e?.projection;if(!r)return n;let i=r(t);return i?p(...i):n}let w={center:[0,0],zoom:1,minZoom:1,maxZoom:8,extent:[[0,0],[0,0]]};function T(e){return[[0,0],[e?.width??0,e?.height??0]]}function E(e,t){return i.zoomIdentity.translate(...e).scale(t)}function D(e){return{scaleExtent:[e.minZoom??w.minZoom,e.maxZoom??w.maxZoom],translateExtent:e.translateExtent??w.extent}}function O(e,t={}){let n=(0,i.zoom)(),r=D({minZoom:t.minZoom,maxZoom:t.maxZoom,translateExtent:t.translateExtent??T(e)});return n.scaleExtent(r.scaleExtent).translateExtent(r.translateExtent),t.onZoomStart&&n.on(`start`,t.onZoomStart),t.onZoom&&n.on(`zoom`,t.onZoom),t.onZoomEnd&&n.on(`end`,t.onZoomEnd),I(n,t.modifiers),n}function k(e,t){let n=L(e);n&&(0,r.select)(n).call(t)}function A(e,t,n){let i=L(e);i&&(0,r.select)(i).call(t.transform,n)}function j(e){let t=e.center??w.center,n=e.zoom??w.zoom;A(e.element,e.behavior,E(t,n))}function M(e){k(e.element,e.behavior),j(e)}function N(e){return c(e)?e:F(e)?e.k:e?.transform?.k??1}function P(e,t=1){let n=N(e);return!c(n)||n===0?t:1/n}function F(e){return!!(e&&c(e.k)&&c(e.x)&&c(e.y))}function I(e,t){if(t)for(let[n,r]of Object.entries(t)){if(!n||r===void 0)continue;let t=e[n];if(typeof t!=`function`)continue;let i=Array.isArray(r)?r:[r];t.apply(e,i)}}function L(e){return e?e instanceof SVGSVGElement?e:e.closest(`svg`):null}e.ZOOM_DEFAULTS=w,e.applyZoomBehaviorTransform=A,e.applyZoomTransform=j,e.attachZoomBehavior=k,e.createZoomBehavior=O,e.createZoomConfig=D,e.createZoomTransform=E,e.get=f,e.getDefaultTranslateExtent=T,e.getFeatureKey=m,e.getInverseZoomScale=P,e.getMarkerTransform=C,e.getObjectStateUpdate=x,e.getZoomScale=N,e.isDefined=o,e.isFunction=u,e.isNullish=s,e.isNumber=c,e.isPlainObject=d,e.isString=a,e.isStringOrNumber=l,e.isTopology=y,e.makeFeatures=g,e.makeMapContext=v,e.makePathFn=_,e.makeProjection=h,e.makeTransform=p,e.mapObjectState=b,e.resolveObjectStyle=S,e.setupZoom=M})(this.D3Maps=this.D3Maps||{},d3,topojson,d3,d3);
package/dist/index.js ADDED
@@ -0,0 +1,256 @@
1
+ import { geoEqualEarth, geoPath } from "d3-geo";
2
+ import { feature } from "topojson-client";
3
+ import { select } from "d3-selection";
4
+ import { zoom, zoomIdentity } from "d3-zoom";
5
+
6
+ //#region src/lib/utils.ts
7
+ function isString(value) {
8
+ return typeof value === "string";
9
+ }
10
+ function isDefined(value) {
11
+ return value !== "undefined";
12
+ }
13
+ const isNullish = (value) => value == null;
14
+ const isNumber = (value) => Number.isFinite(value);
15
+ const isStringOrNumber = (value) => isString(value) || isNumber(value);
16
+ function isFunction(value) {
17
+ return typeof value === "function";
18
+ }
19
+ function isPlainObject(value) {
20
+ if (Object.prototype.toString.call(value) !== "[object Object]") return false;
21
+ const prototype = Object.getPrototypeOf(value);
22
+ return prototype === null || prototype === Object.prototype;
23
+ }
24
+ async function get(url) {
25
+ return (await fetch(url)).json();
26
+ }
27
+ const makeTransform = (x, y, k) => `translate(${x}, ${y}) scale(${k ?? 1})`;
28
+
29
+ //#endregion
30
+ //#region src/lib/feature.ts
31
+ /**
32
+ * Resolves a stable key for a feature.
33
+ *
34
+ * Checks:
35
+ * 1) `feature[idKey]`
36
+ * 2) `feature.properties[idKey]`
37
+ * 3) fallback to the list index
38
+ */
39
+ function getFeatureKey(feature$1, idKey = "id", index) {
40
+ const directValue = feature$1[idKey];
41
+ if (isStringOrNumber(directValue)) return directValue;
42
+ const propertyValue = feature$1.properties?.[idKey];
43
+ if (isStringOrNumber(propertyValue)) return propertyValue;
44
+ return index;
45
+ }
46
+
47
+ //#endregion
48
+ //#region src/lib/map.ts
49
+ /**
50
+ * Creates a configured projection and fits it to the provided GeoJSON (if present).
51
+ */
52
+ function makeProjection({ width, height, config, projection, geoJson }) {
53
+ const mapProjection = projection();
54
+ if (config?.center) {
55
+ const [cx, cy] = config.center;
56
+ if (isNumber(cx) && isNumber(cy)) mapProjection.center([cx, cy]);
57
+ }
58
+ if (config?.rotate) {
59
+ const [rx, ry, rz] = config.rotate;
60
+ if (isNumber(rx) && isNumber(ry)) mapProjection.rotate([
61
+ rx,
62
+ ry,
63
+ isNumber(rz) ? rz : 0
64
+ ]);
65
+ }
66
+ if (config && isNumber(config.scale)) mapProjection.scale(config.scale);
67
+ if (geoJson) mapProjection.fitSize([width, height], geoJson);
68
+ else mapProjection.translate([width / 2, height / 2]);
69
+ return mapProjection;
70
+ }
71
+ /**
72
+ * Normalizes input map data to GeoJSON features.
73
+ *
74
+ * - TopoJSON is converted via `topojson-client`.
75
+ * - If provided, `dataTransformer` is applied to the feature array.
76
+ */
77
+ function makeFeatures(geoData, dataTransformer) {
78
+ let geoJson;
79
+ if (isTopology(geoData)) {
80
+ const objectKey = Object.keys(geoData.objects)[0];
81
+ if (objectKey) {
82
+ const topoObject = geoData.objects[objectKey];
83
+ const normalizedGeoJson = feature(geoData, topoObject);
84
+ geoJson = normalizedGeoJson.type === "FeatureCollection" ? normalizedGeoJson : {
85
+ type: "FeatureCollection",
86
+ features: [normalizedGeoJson]
87
+ };
88
+ } else geoJson = {
89
+ type: "FeatureCollection",
90
+ features: []
91
+ };
92
+ } else geoJson = geoData;
93
+ return [dataTransformer ? dataTransformer(geoJson.features) : geoJson.features, geoJson];
94
+ }
95
+ const makePathFn = (mapProjection) => geoPath().projection(mapProjection);
96
+ /**
97
+ * Creates a full {@link MapContext} from a {@link MapConfig}.
98
+ */
99
+ function makeMapContext({ width = 600, height: passedHeight, aspectRatio = 16 / 9, data, dataTransformer, projection: providedProjection = geoEqualEarth, projectionConfig }) {
100
+ const [features, geoJson] = makeFeatures(data, dataTransformer);
101
+ const height = passedHeight || width / aspectRatio;
102
+ const projection = makeProjection({
103
+ width,
104
+ height,
105
+ projection: providedProjection,
106
+ config: projectionConfig,
107
+ geoJson
108
+ });
109
+ const pathFn = makePathFn(projection);
110
+ return {
111
+ width,
112
+ height,
113
+ projection,
114
+ features,
115
+ path: pathFn,
116
+ renderPath: (feature$1) => pathFn(feature$1)
117
+ };
118
+ }
119
+ /**
120
+ * Type guard for TopoJSON topology inputs.
121
+ */
122
+ function isTopology(data) {
123
+ return data?.type === "Topology";
124
+ }
125
+
126
+ //#endregion
127
+ //#region src/lib/mapObject.ts
128
+ /**
129
+ * Supported interaction states for map objects.
130
+ */
131
+ const mapObjectState = [
132
+ "default",
133
+ "hover",
134
+ "active"
135
+ ];
136
+ /**
137
+ * Maps DOM event names to interaction state updates.
138
+ */
139
+ function getObjectStateUpdate(event) {
140
+ switch (event) {
141
+ case "focus":
142
+ case "mouseenter":
143
+ case "mouseup": return "hover";
144
+ case "blur":
145
+ case "mouseleave": return "default";
146
+ case "mousedown": return "active";
147
+ default: return "default";
148
+ }
149
+ }
150
+ /**
151
+ * Resolves a style value for the current state (falls back to `default`).
152
+ */
153
+ function resolveObjectStyle(state, styles) {
154
+ return styles?.[state] ?? styles?.default;
155
+ }
156
+
157
+ //#endregion
158
+ //#region src/lib/marker.ts
159
+ /**
160
+ * Computes an SVG transform (`translate(x, y)`) for the given coordinates using the active projection.
161
+ *
162
+ * Coordinates must be `[longitude, latitude]`.
163
+ */
164
+ function getMarkerTransform(context, coordinates, fallback = "translate(0, 0)") {
165
+ const projection = context?.projection;
166
+ if (!projection) return fallback;
167
+ const projected = projection(coordinates);
168
+ if (!projected) return fallback;
169
+ return makeTransform(...projected);
170
+ }
171
+
172
+ //#endregion
173
+ //#region src/lib/zoom.ts
174
+ const ZOOM_DEFAULTS = {
175
+ center: [0, 0],
176
+ zoom: 1,
177
+ minZoom: 1,
178
+ maxZoom: 8,
179
+ extent: [[0, 0], [0, 0]]
180
+ };
181
+ function getDefaultTranslateExtent(context) {
182
+ return [[0, 0], [context?.width ?? 0, context?.height ?? 0]];
183
+ }
184
+ function createZoomTransform(center, zoomLevel) {
185
+ return zoomIdentity.translate(...center).scale(zoomLevel);
186
+ }
187
+ function createZoomConfig(options) {
188
+ return {
189
+ scaleExtent: [options.minZoom ?? ZOOM_DEFAULTS.minZoom, options.maxZoom ?? ZOOM_DEFAULTS.maxZoom],
190
+ translateExtent: options.translateExtent ?? ZOOM_DEFAULTS.extent
191
+ };
192
+ }
193
+ function createZoomBehavior(context, options = {}) {
194
+ const behavior = zoom();
195
+ const config = createZoomConfig({
196
+ minZoom: options.minZoom,
197
+ maxZoom: options.maxZoom,
198
+ translateExtent: options.translateExtent ?? getDefaultTranslateExtent(context)
199
+ });
200
+ behavior.scaleExtent(config.scaleExtent).translateExtent(config.translateExtent);
201
+ if (options.onZoomStart) behavior.on("start", options.onZoomStart);
202
+ if (options.onZoom) behavior.on("zoom", options.onZoom);
203
+ if (options.onZoomEnd) behavior.on("end", options.onZoomEnd);
204
+ applyZoomModifiers(behavior, options.modifiers);
205
+ return behavior;
206
+ }
207
+ function attachZoomBehavior(element, behavior) {
208
+ const svgElement = getSvgElement(element);
209
+ if (!svgElement) return;
210
+ select(svgElement).call(behavior);
211
+ }
212
+ function applyZoomBehaviorTransform(element, behavior, transform) {
213
+ const svgElement = getSvgElement(element);
214
+ if (!svgElement) return;
215
+ select(svgElement).call(behavior.transform, transform);
216
+ }
217
+ function applyZoomTransform(options) {
218
+ const center = options.center ?? ZOOM_DEFAULTS.center;
219
+ const zoom$1 = options.zoom ?? ZOOM_DEFAULTS.zoom;
220
+ applyZoomBehaviorTransform(options.element, options.behavior, createZoomTransform(center, zoom$1));
221
+ }
222
+ function setupZoom(options) {
223
+ attachZoomBehavior(options.element, options.behavior);
224
+ applyZoomTransform(options);
225
+ }
226
+ function getZoomScale(source) {
227
+ if (isNumber(source)) return source;
228
+ if (isZoomTransform(source)) return source.k;
229
+ return source?.transform?.k ?? 1;
230
+ }
231
+ function getInverseZoomScale(source, fallback = 1) {
232
+ const scale = getZoomScale(source);
233
+ if (!isNumber(scale) || scale === 0) return fallback;
234
+ return 1 / scale;
235
+ }
236
+ function isZoomTransform(value) {
237
+ return Boolean(value && isNumber(value.k) && isNumber(value.x) && isNumber(value.y));
238
+ }
239
+ function applyZoomModifiers(behavior, modifiers) {
240
+ if (!modifiers) return;
241
+ for (const [methodName, methodArgs] of Object.entries(modifiers)) {
242
+ if (!methodName || methodArgs === void 0) continue;
243
+ const modifier = behavior[methodName];
244
+ if (typeof modifier !== "function") continue;
245
+ const normalizedArgs = Array.isArray(methodArgs) ? methodArgs : [methodArgs];
246
+ modifier.apply(behavior, normalizedArgs);
247
+ }
248
+ }
249
+ function getSvgElement(element) {
250
+ if (!element) return null;
251
+ if (element instanceof SVGSVGElement) return element;
252
+ return element.closest("svg");
253
+ }
254
+
255
+ //#endregion
256
+ export { ZOOM_DEFAULTS, applyZoomBehaviorTransform, applyZoomTransform, attachZoomBehavior, createZoomBehavior, createZoomConfig, createZoomTransform, get, getDefaultTranslateExtent, getFeatureKey, getInverseZoomScale, getMarkerTransform, getObjectStateUpdate, getZoomScale, isDefined, isFunction, isNullish, isNumber, isPlainObject, isString, isStringOrNumber, isTopology, makeFeatures, makeMapContext, makePathFn, makeProjection, makeTransform, mapObjectState, resolveObjectStyle, setupZoom };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@d3-maps/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "private": false,
6
+ "description": "Framework-agnostic core utilities for building reactive D3 maps",
7
+ "author": "Georgii Bukharov <souljorje@gmail.com>",
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/souljorje/d3-maps.git",
15
+ "directory": "packages/core"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "browser": "./dist/index.iife.js"
22
+ },
23
+ "./*.css": {
24
+ "import": "./dist/*.css",
25
+ "browser": "./dist/*.css"
26
+ }
27
+ },
28
+ "style": "dist/index.css",
29
+ "main": "dist/index.js",
30
+ "module": "dist/index.js",
31
+ "browser": "dist/index.iife.js",
32
+ "types": "dist/index.d.ts",
33
+ "files": [
34
+ "dist/*"
35
+ ],
36
+ "dependencies": {
37
+ "d3-geo": "^3.1.1",
38
+ "d3-selection": "^3.0.0",
39
+ "d3-zoom": "^3.0.0",
40
+ "topojson-client": "^3.1.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/d3-geo": "^3.1.0",
44
+ "@types/d3-selection": "^3.0.11",
45
+ "@types/d3-zoom": "^3.0.8",
46
+ "@types/geojson": "^7946.0.16",
47
+ "@types/topojson-client": "^3.1.5",
48
+ "@types/topojson-specification": "^1.0.5",
49
+ "typescript": "^5.9.3",
50
+ "tsdown": "0.19.0",
51
+ "vitest": "^4.0.15"
52
+ },
53
+ "scripts": {
54
+ "typecheck": "tsc --noEmit",
55
+ "build": "pnpm run typecheck && tsdown",
56
+ "dev": "tsdown --watch",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest"
59
+ }
60
+ }