@d3-maps/core 0.2.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.
324
426
  *
325
- * d3-maps applies these options (if provided) before fitting the geometry to the map size.
427
+ * Use projection method names as keys and method arguments as values.
428
+ * Example: `{ center: [[0, 20]], rotate: [[0, 0, 0]], scale: 160 }`
429
+ *
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,9 +470,9 @@ interface MapContext {
366
470
  height: number;
367
471
  projection?: GeoProjection;
368
472
  features: MapFeature[];
369
- mesh?: MultiLineString$1;
473
+ mesh?: MapMesh;
370
474
  path: GeoPath;
371
- renderPath: (feature: Feature) => ReturnType<GeoPath>;
475
+ renderPath: (feature: MapFeature) => ReturnType<GeoPath>;
372
476
  renderMesh: () => ReturnType<GeoPath>;
373
477
  }
374
478
  /**
@@ -385,7 +489,7 @@ declare function makeProjection({
385
489
  height: number;
386
490
  config?: ProjectionConfig;
387
491
  projection: () => GeoProjection;
388
- geoJson?: FeatureCollection;
492
+ geoJson?: GeoPermissibleObjects;
389
493
  }): GeoProjection;
390
494
  /**
391
495
  * Normalizes input map data to GeoJSON features.
@@ -393,12 +497,12 @@ declare function makeProjection({
393
497
  * - TopoJSON is converted via `topojson-client`.
394
498
  * - If provided, `dataTransformer` is applied to the feature array.
395
499
  */
396
- declare function makeFeatures(geoData: MapData, dataTransformer?: DataTransformer): [features: MapFeature[], geoJson: FeatureCollection];
500
+ declare function makeFeatures(geoData: MapData, dataTransformer?: DataTransformer): [features: MapFeature[], geoJson: ExtendedFeatureCollection];
397
501
  declare const makePathFn: (mapProjection: GeoProjection) => GeoPath;
398
502
  /**
399
503
  * Returns a TopoJSON mesh when topology data is provided.
400
504
  */
401
- declare function makeMesh(geoData: MapData): MultiLineString$1 | undefined;
505
+ declare function makeMesh(geoData: MapData): MapMesh | undefined;
402
506
  /**
403
507
  * Creates a full {@link MapContext} from a {@link MapConfig}.
404
508
  */
@@ -433,40 +537,24 @@ interface MapMarkerProps<TStyle = unknown> {
433
537
  */
434
538
  declare function getMarkerTransform(context: MapContext | undefined, coordinates: MapMarkerCoordinates, fallback?: string): string;
435
539
  //#endregion
436
- //#region src/lib/utils.d.ts
437
- declare function isString(value: unknown): value is string;
438
- declare function isDefined<T>(value: T | null | undefined): value is T;
439
- declare const isNullish: (value: unknown) => value is null | undefined;
440
- declare const isNumber: (value: unknown) => value is number;
441
- declare function isStringOrNumber(value: unknown): value is string | number;
442
- declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
443
- declare function isPlainObject(value: unknown): value is Record<string, unknown>;
444
- declare function get<T>(url: string): Promise<T>;
445
- declare function makeTransform(x: number, y: number, k?: number): string;
446
- //#endregion
447
540
  //#region src/lib/zoom.d.ts
448
541
  type Extent = [[number, number], [number, number]];
449
- interface ZoomConfigOptions {
450
- minZoom?: number;
451
- maxZoom?: number;
452
- translateExtent?: Extent;
453
- }
454
- interface ZoomConfig {
455
- scaleExtent: [number, number];
456
- translateExtent: Extent;
457
- }
458
- type ZoomBehaviorMethodName<TElement extends Element, TDatum> = Extract<{ [K in keyof ZoomBehavior$1<TElement, TDatum>]: ZoomBehavior$1<TElement, TDatum>[K] extends ((...args: unknown[]) => unknown) ? K : never }[keyof ZoomBehavior$1<TElement, TDatum>], string>;
459
- type ZoomBehaviorMethodArgs<TElement extends Element, TDatum, TMethod extends ZoomBehaviorMethodName<TElement, TDatum>> = ZoomBehavior$1<TElement, TDatum>[TMethod] extends ((...args: infer TArgs) => unknown) ? TArgs : never;
460
- type ZoomBehaviorSingleArg<TElement extends Element, TDatum, TMethod extends ZoomBehaviorMethodName<TElement, TDatum>> = ZoomBehaviorMethodArgs<TElement, TDatum, TMethod> extends [infer TArg] ? TArg : never;
461
- type ZoomModifierValue<TElement extends Element, TDatum, TMethod extends ZoomBehaviorMethodName<TElement, TDatum>> = ZoomBehaviorMethodArgs<TElement, TDatum, TMethod> | ZoomBehaviorSingleArg<TElement, TDatum, TMethod>;
462
- type ZoomModifiers<TElement extends Element = SVGSVGElement, TDatum = unknown> = Partial<{ [K in ZoomBehaviorMethodName<TElement, TDatum>]: ZoomModifierValue<TElement, TDatum, K> }>;
463
- 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 {
464
553
  center?: [number, number];
465
554
  zoom?: number;
466
555
  minZoom?: number;
467
556
  maxZoom?: number;
468
- translateExtent?: Extent;
469
- modifiers?: ZoomModifiers<TElement, TDatum>;
557
+ config?: ZoomModifiers;
470
558
  }
471
559
  interface ZoomEvent extends D3ZoomEvent$1<SVGSVGElement, unknown> {}
472
560
  interface ZoomEvents {
@@ -474,7 +562,7 @@ interface ZoomEvents {
474
562
  onZoom?: (event: ZoomEvent) => void;
475
563
  onZoomEnd?: (event: ZoomEvent) => void;
476
564
  }
477
- interface ZoomBehaviorOptions<TElement extends Element = SVGSVGElement, TDatum = unknown> extends ZoomProps<TElement, TDatum>, ZoomEvents {}
565
+ interface ZoomBehaviorOptions extends ZoomProps, ZoomEvents {}
478
566
  type ZoomScaleSource = number | ZoomTransform$1 | {
479
567
  transform: ZoomTransform$1;
480
568
  };
@@ -484,24 +572,20 @@ declare const ZOOM_DEFAULTS: {
484
572
  zoom: number;
485
573
  minZoom: number;
486
574
  maxZoom: number;
487
- extent: Extent;
488
575
  };
489
576
  interface ApplyZoomOptions {
490
577
  element: ZoomTargetElement | null | undefined;
491
- behavior: ZoomBehavior$1<SVGSVGElement, unknown>;
578
+ behavior: DefaultZoomBehavior;
492
579
  center?: [number, number];
493
580
  zoom?: number;
494
581
  }
495
582
  interface SetupZoomOptions extends ApplyZoomOptions {}
496
- declare function getDefaultTranslateExtent(context?: MapContext): Extent;
497
- declare function createZoomTransform(center: [number, number], zoomLevel: number): ZoomTransform$1;
498
- declare function createZoomConfig(options: ZoomConfigOptions): ZoomConfig;
499
- declare function createZoomBehavior<TElement extends Element = SVGSVGElement, TDatum = unknown>(context?: MapContext, options?: ZoomBehaviorOptions<TElement, TDatum>): ZoomBehavior$1<TElement, TDatum>;
500
- declare function attachZoomBehavior(element: ZoomTargetElement | null | undefined, behavior: ZoomBehavior$1<SVGSVGElement, unknown>): void;
501
- 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;
502
586
  declare function applyZoomTransform(options: ApplyZoomOptions): void;
503
587
  declare function setupZoom(options: SetupZoomOptions): void;
504
588
  declare function getZoomScale(source: ZoomScaleSource): number;
505
589
  declare function getInverseZoomScale(source: ZoomScaleSource, fallback?: number): number;
506
590
  //#endregion
507
- 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, ZoomBehaviorSingleArg, ZoomConfig, ZoomConfigOptions, ZoomEvent, ZoomEvents, ZoomModifierValue, ZoomModifiers, ZoomProps, ZoomScaleSource, ZoomTargetElement, type ZoomTransform, applyZoomBehaviorTransform, applyZoomTransform, attachZoomBehavior, createZoomBehavior, createZoomConfig, createZoomTransform, get, getDefaultTranslateExtent, getFeatureKey, getInverseZoomScale, getMarkerTransform, getObjectStateUpdate, getTopoObject, getZoomScale, isDefined, isFunction, isNullish, isNumber, isPlainObject, isString, isStringOrNumber, isTopology, makeFeatures, makeMapContext, makeMesh, 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);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}async function f(e){return(await fetch(e)).json()}function p(e,t,n){return`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(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?p(...i):n}let E={center:[0,0],zoom:1,minZoom:1,maxZoom:8,extent:[[0,0],[0,0]]};function D(e){return[[0,0],[e?.width??0,e?.height??0]]}function O(e,t){return i.zoomIdentity.translate(...e).scale(t)}function k(e){return{scaleExtent:[e.minZoom??E.minZoom,e.maxZoom??E.maxZoom],translateExtent:e.translateExtent??E.extent}}function A(e,t={}){let n=(0,i.zoom)(),r=k({minZoom:t.minZoom,maxZoom:t.maxZoom,translateExtent:t.translateExtent??D(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),R(n,t.modifiers),n}function j(e,t){let n=z(e);n&&(0,r.select)(n).call(t)}function M(e,t,n){let i=z(e);i&&(0,r.select)(i).call(t.transform,n)}function N(e){let t=e.center??E.center,n=e.zoom??E.zoom;M(e.element,e.behavior,O(t,n))}function P(e){j(e.element,e.behavior),N(e)}function F(e){return c(e)?e:L(e)?e.k:e?.transform?.k??1}function I(e,t=1){let n=F(e);return!c(n)||n===0?t:1/n}function L(e){return!!(e&&c(e.k)&&c(e.x)&&c(e.y))}function R(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 z(e){return e?e instanceof SVGSVGElement?e:e.closest(`svg`):null}e.ZOOM_DEFAULTS=E,e.applyZoomBehaviorTransform=M,e.applyZoomTransform=N,e.attachZoomBehavior=j,e.createZoomBehavior=A,e.createZoomConfig=k,e.createZoomTransform=O,e.get=f,e.getDefaultTranslateExtent=D,e.getFeatureKey=m,e.getInverseZoomScale=I,e.getMarkerTransform=T,e.getObjectStateUpdate=C,e.getTopoObject=x,e.getZoomScale=F,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=p,e.mapObjectState=S,e.resolveObjectStyle=w,e.setupZoom=P})(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
@@ -23,12 +23,44 @@ function isPlainObject(value) {
23
23
  const prototype = Object.getPrototypeOf(value);
24
24
  return prototype === null || prototype === Object.prototype;
25
25
  }
26
- async function get(url) {
27
- return (await fetch(url)).json();
28
- }
29
26
  function makeTransform(x, y, k) {
30
27
  return `translate(${x}, ${y}) scale(${k ?? 1})`;
31
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
+ }
63
+ }
32
64
 
33
65
  //#endregion
34
66
  //#region src/lib/feature.ts
@@ -55,21 +87,10 @@ function getFeatureKey(feature$1, idKey = "id", index) {
55
87
  */
56
88
  function makeProjection({ width, height, config, projection, geoJson }) {
57
89
  const mapProjection = projection();
58
- if (config?.center) {
59
- const [cx, cy] = config.center;
60
- if (isNumber(cx) && isNumber(cy)) mapProjection.center([cx, cy]);
61
- }
62
- if (config?.rotate) {
63
- const [rx, ry, rz] = config.rotate;
64
- if (isNumber(rx) && isNumber(ry)) mapProjection.rotate([
65
- rx,
66
- ry,
67
- isNumber(rz) ? rz : 0
68
- ]);
69
- }
70
- if (config && isNumber(config.scale)) mapProjection.scale(config.scale);
71
- if (geoJson) mapProjection.fitSize([width, height], geoJson);
72
- 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);
73
94
  return mapProjection;
74
95
  }
75
96
  /**
@@ -186,33 +207,18 @@ const ZOOM_DEFAULTS = {
186
207
  center: [0, 0],
187
208
  zoom: 1,
188
209
  minZoom: 1,
189
- maxZoom: 8,
190
- extent: [[0, 0], [0, 0]]
210
+ maxZoom: 8
191
211
  };
192
- function getDefaultTranslateExtent(context) {
193
- return [[0, 0], [context?.width ?? 0, context?.height ?? 0]];
194
- }
195
- function createZoomTransform(center, zoomLevel) {
196
- return zoomIdentity.translate(...center).scale(zoomLevel);
197
- }
198
- function createZoomConfig(options) {
199
- return {
200
- scaleExtent: [options.minZoom ?? ZOOM_DEFAULTS.minZoom, options.maxZoom ?? ZOOM_DEFAULTS.maxZoom],
201
- translateExtent: options.translateExtent ?? ZOOM_DEFAULTS.extent
202
- };
203
- }
204
212
  function createZoomBehavior(context, options = {}) {
205
213
  const behavior = zoom();
206
- const config = createZoomConfig({
207
- minZoom: options.minZoom,
208
- maxZoom: options.maxZoom,
209
- translateExtent: options.translateExtent ?? getDefaultTranslateExtent(context)
210
- });
211
- 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);
212
218
  if (options.onZoomStart) behavior.on("start", options.onZoomStart);
213
219
  if (options.onZoom) behavior.on("zoom", options.onZoom);
214
220
  if (options.onZoomEnd) behavior.on("end", options.onZoomEnd);
215
- applyZoomModifiers(behavior, options.modifiers);
221
+ applyModifiers(behavior, options.config);
216
222
  return behavior;
217
223
  }
218
224
  function attachZoomBehavior(element, behavior) {
@@ -228,7 +234,7 @@ function applyZoomBehaviorTransform(element, behavior, transform) {
228
234
  function applyZoomTransform(options) {
229
235
  const center = options.center ?? ZOOM_DEFAULTS.center;
230
236
  const zoom$1 = options.zoom ?? ZOOM_DEFAULTS.zoom;
231
- applyZoomBehaviorTransform(options.element, options.behavior, createZoomTransform(center, zoom$1));
237
+ applyZoomBehaviorTransform(options.element, options.behavior, zoomIdentity.translate(...center).scale(zoom$1));
232
238
  }
233
239
  function setupZoom(options) {
234
240
  attachZoomBehavior(options.element, options.behavior);
@@ -247,16 +253,6 @@ function getInverseZoomScale(source, fallback = 1) {
247
253
  function isZoomTransform(value) {
248
254
  return Boolean(value && isNumber(value.k) && isNumber(value.x) && isNumber(value.y));
249
255
  }
250
- function applyZoomModifiers(behavior, modifiers) {
251
- if (!modifiers) return;
252
- for (const [methodName, methodArgs] of Object.entries(modifiers)) {
253
- if (!methodName || methodArgs === void 0) continue;
254
- const modifier = behavior[methodName];
255
- if (typeof modifier !== "function") continue;
256
- const normalizedArgs = Array.isArray(methodArgs) ? methodArgs : [methodArgs];
257
- modifier.apply(behavior, normalizedArgs);
258
- }
259
- }
260
256
  function getSvgElement(element) {
261
257
  if (!element) return null;
262
258
  if (element instanceof SVGSVGElement) return element;
@@ -264,4 +260,4 @@ function getSvgElement(element) {
264
260
  }
265
261
 
266
262
  //#endregion
267
- export { ZOOM_DEFAULTS, applyZoomBehaviorTransform, applyZoomTransform, attachZoomBehavior, createZoomBehavior, createZoomConfig, createZoomTransform, get, getDefaultTranslateExtent, getFeatureKey, getInverseZoomScale, getMarkerTransform, getObjectStateUpdate, getTopoObject, getZoomScale, isDefined, isFunction, isNullish, isNumber, isPlainObject, isString, isStringOrNumber, isTopology, makeFeatures, makeMapContext, makeMesh, 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
3
  "type": "module",
4
- "version": "0.2.0",
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>",
@@ -52,6 +52,7 @@
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",