@accelint/map-toolkit 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.
Files changed (51) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/catalog-info.yaml +6 -3
  3. package/dist/cursor-coordinates/index.d.ts +3 -0
  4. package/dist/cursor-coordinates/index.js +3 -0
  5. package/dist/cursor-coordinates/index.js.map +1 -0
  6. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +76 -0
  7. package/dist/cursor-coordinates/use-cursor-coordinates.js +161 -0
  8. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -0
  9. package/dist/deckgl/base-map/events.d.ts +1 -0
  10. package/dist/deckgl/base-map/events.js +2 -1
  11. package/dist/deckgl/base-map/events.js.map +1 -1
  12. package/dist/deckgl/base-map/index.d.ts +1 -1
  13. package/dist/deckgl/base-map/index.js +69 -15
  14. package/dist/deckgl/base-map/index.js.map +1 -1
  15. package/dist/deckgl/base-map/provider.d.ts +12 -16
  16. package/dist/deckgl/base-map/provider.js +2 -6
  17. package/dist/deckgl/base-map/provider.js.map +1 -1
  18. package/dist/deckgl/base-map/types.d.ts +29 -8
  19. package/dist/deckgl/text-layer/character-sets.d.ts +7 -0
  20. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  21. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  22. package/dist/decorators/deckgl.d.ts +1 -1
  23. package/dist/decorators/deckgl.js.map +1 -1
  24. package/dist/map-mode/index.d.ts +1 -1
  25. package/dist/map-mode/index.js +1 -1
  26. package/dist/map-mode/store.d.ts +35 -109
  27. package/dist/map-mode/store.js +268 -289
  28. package/dist/map-mode/store.js.map +1 -1
  29. package/dist/map-mode/use-map-mode.d.ts +3 -5
  30. package/dist/map-mode/use-map-mode.js +8 -10
  31. package/dist/map-mode/use-map-mode.js.map +1 -1
  32. package/dist/metafile-esm.json +1 -1
  33. package/dist/viewport/constants.d.ts +9 -0
  34. package/dist/viewport/constants.js +11 -0
  35. package/dist/viewport/constants.js.map +1 -0
  36. package/dist/viewport/index.d.ts +13 -0
  37. package/dist/viewport/index.js +6 -0
  38. package/dist/viewport/index.js.map +1 -0
  39. package/dist/viewport/types.d.ts +22 -0
  40. package/dist/viewport/types.js +3 -0
  41. package/dist/viewport/types.js.map +1 -0
  42. package/dist/viewport/use-viewport-state.d.ts +85 -0
  43. package/dist/viewport/use-viewport-state.js +109 -0
  44. package/dist/viewport/use-viewport-state.js.map +1 -0
  45. package/dist/viewport/utils.d.ts +37 -0
  46. package/dist/viewport/utils.js +46 -0
  47. package/dist/viewport/utils.js.map +1 -0
  48. package/dist/viewport/viewport-size.d.ts +42 -0
  49. package/dist/viewport/viewport-size.js +16 -0
  50. package/dist/viewport/viewport-size.js.map +1 -0
  51. package/package.json +15 -20
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # @accelint/map-toolkit
2
2
 
3
+ ## 0.3.0
4
+ ### Minor Changes
5
+
6
+ - 7131cc0: Add `useCursorCoordinates`, a hook to retrieve the current coordinates for the mouse hovered position
7
+ - 874edd5: ## Breaking Change: Structured Clone Constraint
8
+
9
+ The event bus payload is now constrained to values that are serializable by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This constraint aligns the TypeScript types with the actual runtime behavior of the `BroadcastChannel` API.
10
+
11
+ ### What this means
12
+
13
+ You can no longer pass the following types in event payloads:
14
+
15
+ - Functions
16
+ - Symbols
17
+ - DOM nodes
18
+ - Prototype chains (class instances lose their methods)
19
+ - Properties with `undefined` values (they're omitted)
20
+
21
+ ### How to migrate
22
+
23
+ **❌ Before:**
24
+
25
+ ```typescript
26
+ bus.emit('user-action', {
27
+ callback: () => console.log('done'), // ❌ Function
28
+ userData: userClass, // ❌ Class instance with methods
29
+ element: document.getElementById('foo'), // ❌ DOM node
30
+ });
31
+ ```
32
+
33
+ **✅ After:**
34
+
35
+ ```typescript
36
+ // Option 1: Send only data, handle logic separately
37
+ bus.emit('user-action', {
38
+ actionType: 'complete', // ✅ Primitive
39
+ userData: { id: userClass.id, name: userClass.name }, // ✅ Plain object
40
+ elementId: 'foo', // ✅ Reference by ID
41
+ });
42
+
43
+ // Option 2: Use event types to trigger behavior
44
+ bus.on('user-action-complete', () => {
45
+ console.log('done'); // Handle callback logic in listener
46
+ });
47
+ ```
48
+
49
+ ### Supported types
50
+
51
+ - Primitives (string, number, boolean, null, BigInt)
52
+ - Plain objects and arrays
53
+ - Date, RegExp, Map, Set
54
+ - Typed arrays (Uint8Array, etc.)
55
+ - ArrayBuffer, Blob, File
56
+
57
+ ### Finding issues
58
+
59
+ TypeScript will now catch most violations at compile time. Runtime errors from `BroadcastChannel.postMessage()` indicate non-serializable values that slipped through.
60
+ - e8535c4: Adds ViewportSize component that shows the width and height of the viewport and updates with changes.
61
+ Adds useViewportState for more fine-grained control of viewport data (lat/lon/zoom/bounds). Exports `useEffectEvent` ponyfill from `@accelint/bus/react`.
62
+
63
+ ### Patch Changes
64
+
65
+ - 9ab8d72: Convert TextLayer README to Storybook doc and add JSDocs
66
+ - 99f6cd5: Add map-mode/store getCurrentModeOwner method to access the current map mode owner.
67
+ - 80db585: BREAKING CHANGES:
68
+ Design Toolkit no longer exports `tv`, this is now available from Design Foundation
69
+
70
+ Created new package @accelint/design-foundation that houses all of the Tailwind tokens, variants, base styles and utilities for easier reuse without having a dependency on the larger Design Toolkit
71
+
72
+ Updated Map Toolkit Storybook and NextJS demo app styles to import the new Design Foundation
73
+ - f157e42: update picking info to remove more unserializable properties in map-toolkit
74
+ - 7539bbb: update vite config in map-toolkit to re-enable base-map render in storybook preview
75
+ - 86a95cf: refactor map mode to use module useSyncExternalStore pattern instead of class.
76
+
3
77
  ## 0.2.0
4
78
 
5
79
  ### Minor Changes
package/catalog-info.yaml CHANGED
@@ -11,14 +11,15 @@ metadata:
11
11
 
12
12
  Dependencies:
13
13
 
14
- accelint_biome-config@1.0.2, accelint_bus@2.0.0,
14
+ accelint_biome-config@1.0.2, accelint_bus@3.0.0,
15
15
  accelint_constellation-tracker@1.0.1, accelint_core@0.5.0,
16
- accelint_design-toolkit@7.0.0, accelint_typescript-config@0.1.4,
16
+ accelint_design-foundation@1.0.0, accelint_design-toolkit@8.0.0,
17
+ accelint_geo@0.3.0, accelint_typescript-config@0.1.4,
17
18
  accelint_vitest-config@0.1.5
18
19
  annotations:
19
20
  backstage.io/edit-url: https://github.com/gohypergiant/standard-toolkit/blob/main/packages/map-toolkit/catalog-info.yaml
20
21
  backstage.io/techdocs-ref: dir:.
21
- package/version: 0.2.0
22
+ package/version: 0.3.0
22
23
  github.com/project-slug: gohypergiant/standard-toolkit
23
24
  links:
24
25
  - url: https://github.com/gohypergiant/standard-toolkit/tree/main/packages/map-toolkit
@@ -39,6 +40,8 @@ spec:
39
40
  - component:accelint_bus
40
41
  - component:accelint_constellation-tracker
41
42
  - component:accelint_core
43
+ - component:accelint_design-foundation
42
44
  - component:accelint_design-toolkit
45
+ - component:accelint_geo
43
46
  - component:accelint_typescript-config
44
47
  - component:accelint_vitest-config
@@ -0,0 +1,3 @@
1
+ export { CoordinateFormatTypes, clearCursorCoordinateState, useCursorCoordinates } from './use-cursor-coordinates.js';
2
+ import '@accelint/geo';
3
+ import '@accelint/core';
@@ -0,0 +1,3 @@
1
+ export { clearCursorCoordinateState, useCursorCoordinates } from './use-cursor-coordinates.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
@@ -0,0 +1,76 @@
1
+ import { coordinateSystems } from '@accelint/geo';
2
+ import { UniqueId } from '@accelint/core';
3
+
4
+ /**
5
+ * Supported coordinate format types for displaying map coordinates.
6
+ *
7
+ * @typedef {'dd' | 'ddm' | 'dms' | 'mgrs' | 'utm'} CoordinateFormatTypes
8
+ * @property dd - Decimal Degrees (e.g., "45.50000000 E / 30.25000000 N")
9
+ * @property ddm - Degrees Decimal Minutes (e.g., "45° 30' E / 30° 15' N")
10
+ * @property dms - Degrees Minutes Seconds (e.g., "45° 30' 0\" E / 30° 15' 0\" N")
11
+ * @property mgrs - Military Grid Reference System (e.g., "31U DQ 48251 11932")
12
+ * @property utm - Universal Transverse Mercator (e.g., "31N 448251 5411932")
13
+ */
14
+ type CoordinateFormatTypes = keyof typeof coordinateSystems;
15
+ /**
16
+ * Manually clear cursor coordinate state for a specific instanceId.
17
+ * This is typically not needed as cleanup happens automatically when all subscribers unmount.
18
+ * Use this only in advanced scenarios where manual cleanup is required.
19
+ *
20
+ * @param instanceId - The unique identifier for the map to clear
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * // Manual cleanup (rarely needed)
25
+ * clearCursorCoordinateState('my-map-instance');
26
+ * ```
27
+ */
28
+ declare function clearCursorCoordinateState(instanceId: UniqueId): void;
29
+ /**
30
+ * React hook that tracks and formats the cursor hover position coordinates on a map.
31
+ *
32
+ * Subscribes to map hover events via the event bus and converts coordinates to various
33
+ * geographic formats (Decimal Degrees, DMS, MGRS, UTM, etc.). The hook automatically
34
+ * filters events to only process those from the specified map instance.
35
+ *
36
+ * Uses `useSyncExternalStore` for concurrent-safe updates and efficient fan-out pattern
37
+ * where multiple components can subscribe to the same map's coordinates with a single
38
+ * bus listener.
39
+ *
40
+ * @param id - Optional map instance ID. If not provided, attempts to use the ID from MapProvider context.
41
+ * @returns Object containing the formatted coordinate string and format setter function
42
+ * @property formattedCoord - The formatted coordinate string (defaults to "--, --" when no position)
43
+ * @property setFormat - Function to change the coordinate format system
44
+ * @throws {Error} When no id is provided and hook is used outside MapProvider context
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * import { uuid } from '@accelint/core';
49
+ * import { useCursorCoordinates } from '@accelint/map-toolkit/cursor-coordinates';
50
+ *
51
+ * const MAP_ID = uuid();
52
+ *
53
+ * function CoordinateDisplay() {
54
+ * const { formattedCoord, setFormat } = useCursorCoordinates(MAP_ID);
55
+ *
56
+ * return (
57
+ * <div>
58
+ * <select onChange={(e) => setFormat(e.target.value as CoordinateFormatTypes)}>
59
+ * <option value="dd">Decimal Degrees</option>
60
+ * <option value="ddm">Degrees Decimal Minutes</option>
61
+ * <option value="dms">Degrees Minutes Seconds</option>
62
+ * <option value="mgrs">MGRS</option>
63
+ * <option value="utm">UTM</option>
64
+ * </select>
65
+ * <div>{formattedCoord}</div>
66
+ * </div>
67
+ * );
68
+ * }
69
+ * ```
70
+ */
71
+ declare function useCursorCoordinates(id?: UniqueId): {
72
+ formattedCoord: string;
73
+ setFormat: (format: CoordinateFormatTypes) => void;
74
+ };
75
+
76
+ export { type CoordinateFormatTypes, clearCursorCoordinateState, useCursorCoordinates };
@@ -0,0 +1,161 @@
1
+ import 'client-only';
2
+ import { Broadcast } from '@accelint/bus';
3
+ import { createCoordinate, coordinateSystems } from '@accelint/geo';
4
+ import { useContext, useSyncExternalStore, useMemo } from 'react';
5
+ import { MapEvents } from '../deckgl/base-map/events.js';
6
+ import { MapContext } from '../deckgl/base-map/provider.js';
7
+
8
+ const bus = Broadcast.getInstance();
9
+ const create = createCoordinate(coordinateSystems.dd, "LONLAT");
10
+ const MAX_LONGITUDE = 180;
11
+ const LONGITUDE_RANGE = 360;
12
+ const COORDINATE_PRECISION = 8;
13
+ const DEFAULT_COORDINATE = "--, --";
14
+ const prepareCoord = (coord) => {
15
+ let lon = coord[0];
16
+ lon = ((lon + MAX_LONGITUDE) % LONGITUDE_RANGE + LONGITUDE_RANGE) % LONGITUDE_RANGE - MAX_LONGITUDE;
17
+ const lat = coord[1];
18
+ const lonStr = `${Math.abs(lon).toFixed(COORDINATE_PRECISION)} ${lon < 0 ? "W" : "E"}`;
19
+ const latStr = `${Math.abs(lat).toFixed(COORDINATE_PRECISION)} ${lat < 0 ? "S" : "N"}`;
20
+ return `${lonStr} / ${latStr}`;
21
+ };
22
+ function isValidCoordinate(value) {
23
+ return Array.isArray(value) && value.length === 2 && value.every(Number.isFinite);
24
+ }
25
+ const coordinateStore = /* @__PURE__ */ new Map();
26
+ const componentSubscribers = /* @__PURE__ */ new Map();
27
+ const busUnsubscribers = /* @__PURE__ */ new Map();
28
+ const subscriptionCache = /* @__PURE__ */ new Map();
29
+ const snapshotCache = /* @__PURE__ */ new Map();
30
+ function ensureBusListener(instanceId) {
31
+ if (busUnsubscribers.has(instanceId)) {
32
+ return;
33
+ }
34
+ const unsub = bus.on(MapEvents.hover, (data) => {
35
+ const eventId = data.payload.id;
36
+ if (instanceId !== eventId) {
37
+ return;
38
+ }
39
+ const coords = data.payload.info.coordinate;
40
+ const state = coordinateStore.get(instanceId);
41
+ if (state) {
42
+ if (isValidCoordinate(coords)) {
43
+ state.coordinate = coords;
44
+ } else {
45
+ state.coordinate = null;
46
+ }
47
+ const subscribers = componentSubscribers.get(instanceId);
48
+ if (subscribers) {
49
+ for (const onStoreChange of subscribers) {
50
+ onStoreChange();
51
+ }
52
+ }
53
+ }
54
+ });
55
+ busUnsubscribers.set(instanceId, unsub);
56
+ }
57
+ function cleanupBusListenerIfNeeded(instanceId) {
58
+ const subscribers = componentSubscribers.get(instanceId);
59
+ if (!subscribers || subscribers.size === 0) {
60
+ const unsub = busUnsubscribers.get(instanceId);
61
+ if (unsub) {
62
+ unsub();
63
+ busUnsubscribers.delete(instanceId);
64
+ }
65
+ coordinateStore.delete(instanceId);
66
+ componentSubscribers.delete(instanceId);
67
+ subscriptionCache.delete(instanceId);
68
+ snapshotCache.delete(instanceId);
69
+ }
70
+ }
71
+ function getOrCreateSubscription(instanceId) {
72
+ const subscription = subscriptionCache.get(instanceId) ?? ((onStoreChange) => {
73
+ ensureBusListener(instanceId);
74
+ let subscriberSet = componentSubscribers.get(instanceId);
75
+ if (!subscriberSet) {
76
+ subscriberSet = /* @__PURE__ */ new Set();
77
+ componentSubscribers.set(instanceId, subscriberSet);
78
+ }
79
+ subscriberSet.add(onStoreChange);
80
+ return () => {
81
+ const currentSubscriberSet = componentSubscribers.get(instanceId);
82
+ if (currentSubscriberSet) {
83
+ currentSubscriberSet.delete(onStoreChange);
84
+ }
85
+ cleanupBusListenerIfNeeded(instanceId);
86
+ };
87
+ });
88
+ subscriptionCache.set(instanceId, subscription);
89
+ return subscription;
90
+ }
91
+ function getOrCreateSnapshot(instanceId) {
92
+ let cached = snapshotCache.get(instanceId);
93
+ if (!cached) {
94
+ cached = () => {
95
+ const state = coordinateStore.get(instanceId);
96
+ if (!state) {
97
+ return DEFAULT_COORDINATE;
98
+ }
99
+ if (!state.coordinate) {
100
+ return DEFAULT_COORDINATE;
101
+ }
102
+ const coord = create(prepareCoord(state.coordinate));
103
+ return coord[state.format]();
104
+ };
105
+ snapshotCache.set(instanceId, cached);
106
+ }
107
+ return cached;
108
+ }
109
+ function setFormatForInstance(instanceId, format) {
110
+ const state = coordinateStore.get(instanceId);
111
+ if (state) {
112
+ state.format = format;
113
+ const subscribers = componentSubscribers.get(instanceId);
114
+ if (subscribers) {
115
+ for (const onStoreChange of subscribers) {
116
+ onStoreChange();
117
+ }
118
+ }
119
+ }
120
+ }
121
+ function clearCursorCoordinateState(instanceId) {
122
+ const unsub = busUnsubscribers.get(instanceId);
123
+ if (unsub) {
124
+ unsub();
125
+ busUnsubscribers.delete(instanceId);
126
+ }
127
+ coordinateStore.delete(instanceId);
128
+ componentSubscribers.delete(instanceId);
129
+ subscriptionCache.delete(instanceId);
130
+ snapshotCache.delete(instanceId);
131
+ }
132
+ function useCursorCoordinates(id) {
133
+ const contextId = useContext(MapContext);
134
+ const actualId = id ?? contextId;
135
+ if (!actualId) {
136
+ throw new Error(
137
+ "useCursorCoordinates requires either an id parameter or to be used within a MapProvider"
138
+ );
139
+ }
140
+ if (!coordinateStore.has(actualId)) {
141
+ coordinateStore.set(actualId, {
142
+ coordinate: null,
143
+ format: "dd"
144
+ });
145
+ }
146
+ const formattedCoord = useSyncExternalStore(
147
+ getOrCreateSubscription(actualId),
148
+ getOrCreateSnapshot(actualId)
149
+ );
150
+ return useMemo(
151
+ () => ({
152
+ formattedCoord,
153
+ setFormat: (format) => setFormatForInstance(actualId, format)
154
+ }),
155
+ [formattedCoord, actualId]
156
+ );
157
+ }
158
+
159
+ export { clearCursorCoordinateState, useCursorCoordinates };
160
+ //# sourceMappingURL=use-cursor-coordinates.js.map
161
+ //# sourceMappingURL=use-cursor-coordinates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cursor-coordinates/use-cursor-coordinates.ts"],"names":[],"mappings":"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","file":"use-cursor-coordinates.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n'use client';\n\nimport 'client-only';\nimport { Broadcast } from '@accelint/bus';\nimport { coordinateSystems, createCoordinate } from '@accelint/geo';\nimport { useContext, useMemo, useSyncExternalStore } from 'react';\nimport { MapEvents } from '../deckgl/base-map/events';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapEventType, MapHoverEvent } from '../deckgl/base-map/types';\n\n/**\n * Supported coordinate format types for displaying map coordinates.\n *\n * @typedef {'dd' | 'ddm' | 'dms' | 'mgrs' | 'utm'} CoordinateFormatTypes\n * @property dd - Decimal Degrees (e.g., \"45.50000000 E / 30.25000000 N\")\n * @property ddm - Degrees Decimal Minutes (e.g., \"45° 30' E / 30° 15' N\")\n * @property dms - Degrees Minutes Seconds (e.g., \"45° 30' 0\\\" E / 30° 15' 0\\\" N\")\n * @property mgrs - Military Grid Reference System (e.g., \"31U DQ 48251 11932\")\n * @property utm - Universal Transverse Mercator (e.g., \"31N 448251 5411932\")\n */\nexport type CoordinateFormatTypes = keyof typeof coordinateSystems;\n\nconst bus = Broadcast.getInstance<MapEventType>();\nconst create = createCoordinate(coordinateSystems.dd, 'LONLAT');\n\nconst MAX_LONGITUDE = 180;\nconst LONGITUDE_RANGE = 360;\nconst COORDINATE_PRECISION = 8;\nconst DEFAULT_COORDINATE = '--, --';\n\n/**\n * Prepares coordinates for display by normalizing longitude and formatting with cardinal directions.\n * Normalizes longitude to -180 to 180 range and formats both longitude and latitude with\n * compass directions (E/W for longitude, N/S for latitude).\n *\n * @param coord - Tuple of [longitude, latitude] coordinates\n * @returns Formatted string in the format \"LON.NNNNNNNN E/W / LAT.NNNNNNNN N/S\"\n */\nconst prepareCoord = (coord: [number, number]) => {\n // Normalize longitude to -180 to 180 range (handles wraparound including multi-revolution values)\n let lon = coord[0];\n lon =\n ((((lon + MAX_LONGITUDE) % LONGITUDE_RANGE) + LONGITUDE_RANGE) %\n LONGITUDE_RANGE) -\n MAX_LONGITUDE;\n\n const lat = coord[1];\n const lonStr = `${Math.abs(lon).toFixed(COORDINATE_PRECISION)} ${lon < 0 ? 'W' : 'E'}`;\n const latStr = `${Math.abs(lat).toFixed(COORDINATE_PRECISION)} ${lat < 0 ? 'S' : 'N'}`;\n\n return `${lonStr} / ${latStr}`;\n};\n\n/**\n * Type guard to validate that a value is a proper coordinate tuple.\n * Checks that the value is an array with exactly two finite numbers.\n *\n * @param value - Value to validate as a coordinate\n * @returns True if value is a valid [longitude, latitude] tuple\n */\nfunction isValidCoordinate(value?: number[]): value is [number, number] {\n return (\n Array.isArray(value) && value.length === 2 && value.every(Number.isFinite)\n );\n}\n\n/**\n * State stored for each map instance's cursor coordinates\n */\ntype CursorCoordinateState = {\n coordinate: [number, number] | null;\n format: CoordinateFormatTypes;\n};\n\n/**\n * Store for cursor coordinate state keyed by instanceId\n */\nconst coordinateStore = new Map<UniqueId, CursorCoordinateState>();\n\n/**\n * Track React component subscribers per instanceId (for fan-out notifications).\n * Each Set contains onStoreChange callbacks from useSyncExternalStore.\n */\nconst componentSubscribers = new Map<UniqueId, Set<() => void>>();\n\n/**\n * Cache of bus unsubscribe functions (1 per instanceId).\n * This ensures we only have one bus listener per map, regardless of\n * how many React components subscribe to it.\n */\nconst busUnsubscribers = new Map<UniqueId, () => void>();\n\ntype Subscription = (onStoreChange: () => void) => () => void;\n/**\n * Cache of subscription functions per instanceId to avoid recreating on every render\n */\nconst subscriptionCache = new Map<UniqueId, Subscription>();\n\n/**\n * Cache of snapshot functions per instanceId to maintain referential stability\n */\nconst snapshotCache = new Map<UniqueId, () => string>();\n\n/**\n * Ensures a single bus listener exists for the given instanceId.\n * All React subscribers will be notified via fan-out when the bus event fires.\n * This prevents creating N bus listeners for N React components.\n *\n * @param instanceId - The unique identifier for the map\n */\nfunction ensureBusListener(instanceId: UniqueId): void {\n if (busUnsubscribers.has(instanceId)) {\n return; // Already listening\n }\n\n const unsub = bus.on(MapEvents.hover, (data: MapHoverEvent) => {\n const eventId = data.payload.id;\n\n // Ignore hover events from other possible map instances\n if (instanceId !== eventId) {\n return;\n }\n\n const coords = data.payload.info.coordinate;\n const state = coordinateStore.get(instanceId);\n\n // Update coordinate if valid, or clear if invalid\n if (state) {\n if (isValidCoordinate(coords)) {\n state.coordinate = coords as [number, number];\n } else {\n state.coordinate = null;\n }\n\n // Fan-out: notify all React subscribers\n const subscribers = componentSubscribers.get(instanceId);\n if (subscribers) {\n for (const onStoreChange of subscribers) {\n onStoreChange();\n }\n }\n }\n });\n\n busUnsubscribers.set(instanceId, unsub);\n}\n\n/**\n * Cleans up the bus listener if no React subscribers remain.\n *\n * @param instanceId - The unique identifier for the map\n */\nfunction cleanupBusListenerIfNeeded(instanceId: UniqueId): void {\n const subscribers = componentSubscribers.get(instanceId);\n\n if (!subscribers || subscribers.size === 0) {\n // No more React subscribers - clean up bus listener\n const unsub = busUnsubscribers.get(instanceId);\n if (unsub) {\n unsub();\n busUnsubscribers.delete(instanceId);\n }\n\n // Clean up all state\n coordinateStore.delete(instanceId);\n componentSubscribers.delete(instanceId);\n subscriptionCache.delete(instanceId);\n snapshotCache.delete(instanceId);\n }\n}\n\n/**\n * Creates or retrieves a cached subscription function for a given instanceId.\n * Uses a fan-out pattern: 1 bus listener -> N React subscribers.\n * Automatically cleans up coordinate state when the last subscriber unsubscribes.\n *\n * @param instanceId - The unique identifier for the map\n * @returns A subscription function for useSyncExternalStore\n */\nfunction getOrCreateSubscription(\n instanceId: UniqueId,\n): (onStoreChange: () => void) => () => void {\n const subscription =\n subscriptionCache.get(instanceId) ??\n ((onStoreChange: () => void) => {\n // Ensure single bus listener exists for this instanceId\n ensureBusListener(instanceId);\n\n // Get or create the subscriber set for this map instance, then add this component's callback\n let subscriberSet = componentSubscribers.get(instanceId);\n if (!subscriberSet) {\n subscriberSet = new Set();\n componentSubscribers.set(instanceId, subscriberSet);\n }\n subscriberSet.add(onStoreChange);\n\n // Return cleanup function to remove this component's subscription\n return () => {\n const currentSubscriberSet = componentSubscribers.get(instanceId);\n if (currentSubscriberSet) {\n currentSubscriberSet.delete(onStoreChange);\n }\n\n // Clean up bus listener if this was the last React subscriber\n cleanupBusListenerIfNeeded(instanceId);\n };\n });\n\n subscriptionCache.set(instanceId, subscription);\n\n return subscription;\n}\n\n/**\n * Creates or retrieves a cached snapshot function for a given instanceId.\n * The function must read from the store on every call to get current state.\n *\n * @param instanceId - The unique identifier for the map\n * @returns A snapshot function for useSyncExternalStore that returns formatted coordinate string\n */\nfunction getOrCreateSnapshot(instanceId: UniqueId): () => string {\n let cached = snapshotCache.get(instanceId);\n\n if (!cached) {\n // Create a snapshot function that always reads current state from the store\n cached = () => {\n const state = coordinateStore.get(instanceId);\n\n if (!state) {\n return DEFAULT_COORDINATE;\n }\n\n if (!state.coordinate) {\n return DEFAULT_COORDINATE;\n }\n\n const coord = create(prepareCoord(state.coordinate));\n return coord[state.format]();\n };\n\n snapshotCache.set(instanceId, cached);\n }\n\n return cached;\n}\n\n/**\n * Updates the format for a given map instance and notifies subscribers.\n *\n * @param instanceId - The unique identifier for the map\n * @param format - The new coordinate format to use\n */\nfunction setFormatForInstance(\n instanceId: UniqueId,\n format: CoordinateFormatTypes,\n): void {\n const state = coordinateStore.get(instanceId);\n if (state) {\n state.format = format;\n\n // Notify all subscribers of the format change\n // The coordinate remains unchanged; only the display format changes\n const subscribers = componentSubscribers.get(instanceId);\n if (subscribers) {\n for (const onStoreChange of subscribers) {\n onStoreChange();\n }\n }\n }\n}\n\n/**\n * Manually clear cursor coordinate state for a specific instanceId.\n * This is typically not needed as cleanup happens automatically when all subscribers unmount.\n * Use this only in advanced scenarios where manual cleanup is required.\n *\n * @param instanceId - The unique identifier for the map to clear\n *\n * @example\n * ```tsx\n * // Manual cleanup (rarely needed)\n * clearCursorCoordinateState('my-map-instance');\n * ```\n */\nexport function clearCursorCoordinateState(instanceId: UniqueId): void {\n // Unsubscribe from bus if listening\n const unsub = busUnsubscribers.get(instanceId);\n if (unsub) {\n unsub();\n busUnsubscribers.delete(instanceId);\n }\n\n // Clear all state\n coordinateStore.delete(instanceId);\n componentSubscribers.delete(instanceId);\n subscriptionCache.delete(instanceId);\n snapshotCache.delete(instanceId);\n}\n\n/**\n * React hook that tracks and formats the cursor hover position coordinates on a map.\n *\n * Subscribes to map hover events via the event bus and converts coordinates to various\n * geographic formats (Decimal Degrees, DMS, MGRS, UTM, etc.). The hook automatically\n * filters events to only process those from the specified map instance.\n *\n * Uses `useSyncExternalStore` for concurrent-safe updates and efficient fan-out pattern\n * where multiple components can subscribe to the same map's coordinates with a single\n * bus listener.\n *\n * @param id - Optional map instance ID. If not provided, attempts to use the ID from MapProvider context.\n * @returns Object containing the formatted coordinate string and format setter function\n * @property formattedCoord - The formatted coordinate string (defaults to \"--, --\" when no position)\n * @property setFormat - Function to change the coordinate format system\n * @throws {Error} When no id is provided and hook is used outside MapProvider context\n *\n * @example\n * ```tsx\n * import { uuid } from '@accelint/core';\n * import { useCursorCoordinates } from '@accelint/map-toolkit/cursor-coordinates';\n *\n * const MAP_ID = uuid();\n *\n * function CoordinateDisplay() {\n * const { formattedCoord, setFormat } = useCursorCoordinates(MAP_ID);\n *\n * return (\n * <div>\n * <select onChange={(e) => setFormat(e.target.value as CoordinateFormatTypes)}>\n * <option value=\"dd\">Decimal Degrees</option>\n * <option value=\"ddm\">Degrees Decimal Minutes</option>\n * <option value=\"dms\">Degrees Minutes Seconds</option>\n * <option value=\"mgrs\">MGRS</option>\n * <option value=\"utm\">UTM</option>\n * </select>\n * <div>{formattedCoord}</div>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCursorCoordinates(id?: UniqueId) {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useCursorCoordinates requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n // Initialize state for this map instance BEFORE subscribing\n // This ensures the bus listener has a store to write to\n if (!coordinateStore.has(actualId)) {\n coordinateStore.set(actualId, {\n coordinate: null,\n format: 'dd',\n });\n }\n\n // Subscribe to coordinate changes using useSyncExternalStore\n // This must happen after store initialization\n const formattedCoord = useSyncExternalStore<string>(\n getOrCreateSubscription(actualId),\n getOrCreateSnapshot(actualId),\n );\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n formattedCoord,\n setFormat: (format: CoordinateFormatTypes) =>\n setFormatForInstance(actualId, format),\n }),\n [formattedCoord, actualId],\n );\n}\n"]}
@@ -2,6 +2,7 @@ declare const MapEventsNamespace = "map";
2
2
  declare const MapEvents: {
3
3
  readonly click: "map:click";
4
4
  readonly hover: "map:hover";
5
+ readonly viewport: "map:viewport";
5
6
  };
6
7
 
7
8
  export { MapEvents, MapEventsNamespace };
@@ -1,7 +1,8 @@
1
1
  const MapEventsNamespace = "map";
2
2
  const MapEvents = {
3
3
  click: `${MapEventsNamespace}:click`,
4
- hover: `${MapEventsNamespace}:hover`
4
+ hover: `${MapEventsNamespace}:hover`,
5
+ viewport: `${MapEventsNamespace}:viewport`
5
6
  };
6
7
 
7
8
  export { MapEvents, MapEventsNamespace };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/deckgl/base-map/events.ts"],"names":[],"mappings":"AAYO,MAAM,kBAAA,GAAqB;AAE3B,MAAM,SAAA,GAAY;AAAA,EACvB,KAAA,EAAO,GAAG,kBAAkB,CAAA,MAAA,CAAA;AAAA,EAC5B,KAAA,EAAO,GAAG,kBAAkB,CAAA,MAAA;AAC9B","file":"events.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport const MapEventsNamespace = 'map';\n\nexport const MapEvents = {\n click: `${MapEventsNamespace}:click`,\n hover: `${MapEventsNamespace}:hover`,\n} as const;\n"]}
1
+ {"version":3,"sources":["../../../src/deckgl/base-map/events.ts"],"names":[],"mappings":"AAYO,MAAM,kBAAA,GAAqB;AAE3B,MAAM,SAAA,GAAY;AAAA,EACvB,KAAA,EAAO,GAAG,kBAAkB,CAAA,MAAA,CAAA;AAAA,EAC5B,KAAA,EAAO,GAAG,kBAAkB,CAAA,MAAA,CAAA;AAAA,EAC5B,QAAA,EAAU,GAAG,kBAAkB,CAAA,SAAA;AACjC","file":"events.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport const MapEventsNamespace = 'map';\n\nexport const MapEvents = {\n click: `${MapEventsNamespace}:click`,\n hover: `${MapEventsNamespace}:hover`,\n viewport: `${MapEventsNamespace}:viewport`,\n} as const;\n"]}
@@ -93,6 +93,6 @@ type BaseMapProps = DeckglProps & {
93
93
  * }
94
94
  * ```
95
95
  */
96
- declare function BaseMap({ className, children, id, onClick, onHover, parameters, ...rest }: BaseMapProps): react_jsx_runtime.JSX.Element;
96
+ declare function BaseMap({ id, className, children, controller, interleaved, parameters, useDevicePixels, widgets: widgetsProp, onClick, onHover, onViewStateChange, ...rest }: BaseMapProps): react_jsx_runtime.JSX.Element;
97
97
 
98
98
  export { BaseMap, type BaseMapProps };
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import 'client-only';
3
- import { useEmit } from '@accelint/bus/react';
3
+ import { useEmit, useEffectEvent } from '@accelint/bus/react';
4
4
  import { useDeckgl, Deckgl } from '@deckgl-fiber-renderer/dom';
5
5
  import { useId, useMemo, useCallback } from 'react';
6
6
  import { INITIAL_VIEW_STATE } from '../../maplibre/constants.js';
@@ -10,12 +10,17 @@ import { MapEvents } from './events.js';
10
10
  import { MapProvider } from './provider.js';
11
11
 
12
12
  function BaseMap({
13
+ id,
13
14
  className,
14
15
  children,
15
- id,
16
+ controller = true,
17
+ interleaved = true,
18
+ parameters = {},
19
+ useDevicePixels = false,
20
+ widgets: widgetsProp = [],
16
21
  onClick,
17
22
  onHover,
18
- parameters,
23
+ onViewStateChange,
19
24
  ...rest
20
25
  }) {
21
26
  const deckglInstance = useDeckgl();
@@ -28,17 +33,24 @@ function BaseMap({
28
33
  doubleClickZoom: false,
29
34
  dragRotate: false,
30
35
  pitchWithRotate: false,
31
- rollEnabled: false
36
+ rollEnabled: false,
37
+ attributionControl: { compact: true }
32
38
  }),
33
39
  [container]
34
40
  );
35
41
  useMapLibre(deckglInstance, BASE_MAP_STYLE, mapOptions);
36
42
  const emitClick = useEmit(MapEvents.click);
37
43
  const emitHover = useEmit(MapEvents.hover);
38
- const handleMapClick = useCallback(
44
+ const emitViewport = useEmit(MapEvents.viewport);
45
+ const handleClick = useCallback(
39
46
  (info, event) => {
40
47
  onClick?.(info, event);
41
- const { viewport, ...infoRest } = info;
48
+ const { viewport, layer, sourceLayer, ...infoRest } = info;
49
+ const infoObject = {
50
+ layerId: layer?.id,
51
+ sourceLayerId: sourceLayer?.id,
52
+ ...infoRest
53
+ };
42
54
  const {
43
55
  stopImmediatePropagation,
44
56
  stopPropagation,
@@ -51,17 +63,22 @@ function BaseMap({
51
63
  ...eventRest
52
64
  } = event;
53
65
  emitClick({
54
- info: infoRest,
66
+ info: infoObject,
55
67
  event: eventRest,
56
68
  id
57
69
  });
58
70
  },
59
71
  [emitClick, id, onClick]
60
72
  );
61
- const handleMapHover = useCallback(
73
+ const handleHover = useCallback(
62
74
  (info, event) => {
63
75
  onHover?.(info, event);
64
- const { viewport, ...infoRest } = info;
76
+ const { viewport, layer, sourceLayer, ...infoRest } = info;
77
+ const infoObject = {
78
+ layerId: layer?.id,
79
+ sourceLayerId: sourceLayer?.id,
80
+ ...infoRest
81
+ };
65
82
  const {
66
83
  stopImmediatePropagation,
67
84
  stopPropagation,
@@ -72,22 +89,59 @@ function BaseMap({
72
89
  ...eventRest
73
90
  } = event;
74
91
  emitHover({
75
- info: infoRest,
92
+ info: infoObject,
76
93
  event: eventRest,
77
94
  id
78
95
  });
79
96
  },
80
97
  [emitHover, id, onHover]
81
98
  );
99
+ const handleViewStateChange = useEffectEvent(
100
+ (params) => {
101
+ onViewStateChange?.(params);
102
+ const {
103
+ viewId,
104
+ viewState: { latitude, longitude, zoom }
105
+ } = params;
106
+ const viewport = deckglInstance._deck.getViewports()?.find((vp) => vp.id === viewId);
107
+ emitViewport({
108
+ id,
109
+ bounds: viewport?.getBounds(),
110
+ latitude,
111
+ longitude,
112
+ zoom,
113
+ width: viewport?.width ?? 0,
114
+ height: viewport?.height ?? 0
115
+ });
116
+ }
117
+ );
118
+ const handleLoad = useEffectEvent(() => {
119
+ deckglInstance._deck.getViewports().forEach((vp) => {
120
+ handleViewStateChange({
121
+ viewId: vp.id,
122
+ viewState: {
123
+ latitude: vp.latitude,
124
+ longitude: vp.longitude,
125
+ zoom: vp.zoom,
126
+ id: vp.id,
127
+ bounds: vp.getBounds(),
128
+ width: vp.width,
129
+ height: vp.height
130
+ }
131
+ });
132
+ });
133
+ });
82
134
  return /* @__PURE__ */ jsx("div", { id: container, className, children: /* @__PURE__ */ jsx(MapProvider, { id, children: /* @__PURE__ */ jsx(
83
135
  Deckgl,
84
136
  {
85
137
  ...rest,
86
- controller: true,
87
- interleaved: true,
88
- useDevicePixels: false,
89
- onHover: handleMapHover,
90
- onClick: handleMapClick,
138
+ controller,
139
+ interleaved,
140
+ useDevicePixels,
141
+ onClick: handleClick,
142
+ onHover: handleHover,
143
+ onLoad: handleLoad,
144
+ onViewStateChange: handleViewStateChange,
91
145
  parameters: { ...PARAMETERS, ...parameters },
92
146
  children
93
147
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/deckgl/base-map/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport 'client-only';\nimport { useEmit } from '@accelint/bus/react';\nimport { Deckgl, useDeckgl } from '@deckgl-fiber-renderer/dom';\nimport { useCallback, useId, useMemo } from 'react';\nimport { INITIAL_VIEW_STATE } from '../../maplibre/constants';\nimport { useMapLibre } from '../../maplibre/hooks/use-maplibre';\nimport { BASE_MAP_STYLE, PARAMETERS } from './constants';\nimport { MapEvents } from './events';\nimport { MapProvider } from './provider';\nimport type { UniqueId } from '@accelint/core';\nimport type { PickingInfo } from '@deck.gl/core';\nimport type { DeckglProps } from '@deckgl-fiber-renderer/types';\nimport type { IControl } from 'maplibre-gl';\nimport type { MjolnirGestureEvent, MjolnirPointerEvent } from 'mjolnir.js';\nimport type { MapClickEvent, MapHoverEvent } from './types';\n\n/**\n * Props for the BaseMap component.\n * Extends all Deck.gl props and adds additional map-specific properties.\n */\nexport type BaseMapProps = DeckglProps & {\n /** Optional CSS class name to apply to the map container element */\n className?: string;\n /**\n * Unique identifier for this map instance (required).\n *\n * Used to isolate map mode state between multiple map instances (e.g., main map vs minimap).\n * This should be a UUID generated using `uuid()` from `@accelint/core`.\n *\n * The same id should be passed to `useMapMode()` when accessing map mode state\n * from components rendered outside of the BaseMap's children (i.e., as siblings).\n */\n id: UniqueId;\n};\n\n/**\n * A React component that provides a Deck.gl-powered base map with MapLibre GL integration.\n *\n * This component serves as the foundation for building interactive map applications with\n * support for click and hover events through a centralized event bus. It integrates\n * Deck.gl for 3D visualizations with MapLibre GL for the base map tiles.\n *\n * **Map Mode Integration**: BaseMap automatically creates a `MapProvider` internally,\n * which sets up the map mode state management for this instance.\n * - **Children**: Only Deck.gl layer components can be rendered as children. Custom Deck.gl\n * layers can use `useMapMode()` without parameters to access context.\n * - **Siblings**: UI components (buttons, toolbars, etc.) must be rendered as siblings\n * and pass `id` to `useMapMode(id)`.\n *\n * **Event Bus**: Click and hover events are emitted through the event bus with the `id`\n * included in the payload, allowing multiple map instances to coexist without interference.\n *\n * @param props - Component props including id (required), className, onClick, onHover, and all Deck.gl props\n * @returns A map component with Deck.gl and MapLibre GL integration\n *\n * @example\n * Basic usage with id (recommended: module-level constant):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { View } from '@deckgl-fiber-renderer/dom';\n * import { uuid } from '@accelint/core';\n *\n * // Create id at module level for stability and easy sharing\n * const MAIN_MAP_ID = uuid();\n *\n * export function MapView() {\n * return (\n * <BaseMap className=\"w-full h-full\" id={MAIN_MAP_ID}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example\n * With map mode and event handlers (module-level constant for sharing):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { useMapMode } from '@accelint/map-toolkit/map-mode';\n * import { uuid } from '@accelint/core';\n * import type { PickingInfo } from '@deck.gl/core';\n * import type { MjolnirGestureEvent } from 'mjolnir.js';\n *\n * // Module-level constant - stable and shareable across all components\n * const MAIN_MAP_ID = uuid();\n *\n * function Toolbar() {\n * // Access map mode using the shared id\n * const { mode, requestModeChange } = useMapMode(MAIN_MAP_ID);\n * return <div>Current mode: {mode}</div>;\n * }\n *\n * export function InteractiveMap() {\n * const handleClick = (info: PickingInfo, event: MjolnirGestureEvent) => {\n * console.log('Clicked:', info.object);\n * };\n *\n * return (\n * <div className=\"relative w-full h-full\">\n * <BaseMap className=\"absolute inset-0\" id={MAIN_MAP_ID} onClick={handleClick}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * <Toolbar />\n * </div>\n * );\n * }\n * ```\n */\nexport function BaseMap({\n className,\n children,\n id,\n onClick,\n onHover,\n parameters,\n ...rest\n}: BaseMapProps) {\n const deckglInstance = useDeckgl();\n const container = useId();\n\n // Memoize MapLibre options to avoid creating new object on every render\n const mapOptions = useMemo(\n () => ({\n container,\n center: [INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude] as [\n number,\n number,\n ],\n zoom: INITIAL_VIEW_STATE.zoom,\n doubleClickZoom: false,\n dragRotate: false,\n pitchWithRotate: false,\n rollEnabled: false,\n }),\n [container],\n );\n\n // Use the custom hook to handle MapLibre\n useMapLibre(deckglInstance as IControl, BASE_MAP_STYLE, mapOptions);\n\n const emitClick = useEmit<MapClickEvent>(MapEvents.click);\n const emitHover = useEmit<MapHoverEvent>(MapEvents.hover);\n\n const handleMapClick = useCallback(\n (info: PickingInfo, event: MjolnirGestureEvent) => {\n // send full pickingInfo and event to user-defined onClick\n onClick?.(info, event);\n\n // the bus cannot serialize functions, so we omit them from the event payloads\n const { viewport, ...infoRest } = info;\n const {\n stopImmediatePropagation,\n stopPropagation,\n preventDefault,\n srcEvent,\n rootElement,\n target,\n changedPointers,\n pointers,\n ...eventRest\n } = event;\n\n emitClick({\n info: infoRest,\n event: eventRest,\n id,\n });\n },\n [emitClick, id, onClick],\n );\n\n const handleMapHover = useCallback(\n (info: PickingInfo, event: MjolnirPointerEvent) => {\n // send full pickingInfo and event to user-defined onHover\n onHover?.(info, event);\n\n // the bus cannot serialize functions, so we omit them from the event payloads\n const { viewport, ...infoRest } = info;\n const {\n stopImmediatePropagation,\n stopPropagation,\n preventDefault,\n srcEvent,\n rootElement,\n target,\n ...eventRest\n } = event;\n\n emitHover({\n info: infoRest,\n event: eventRest,\n id,\n });\n },\n [emitHover, id, onHover],\n );\n\n return (\n <div id={container} className={className}>\n <MapProvider id={id}>\n <Deckgl\n {...rest}\n controller\n interleaved\n useDevicePixels={false}\n onHover={handleMapHover}\n onClick={handleMapClick}\n // @ts-expect-error - DeckglProps parameters type is overly strict for WebGL parameter spreading.\n // The merged object is valid at runtime but TypeScript cannot verify all possible parameter combinations.\n parameters={{ ...PARAMETERS, ...parameters }}\n >\n {children}\n </Deckgl>\n </MapProvider>\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/deckgl/base-map/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport 'client-only';\nimport { useEffectEvent, useEmit } from '@accelint/bus/react';\nimport { Deckgl, useDeckgl } from '@deckgl-fiber-renderer/dom';\nimport { useCallback, useId, useMemo } from 'react';\nimport { INITIAL_VIEW_STATE } from '../../maplibre/constants';\nimport { useMapLibre } from '../../maplibre/hooks/use-maplibre';\nimport { BASE_MAP_STYLE, PARAMETERS } from './constants';\nimport { MapEvents } from './events';\nimport { MapProvider } from './provider';\nimport type { UniqueId } from '@accelint/core';\nimport type { PickingInfo, ViewStateChangeParameters } from '@deck.gl/core';\nimport type { DeckglProps } from '@deckgl-fiber-renderer/types';\nimport type { IControl } from 'maplibre-gl';\nimport type { MjolnirGestureEvent, MjolnirPointerEvent } from 'mjolnir.js';\nimport type { MapClickEvent, MapHoverEvent, MapViewportEvent } from './types';\n\n/**\n * Props for the BaseMap component.\n * Extends all Deck.gl props and adds additional map-specific properties.\n */\nexport type BaseMapProps = DeckglProps & {\n /** Optional CSS class name to apply to the map container element */\n className?: string;\n /**\n * Unique identifier for this map instance (required).\n *\n * Used to isolate map mode state between multiple map instances (e.g., main map vs minimap).\n * This should be a UUID generated using `uuid()` from `@accelint/core`.\n *\n * The same id should be passed to `useMapMode()` when accessing map mode state\n * from components rendered outside of the BaseMap's children (i.e., as siblings).\n */\n id: UniqueId;\n};\n\n/**\n * A React component that provides a Deck.gl-powered base map with MapLibre GL integration.\n *\n * This component serves as the foundation for building interactive map applications with\n * support for click and hover events through a centralized event bus. It integrates\n * Deck.gl for 3D visualizations with MapLibre GL for the base map tiles.\n *\n * **Map Mode Integration**: BaseMap automatically creates a `MapProvider` internally,\n * which sets up the map mode state management for this instance.\n * - **Children**: Only Deck.gl layer components can be rendered as children. Custom Deck.gl\n * layers can use `useMapMode()` without parameters to access context.\n * - **Siblings**: UI components (buttons, toolbars, etc.) must be rendered as siblings\n * and pass `id` to `useMapMode(id)`.\n *\n * **Event Bus**: Click and hover events are emitted through the event bus with the `id`\n * included in the payload, allowing multiple map instances to coexist without interference.\n *\n * @param props - Component props including id (required), className, onClick, onHover, and all Deck.gl props\n * @returns A map component with Deck.gl and MapLibre GL integration\n *\n * @example\n * Basic usage with id (recommended: module-level constant):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { View } from '@deckgl-fiber-renderer/dom';\n * import { uuid } from '@accelint/core';\n *\n * // Create id at module level for stability and easy sharing\n * const MAIN_MAP_ID = uuid();\n *\n * export function MapView() {\n * return (\n * <BaseMap className=\"w-full h-full\" id={MAIN_MAP_ID}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example\n * With map mode and event handlers (module-level constant for sharing):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { useMapMode } from '@accelint/map-toolkit/map-mode';\n * import { uuid } from '@accelint/core';\n * import type { PickingInfo } from '@deck.gl/core';\n * import type { MjolnirGestureEvent } from 'mjolnir.js';\n *\n * // Module-level constant - stable and shareable across all components\n * const MAIN_MAP_ID = uuid();\n *\n * function Toolbar() {\n * // Access map mode using the shared id\n * const { mode, requestModeChange } = useMapMode(MAIN_MAP_ID);\n * return <div>Current mode: {mode}</div>;\n * }\n *\n * export function InteractiveMap() {\n * const handleClick = (info: PickingInfo, event: MjolnirGestureEvent) => {\n * console.log('Clicked:', info.object);\n * };\n *\n * return (\n * <div className=\"relative w-full h-full\">\n * <BaseMap className=\"absolute inset-0\" id={MAIN_MAP_ID} onClick={handleClick}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * <Toolbar />\n * </div>\n * );\n * }\n * ```\n */\nexport function BaseMap({\n id,\n className,\n children,\n controller = true,\n interleaved = true,\n parameters = {},\n useDevicePixels = false,\n widgets: widgetsProp = [],\n onClick,\n onHover,\n onViewStateChange,\n ...rest\n}: BaseMapProps) {\n const deckglInstance = useDeckgl();\n const container = useId();\n\n // Memoize MapLibre options to avoid creating new object on every render\n const mapOptions = useMemo(\n () => ({\n container,\n center: [INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude] as [\n number,\n number,\n ],\n zoom: INITIAL_VIEW_STATE.zoom,\n doubleClickZoom: false,\n dragRotate: false,\n pitchWithRotate: false,\n rollEnabled: false,\n attributionControl: { compact: true },\n }),\n [container],\n );\n\n // Use the custom hook to handle MapLibre\n useMapLibre(deckglInstance as IControl, BASE_MAP_STYLE, mapOptions);\n\n const emitClick = useEmit<MapClickEvent>(MapEvents.click);\n const emitHover = useEmit<MapHoverEvent>(MapEvents.hover);\n const emitViewport = useEmit<MapViewportEvent>(MapEvents.viewport);\n\n const handleClick = useCallback(\n (info: PickingInfo, event: MjolnirGestureEvent) => {\n // send full pickingInfo and event to user-defined onClick\n onClick?.(info, event);\n\n // omit viewport, layer, and sourceLayer (contain functions) for event bus serialization\n // extract layerId and sourceLayerId before omission to preserve layer identification\n const { viewport, layer, sourceLayer, ...infoRest } = info;\n const infoObject = {\n layerId: layer?.id,\n sourceLayerId: sourceLayer?.id,\n ...infoRest,\n };\n\n const {\n stopImmediatePropagation,\n stopPropagation,\n preventDefault,\n srcEvent,\n rootElement,\n target,\n changedPointers,\n pointers,\n ...eventRest\n } = event;\n\n emitClick({\n info: infoObject,\n event: eventRest,\n id,\n });\n },\n [emitClick, id, onClick],\n );\n\n const handleHover = useCallback(\n (info: PickingInfo, event: MjolnirPointerEvent) => {\n // send full pickingInfo and event to user-defined onHover\n onHover?.(info, event);\n\n // omit viewport, layer, and sourceLayer (contain functions) for event bus serialization\n // extract layerId and sourceLayerId before omission to preserve layer identification\n const { viewport, layer, sourceLayer, ...infoRest } = info;\n const infoObject = {\n layerId: layer?.id,\n sourceLayerId: sourceLayer?.id,\n ...infoRest,\n };\n\n const {\n stopImmediatePropagation,\n stopPropagation,\n preventDefault,\n srcEvent,\n rootElement,\n target,\n ...eventRest\n } = event;\n\n emitHover({\n info: infoObject,\n event: eventRest,\n id,\n });\n },\n [emitHover, id, onHover],\n );\n\n const handleViewStateChange = useEffectEvent(\n (params: ViewStateChangeParameters) => {\n onViewStateChange?.(params);\n\n const {\n viewId,\n viewState: { latitude, longitude, zoom },\n } = params;\n\n // @ts-expect-error squirrelly deckglInstance typing\n const viewport = deckglInstance._deck\n .getViewports()\n // @ts-expect-error squirrelly deckglInstance typing\n ?.find((vp) => vp.id === viewId);\n\n emitViewport({\n id,\n bounds: viewport?.getBounds(),\n latitude,\n longitude,\n zoom,\n width: viewport?.width ?? 0,\n height: viewport?.height ?? 0,\n });\n },\n );\n\n const handleLoad = useEffectEvent(() => {\n //--- force update viewport state once all viewports initialized ---\n // @ts-expect-error squirrelly deckglInstance typing\n deckglInstance._deck.getViewports().forEach((vp) => {\n handleViewStateChange({\n viewId: vp.id,\n viewState: {\n latitude: vp.latitude,\n longitude: vp.longitude,\n zoom: vp.zoom,\n id: vp.id,\n bounds: vp.getBounds(),\n width: vp.width,\n height: vp.height,\n },\n } as ViewStateChangeParameters);\n });\n });\n\n return (\n <div id={container} className={className}>\n <MapProvider id={id}>\n <Deckgl\n {...rest}\n controller={controller}\n interleaved={interleaved}\n useDevicePixels={useDevicePixels}\n onClick={handleClick}\n onHover={handleHover}\n onLoad={handleLoad}\n onViewStateChange={handleViewStateChange}\n // @ts-expect-error - DeckglProps parameters type is overly strict for WebGL parameter spreading.\n // The merged object is valid at runtime but TypeScript cannot verify all possible parameter combinations.\n parameters={{ ...PARAMETERS, ...parameters }}\n >\n {children}\n </Deckgl>\n </MapProvider>\n </div>\n );\n}\n"]}