@d3-maps/core 0.1.1-next.0 → 0.3.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/dist/index.css CHANGED
@@ -2,6 +2,7 @@
2
2
  height: 100%;
3
3
  width: auto;
4
4
  max-width: 100%;
5
+ stroke-width: 0.5
5
6
  }
6
7
  .d3-map-zoom path {
7
8
  vector-effect: non-scaling-stroke;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,55 @@
1
- import { GeoPath, GeoProjection } from "d3-geo";
1
+ import { ExtendedFeature, ExtendedFeatureCollection, GeoGeometryObjects, GeoPath, GeoPermissibleObjects, GeoProjection } from "d3-geo";
2
+ import { mesh } from "topojson-client";
2
3
  import { D3ZoomEvent, D3ZoomEvent as D3ZoomEvent$1, ZoomBehavior, ZoomBehavior as ZoomBehavior$1, ZoomTransform, ZoomTransform as ZoomTransform$1 } from "d3-zoom";
3
4
 
5
+ //#region src/lib/mapObject.d.ts
6
+ type MapObject = GeoGeometryObjects | ExtendedFeature;
7
+ type MapObjectFocusEventType = 'focus' | 'blur';
8
+ type MapObjectMouseEventType = 'mouseenter' | 'mouseleave' | 'mousedown' | 'mouseup';
9
+ type MapObjectEventType = MapObjectFocusEventType | MapObjectMouseEventType;
10
+ type MapObjectEvent<E> = E extends MapObjectFocusEventType ? FocusEvent : MouseEvent;
11
+ /**
12
+ * Supported interaction states for map objects.
13
+ */
14
+ declare const mapObjectState: readonly ["default", "hover", "active"];
15
+ type MapObjectState = typeof mapObjectState[number];
16
+ type MapObjectStyles<TStyle> = Partial<Record<MapObjectState, TStyle>>;
17
+ /**
18
+ * Maps DOM event names to interaction state updates.
19
+ */
20
+ declare function getObjectStateUpdate(event: MapObjectEventType): MapObjectState;
21
+ /**
22
+ * Resolves a style value for the current state (falls back to `default`).
23
+ */
24
+ declare function resolveObjectStyle<TStyle>(state: MapObjectState, styles?: MapObjectStyles<TStyle>): TStyle | undefined;
25
+ //#endregion
26
+ //#region src/lib/feature.d.ts
27
+ /**
28
+ * A GeoJSON Feature used by d3-maps.
29
+ *
30
+ * This type allows extra top-level fields to be attached in `dataTransformer` (e.g. choropleth colors).
31
+ */
32
+ type MapFeature = (ExtendedFeature & Record<string, unknown>) | ExtendedFeature;
33
+ /**
34
+ * Shared props contract for a single rendered feature.
35
+ */
36
+ interface MapFeatureProps<TStyle = unknown> {
37
+ data: MapFeature;
38
+ styles?: MapObjectStyles<TStyle>;
39
+ fill?: string;
40
+ stroke?: string;
41
+ }
42
+ /**
43
+ * Resolves a stable key for a feature.
44
+ *
45
+ * Checks:
46
+ * 1) `feature[idKey]`
47
+ * 2) `feature.properties[idKey]`
48
+ * 3) fallback to the list index
49
+ */
50
+ declare function getFeatureKey(feature: MapFeature, idKey: string | undefined, index: number): string | number;
51
+ //#endregion
4
52
  //#region ../../node_modules/.pnpm/@types+geojson@7946.0.16/node_modules/@types/geojson/index.d.ts
5
-
6
53
  /**
7
54
  * The valid values for the "type" property of GeoJSON geometry objects.
8
55
  * https://tools.ietf.org/html/rfc7946#section-1.4
@@ -183,53 +230,6 @@ interface FeatureCollection<G extends Geometry | null = Geometry, P$1 = GeoJsonP
183
230
  features: Array<Feature<G, P$1>>;
184
231
  }
185
232
  //#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
233
  //#region ../../node_modules/.pnpm/@types+topojson-specification@1.0.5/node_modules/@types/topojson-specification/index.d.ts
234
234
  // ---------------------------------------------------------------
235
235
  // TopoJSON Format Specification
@@ -316,19 +316,120 @@ interface NullObject extends GeometryObjectA {
316
316
  type: null;
317
317
  }
318
318
  //#endregion
319
+ //#region src/lib/utils.d.ts
320
+ declare function isString(value: unknown): value is string;
321
+ declare function isDefined<T>(value: T | null | undefined): value is T;
322
+ declare const isNullish: (value: unknown) => value is null | undefined;
323
+ declare const isNumber: (value: unknown) => value is number;
324
+ declare function isStringOrNumber(value: unknown): value is string | number;
325
+ declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
326
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
327
+ declare function makeTransform(x: number, y: number, k?: number): string;
328
+ type AnyFn = (...args: any) => any;
329
+ /**
330
+ * Extracts a union of parameter tuples from a (possibly overloaded) function type.
331
+ *
332
+ * TypeScript's built-in `Parameters<F>` only captures the *last* overload, which breaks typing
333
+ * for overloaded getter/setter APIs (common in d3), where the setter overload might not be last.
334
+ *
335
+ * Notes:
336
+ * - This helper supports up to 5 overload signatures (adjust if needed).
337
+ * - Getter overloads like `(): T` are filtered out later via `Exclude<..., []>` when we build
338
+ * setter-only config types.
339
+ */
340
+ type OverloadedArgs<F> = F extends {
341
+ (...a: infer A1): any;
342
+ (...a: infer A2): any;
343
+ (...a: infer A3): any;
344
+ (...a: infer A4): any;
345
+ (...a: infer A5): any;
346
+ } ? A1 | A2 | A3 | A4 | A5 : F extends {
347
+ (...a: infer A1): any;
348
+ (...a: infer A2): any;
349
+ (...a: infer A3): any;
350
+ (...a: infer A4): any;
351
+ } ? A1 | A2 | A3 | A4 : F extends {
352
+ (...a: infer A1): any;
353
+ (...a: infer A2): any;
354
+ (...a: infer A3): any;
355
+ } ? A1 | A2 | A3 : F extends {
356
+ (...a: infer A1): any;
357
+ (...a: infer A2): any;
358
+ } ? A1 | A2 : F extends ((...a: infer A1) => any) ? A1 : never;
359
+ /**
360
+ * Removes 0-arg overloads (getters), leaving only setter-style overload argument tuples.
361
+ */
362
+ type SetterArgs<F> = Exclude<OverloadedArgs<F>, []>;
363
+ /**
364
+ * True if the function has at least one overload that accepts arguments (i.e. a setter overload).
365
+ */
366
+ type HasArgs<F> = [SetterArgs<F>] extends [never] ? false : true;
367
+ type OwnKeys<T> = T extends AnyFn ? Exclude<keyof T, keyof CallableFunction> : keyof T;
368
+ /**
369
+ * Converts method parameters to modifiers values
370
+ * - single non-array arg: `arg` | `[arg]`
371
+ * - multiple args/single array wrapped with array
372
+ */
373
+ type ModifierArgs<P$1 extends unknown[]> = P$1 extends [infer Only] ? Only extends readonly unknown[] ? [Only] : Only | [Only] : P$1;
374
+ /**
375
+ * Maps methods with args to modifiers
376
+ *
377
+ * @example
378
+ * type X = {
379
+ * a: string; // not a function - will be ignored
380
+ * b(): void; // has no arguments - will be ignored
381
+ * c(x: number): void;
382
+ * d(x: number, y: string): void;
383
+ * e(xs: string[]): void;
384
+ * }
385
+ *
386
+ * type R = MethodsToModifiers<X>
387
+ * {
388
+ * c: number | [number];
389
+ * d: [number, string];
390
+ * e: [string[]]; // forced wrapper (arg is array)
391
+ * }
392
+ */
393
+ type MethodsToModifiers<T extends object> = { [K in OwnKeys<T> as Extract<T[K], AnyFn> extends never ? never : HasArgs<Extract<T[K], AnyFn>> extends true ? K : never]?: ModifierArgs<Extract<SetterArgs<Extract<T[K], AnyFn>>, unknown[]>> };
394
+ /**
395
+ * Invokes `target` methods with arguments from `modifiers`.
396
+ *
397
+ * modifiers: `{ [methodName]: args[] | arg }`
398
+ *
399
+ * @example
400
+ * class X {
401
+ * a(x: number) {}
402
+ * b(x: number, y: string) {}
403
+ * c(x: string[]) {}
404
+ * d() {}
405
+ * e = 'foo'
406
+ * }
407
+ *
408
+ * applyModifiers(new X(), {
409
+ * a: 1, // ok (single arg as-is)
410
+ * a: [1], // ok (single arg wrapped)
411
+ * b: [1, 'foo'], // ok (tuple for 2 args)
412
+ * c: [['foo', 'bar']], // ok (array-arg must be wrapped)
413
+ * c: ['foo', 'bar'], // error (single array-arg must be wrapped into array)
414
+ * d: [], // error (d has no args, excluded)
415
+ * e: 'foo' // error (e is not a function, excluded)
416
+ * })
417
+ */
418
+ declare function applyModifiers<T extends object>(target: T, modifiers?: MethodsToModifiers<T>): void;
419
+ //#endregion
319
420
  //#region src/lib/map.d.ts
320
- type MapData = FeatureCollection | Topology;
421
+ type MapMesh = ReturnType<typeof mesh>;
422
+ type MapData = ExtendedFeatureCollection | Topology;
321
423
  type DataTransformer = (features: MapFeature[]) => MapFeature[];
322
424
  /**
323
- * Configuration for a d3-geo projection.
425
+ * Extra projection method calls to apply before rendering.
426
+ *
427
+ * Use projection method names as keys and method arguments as values.
428
+ * Example: `{ center: [[0, 20]], rotate: [[0, 0, 0]], scale: 160 }`
324
429
  *
325
- * d3-maps applies these options (if provided) before fitting the geometry to the map size.
430
+ * @see https://d3js.org/d3-geo/projection
326
431
  */
327
- interface ProjectionConfig {
328
- center?: [number, number];
329
- rotate?: [number, number, number];
330
- scale?: number;
331
- }
432
+ interface ProjectionConfig extends Omit<MethodsToModifiers<GeoProjection>, 'invert' | 'stream'> {}
332
433
  /**
333
434
  * Input configuration for creating a map context.
334
435
  *
@@ -344,6 +445,9 @@ interface MapConfig {
344
445
  * Example: `geoEqualEarth`.
345
446
  */
346
447
  projection?: () => GeoProjection;
448
+ /**
449
+ * Projection method arguments passed to the created projection
450
+ */
347
451
  projectionConfig?: ProjectionConfig;
348
452
  /**
349
453
  * TopoJSON or GeoJSON input.
@@ -366,8 +470,10 @@ interface MapContext {
366
470
  height: number;
367
471
  projection?: GeoProjection;
368
472
  features: MapFeature[];
473
+ mesh?: MapMesh;
369
474
  path: GeoPath;
370
- renderPath: (feature: Feature) => ReturnType<GeoPath>;
475
+ renderPath: (feature: MapFeature) => ReturnType<GeoPath>;
476
+ renderMesh: () => ReturnType<GeoPath>;
371
477
  }
372
478
  /**
373
479
  * Creates a configured projection and fits it to the provided GeoJSON (if present).
@@ -383,7 +489,7 @@ declare function makeProjection({
383
489
  height: number;
384
490
  config?: ProjectionConfig;
385
491
  projection: () => GeoProjection;
386
- geoJson?: FeatureCollection;
492
+ geoJson?: GeoPermissibleObjects;
387
493
  }): GeoProjection;
388
494
  /**
389
495
  * Normalizes input map data to GeoJSON features.
@@ -391,8 +497,12 @@ declare function makeProjection({
391
497
  * - TopoJSON is converted via `topojson-client`.
392
498
  * - If provided, `dataTransformer` is applied to the feature array.
393
499
  */
394
- declare function makeFeatures(geoData: MapData, dataTransformer?: DataTransformer): [features: MapFeature[], geoJson: FeatureCollection];
500
+ declare function makeFeatures(geoData: MapData, dataTransformer?: DataTransformer): [features: MapFeature[], geoJson: ExtendedFeatureCollection];
395
501
  declare const makePathFn: (mapProjection: GeoProjection) => GeoPath;
502
+ /**
503
+ * Returns a TopoJSON mesh when topology data is provided.
504
+ */
505
+ declare function makeMesh(geoData: MapData): MapMesh | undefined;
396
506
  /**
397
507
  * Creates a full {@link MapContext} from a {@link MapConfig}.
398
508
  */
@@ -409,6 +519,7 @@ declare function makeMapContext({
409
519
  * Type guard for TopoJSON topology inputs.
410
520
  */
411
521
  declare function isTopology(data: MapData): data is Topology;
522
+ declare function getTopoObject(geoData: Topology): GeometryObject;
412
523
  //#endregion
413
524
  //#region src/lib/marker.d.ts
414
525
  type MapMarkerCoordinates = [number, number];
@@ -426,41 +537,24 @@ interface MapMarkerProps<TStyle = unknown> {
426
537
  */
427
538
  declare function getMarkerTransform(context: MapContext | undefined, coordinates: MapMarkerCoordinates, fallback?: string): string;
428
539
  //#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
540
  //#region src/lib/zoom.d.ts
441
541
  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> {
542
+ interface DefaultZoomBehavior extends ZoomBehavior$1<SVGSVGElement, unknown> {}
543
+ /**
544
+ * Extra zoom method calls to apply before rendering.
545
+ *
546
+ * Use zoom method names as keys and method arguments as values.
547
+ * Example: `{ scaleExtent: [[2, 9]], translateExtent: [[[0, 0], [10, 10]]] }`
548
+ *
549
+ * @see https://d3js.org/d3-zoom
550
+ */
551
+ interface ZoomModifiers extends MethodsToModifiers<DefaultZoomBehavior> {}
552
+ interface ZoomProps {
458
553
  center?: [number, number];
459
554
  zoom?: number;
460
555
  minZoom?: number;
461
556
  maxZoom?: number;
462
- translateExtent?: Extent;
463
- modifiers?: ZoomModifiers<TElement, TDatum>;
557
+ config?: ZoomModifiers;
464
558
  }
465
559
  interface ZoomEvent extends D3ZoomEvent$1<SVGSVGElement, unknown> {}
466
560
  interface ZoomEvents {
@@ -468,7 +562,7 @@ interface ZoomEvents {
468
562
  onZoom?: (event: ZoomEvent) => void;
469
563
  onZoomEnd?: (event: ZoomEvent) => void;
470
564
  }
471
- interface ZoomBehaviorOptions<TElement extends Element = SVGSVGElement, TDatum = unknown> extends ZoomProps<TElement, TDatum>, ZoomEvents {}
565
+ interface ZoomBehaviorOptions extends ZoomProps, ZoomEvents {}
472
566
  type ZoomScaleSource = number | ZoomTransform$1 | {
473
567
  transform: ZoomTransform$1;
474
568
  };
@@ -478,24 +572,20 @@ declare const ZOOM_DEFAULTS: {
478
572
  zoom: number;
479
573
  minZoom: number;
480
574
  maxZoom: number;
481
- extent: Extent;
482
575
  };
483
576
  interface ApplyZoomOptions {
484
577
  element: ZoomTargetElement | null | undefined;
485
- behavior: ZoomBehavior$1<SVGSVGElement, unknown>;
578
+ behavior: DefaultZoomBehavior;
486
579
  center?: [number, number];
487
580
  zoom?: number;
488
581
  }
489
582
  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;
583
+ declare function createZoomBehavior(context?: MapContext, options?: ZoomBehaviorOptions): DefaultZoomBehavior;
584
+ declare function attachZoomBehavior(element: ZoomTargetElement | null | undefined, behavior: DefaultZoomBehavior): void;
585
+ declare function applyZoomBehaviorTransform(element: ZoomTargetElement | null | undefined, behavior: DefaultZoomBehavior, transform: ZoomTransform$1): void;
496
586
  declare function applyZoomTransform(options: ApplyZoomOptions): void;
497
587
  declare function setupZoom(options: SetupZoomOptions): void;
498
588
  declare function getZoomScale(source: ZoomScaleSource): number;
499
589
  declare function getInverseZoomScale(source: ZoomScaleSource, fallback?: number): number;
500
590
  //#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 };
591
+ export { AnyFn, ApplyZoomOptions, type D3ZoomEvent, DataTransformer, DefaultZoomBehavior, Extent, HasArgs, MapConfig, MapContext, MapData, MapFeature, MapFeatureProps, MapMarkerCoordinates, MapMarkerProps, MapMesh, MapObject, MapObjectEvent, MapObjectEventType, MapObjectFocusEventType, MapObjectMouseEventType, MapObjectState, MapObjectStyles, MethodsToModifiers, ModifierArgs, OverloadedArgs, OwnKeys, ProjectionConfig, SetterArgs, SetupZoomOptions, ZOOM_DEFAULTS, type ZoomBehavior, ZoomBehaviorOptions, ZoomEvent, ZoomEvents, ZoomModifiers, ZoomProps, ZoomScaleSource, ZoomTargetElement, type ZoomTransform, applyModifiers, applyZoomBehaviorTransform, applyZoomTransform, attachZoomBehavior, createZoomBehavior, getFeatureKey, getInverseZoomScale, getMarkerTransform, getObjectStateUpdate, getTopoObject, getZoomScale, isDefined, isFunction, isNullish, isNumber, isPlainObject, isString, isStringOrNumber, isTopology, makeFeatures, makeMapContext, makeMesh, makePathFn, makeProjection, makeTransform, mapObjectState, resolveObjectStyle, setupZoom };
@@ -1 +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);
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);function l(e){return 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}function f(e,t,n){return`translate(${e}, ${t}) scale(${n??1})`}function p(e,t){if(t)for(let n of Object.keys(t)){let r=t[n];if(!o(r))continue;let i=e[n];if(!u(i))continue;let a=Array.isArray(r)?r:[r];i.apply(e,a)}}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();return i?n?.fitSize||a.fitSize([e,t],i):n?.translate||a.translate([e/2,t/2]),p(a,n),a}function g(e,t){let r;if(b(e)){let t=(0,n.feature)(e,x(e));r=t.type===`FeatureCollection`?t:{type:`FeatureCollection`,features:[t]}}else r=e;return[t?t(r.features):r.features,r]}let _=e=>(0,t.geoPath)().projection(e);function v(e){if(b(e))return(0,n.mesh)(e,x(e))}function y({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=v(i),d=n||e/r,f=h({width:e,height:d,projection:o,config:s,geoJson:l}),p=_(f);return{width:e,height:d,projection:f,features:c,mesh:u,path:p,renderPath:e=>p(e),renderMesh:()=>u?p(u):null}}function b(e){return e?.type===`Topology`}function x(e){let t=Object.keys(e.objects)[0];return e.objects[t]}let S=[`default`,`hover`,`active`];function C(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 w(e,t){return t?.[e]??t?.default}function T(e,t,n=`translate(0, 0)`){let r=e?.projection;if(!r)return n;let i=r(t);return i?f(...i):n}let E={center:[0,0],zoom:1,minZoom:1,maxZoom:8};function D(e,t={}){let n=(0,i.zoom)(),r=t.minZoom??E.minZoom,a=t.maxZoom??E.maxZoom,o=[[0,0],[e?.width??0,e?.height??0]];return n.scaleExtent([r,a]).translateExtent(o),t.onZoomStart&&n.on(`start`,t.onZoomStart),t.onZoom&&n.on(`zoom`,t.onZoom),t.onZoomEnd&&n.on(`end`,t.onZoomEnd),p(n,t.config),n}function O(e,t){let n=F(e);n&&(0,r.select)(n).call(t)}function k(e,t,n){let i=F(e);i&&(0,r.select)(i).call(t.transform,n)}function A(e){let t=e.center??E.center,n=e.zoom??E.zoom;k(e.element,e.behavior,i.zoomIdentity.translate(...t).scale(n))}function j(e){O(e.element,e.behavior),A(e)}function M(e){return c(e)?e:P(e)?e.k:e?.transform?.k??1}function N(e,t=1){let n=M(e);return!c(n)||n===0?t:1/n}function P(e){return!!(e&&c(e.k)&&c(e.x)&&c(e.y))}function F(e){return e?e instanceof SVGSVGElement?e:e.closest(`svg`):null}e.ZOOM_DEFAULTS=E,e.applyModifiers=p,e.applyZoomBehaviorTransform=k,e.applyZoomTransform=A,e.attachZoomBehavior=O,e.createZoomBehavior=D,e.getFeatureKey=m,e.getInverseZoomScale=N,e.getMarkerTransform=T,e.getObjectStateUpdate=C,e.getTopoObject=x,e.getZoomScale=M,e.isDefined=o,e.isFunction=u,e.isNullish=s,e.isNumber=c,e.isPlainObject=d,e.isString=a,e.isStringOrNumber=l,e.isTopology=b,e.makeFeatures=g,e.makeMapContext=y,e.makeMesh=v,e.makePathFn=_,e.makeProjection=h,e.makeTransform=f,e.mapObjectState=S,e.resolveObjectStyle=w,e.setupZoom=j})(this.D3Maps=this.D3Maps||{},d3,topojson,d3,d3);
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { geoEqualEarth, geoPath } from "d3-geo";
2
- import { feature } from "topojson-client";
2
+ import { feature, mesh } from "topojson-client";
3
3
  import { select } from "d3-selection";
4
4
  import { zoom, zoomIdentity } from "d3-zoom";
5
5
 
@@ -12,7 +12,9 @@ function isDefined(value) {
12
12
  }
13
13
  const isNullish = (value) => value == null;
14
14
  const isNumber = (value) => Number.isFinite(value);
15
- const isStringOrNumber = (value) => isString(value) || isNumber(value);
15
+ function isStringOrNumber(value) {
16
+ return isString(value) || isNumber(value);
17
+ }
16
18
  function isFunction(value) {
17
19
  return typeof value === "function";
18
20
  }
@@ -21,10 +23,44 @@ function isPlainObject(value) {
21
23
  const prototype = Object.getPrototypeOf(value);
22
24
  return prototype === null || prototype === Object.prototype;
23
25
  }
24
- async function get(url) {
25
- return (await fetch(url)).json();
26
+ function makeTransform(x, y, k) {
27
+ return `translate(${x}, ${y}) scale(${k ?? 1})`;
28
+ }
29
+ /**
30
+ * Invokes `target` methods with arguments from `modifiers`.
31
+ *
32
+ * modifiers: `{ [methodName]: args[] | arg }`
33
+ *
34
+ * @example
35
+ * class X {
36
+ * a(x: number) {}
37
+ * b(x: number, y: string) {}
38
+ * c(x: string[]) {}
39
+ * d() {}
40
+ * e = 'foo'
41
+ * }
42
+ *
43
+ * applyModifiers(new X(), {
44
+ * a: 1, // ok (single arg as-is)
45
+ * a: [1], // ok (single arg wrapped)
46
+ * b: [1, 'foo'], // ok (tuple for 2 args)
47
+ * c: [['foo', 'bar']], // ok (array-arg must be wrapped)
48
+ * c: ['foo', 'bar'], // error (single array-arg must be wrapped into array)
49
+ * d: [], // error (d has no args, excluded)
50
+ * e: 'foo' // error (e is not a function, excluded)
51
+ * })
52
+ */
53
+ function applyModifiers(target, modifiers) {
54
+ if (!modifiers) return;
55
+ for (const methodName of Object.keys(modifiers)) {
56
+ const methodArgs = modifiers[methodName];
57
+ if (!isDefined(methodArgs)) continue;
58
+ const fn = target[methodName];
59
+ if (!isFunction(fn)) continue;
60
+ const normalizedArgs = Array.isArray(methodArgs) ? methodArgs : [methodArgs];
61
+ fn.apply(target, normalizedArgs);
62
+ }
26
63
  }
27
- const makeTransform = (x, y, k) => `translate(${x}, ${y}) scale(${k ?? 1})`;
28
64
 
29
65
  //#endregion
30
66
  //#region src/lib/feature.ts
@@ -51,21 +87,10 @@ function getFeatureKey(feature$1, idKey = "id", index) {
51
87
  */
52
88
  function makeProjection({ width, height, config, projection, geoJson }) {
53
89
  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]);
90
+ if (!geoJson) {
91
+ if (!config?.translate) mapProjection.translate([width / 2, height / 2]);
92
+ } else if (!config?.fitSize) mapProjection.fitSize([width, height], geoJson);
93
+ applyModifiers(mapProjection, config);
69
94
  return mapProjection;
70
95
  }
71
96
  /**
@@ -77,27 +102,28 @@ function makeProjection({ width, height, config, projection, geoJson }) {
77
102
  function makeFeatures(geoData, dataTransformer) {
78
103
  let geoJson;
79
104
  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 = {
105
+ const normalizedGeoJson = feature(geoData, getTopoObject(geoData));
106
+ geoJson = normalizedGeoJson.type === "FeatureCollection" ? normalizedGeoJson : {
89
107
  type: "FeatureCollection",
90
- features: []
108
+ features: [normalizedGeoJson]
91
109
  };
92
110
  } else geoJson = geoData;
93
111
  return [dataTransformer ? dataTransformer(geoJson.features) : geoJson.features, geoJson];
94
112
  }
95
113
  const makePathFn = (mapProjection) => geoPath().projection(mapProjection);
96
114
  /**
115
+ * Returns a TopoJSON mesh when topology data is provided.
116
+ */
117
+ function makeMesh(geoData) {
118
+ if (!isTopology(geoData)) return void 0;
119
+ return mesh(geoData, getTopoObject(geoData));
120
+ }
121
+ /**
97
122
  * Creates a full {@link MapContext} from a {@link MapConfig}.
98
123
  */
99
124
  function makeMapContext({ width = 600, height: passedHeight, aspectRatio = 16 / 9, data, dataTransformer, projection: providedProjection = geoEqualEarth, projectionConfig }) {
100
125
  const [features, geoJson] = makeFeatures(data, dataTransformer);
126
+ const mapMesh = makeMesh(data);
101
127
  const height = passedHeight || width / aspectRatio;
102
128
  const projection = makeProjection({
103
129
  width,
@@ -112,8 +138,10 @@ function makeMapContext({ width = 600, height: passedHeight, aspectRatio = 16 /
112
138
  height,
113
139
  projection,
114
140
  features,
141
+ mesh: mapMesh,
115
142
  path: pathFn,
116
- renderPath: (feature$1) => pathFn(feature$1)
143
+ renderPath: (feature$1) => pathFn(feature$1),
144
+ renderMesh: () => mapMesh ? pathFn(mapMesh) : null
117
145
  };
118
146
  }
119
147
  /**
@@ -122,6 +150,10 @@ function makeMapContext({ width = 600, height: passedHeight, aspectRatio = 16 /
122
150
  function isTopology(data) {
123
151
  return data?.type === "Topology";
124
152
  }
153
+ function getTopoObject(geoData) {
154
+ const objectKey = Object.keys(geoData.objects)[0];
155
+ return geoData.objects[objectKey];
156
+ }
125
157
 
126
158
  //#endregion
127
159
  //#region src/lib/mapObject.ts
@@ -175,33 +207,18 @@ const ZOOM_DEFAULTS = {
175
207
  center: [0, 0],
176
208
  zoom: 1,
177
209
  minZoom: 1,
178
- maxZoom: 8,
179
- extent: [[0, 0], [0, 0]]
210
+ maxZoom: 8
180
211
  };
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
212
  function createZoomBehavior(context, options = {}) {
194
213
  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);
214
+ const minZoom = options.minZoom ?? ZOOM_DEFAULTS.minZoom;
215
+ const maxZoom = options.maxZoom ?? ZOOM_DEFAULTS.maxZoom;
216
+ const translateExtent = [[0, 0], [context?.width ?? 0, context?.height ?? 0]];
217
+ behavior.scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent);
201
218
  if (options.onZoomStart) behavior.on("start", options.onZoomStart);
202
219
  if (options.onZoom) behavior.on("zoom", options.onZoom);
203
220
  if (options.onZoomEnd) behavior.on("end", options.onZoomEnd);
204
- applyZoomModifiers(behavior, options.modifiers);
221
+ applyModifiers(behavior, options.config);
205
222
  return behavior;
206
223
  }
207
224
  function attachZoomBehavior(element, behavior) {
@@ -217,7 +234,7 @@ function applyZoomBehaviorTransform(element, behavior, transform) {
217
234
  function applyZoomTransform(options) {
218
235
  const center = options.center ?? ZOOM_DEFAULTS.center;
219
236
  const zoom$1 = options.zoom ?? ZOOM_DEFAULTS.zoom;
220
- applyZoomBehaviorTransform(options.element, options.behavior, createZoomTransform(center, zoom$1));
237
+ applyZoomBehaviorTransform(options.element, options.behavior, zoomIdentity.translate(...center).scale(zoom$1));
221
238
  }
222
239
  function setupZoom(options) {
223
240
  attachZoomBehavior(options.element, options.behavior);
@@ -236,16 +253,6 @@ function getInverseZoomScale(source, fallback = 1) {
236
253
  function isZoomTransform(value) {
237
254
  return Boolean(value && isNumber(value.k) && isNumber(value.x) && isNumber(value.y));
238
255
  }
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
256
  function getSvgElement(element) {
250
257
  if (!element) return null;
251
258
  if (element instanceof SVGSVGElement) return element;
@@ -253,4 +260,4 @@ function getSvgElement(element) {
253
260
  }
254
261
 
255
262
  //#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 };
263
+ export { ZOOM_DEFAULTS, applyModifiers, applyZoomBehaviorTransform, applyZoomTransform, attachZoomBehavior, createZoomBehavior, getFeatureKey, getInverseZoomScale, getMarkerTransform, getObjectStateUpdate, getTopoObject, getZoomScale, isDefined, isFunction, isNullish, isNumber, isPlainObject, isString, isStringOrNumber, isTopology, makeFeatures, makeMapContext, makeMesh, makePathFn, makeProjection, makeTransform, mapObjectState, resolveObjectStyle, setupZoom };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@d3-maps/core",
3
- "version": "0.1.1-next.0",
4
3
  "type": "module",
4
+ "version": "0.3.0",
5
5
  "private": false,
6
6
  "description": "Framework-agnostic core utilities for building reactive D3 maps",
7
7
  "author": "Georgii Bukharov <souljorje@gmail.com>",
@@ -46,12 +46,13 @@
46
46
  "@types/geojson": "^7946.0.16",
47
47
  "@types/topojson-client": "^3.1.5",
48
48
  "@types/topojson-specification": "^1.0.5",
49
- "typescript": "^5.9.3",
50
49
  "tsdown": "0.19.0",
50
+ "typescript": "^5.9.3",
51
51
  "vitest": "^4.0.15"
52
52
  },
53
53
  "scripts": {
54
54
  "typecheck": "tsc --noEmit",
55
+ "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
55
56
  "build": "pnpm run typecheck && tsdown",
56
57
  "dev": "tsdown --watch",
57
58
  "test": "vitest run",