@accelint/map-toolkit 0.4.1 → 0.6.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/CHANGELOG.md +30 -0
- package/README.md +3 -0
- package/catalog-info.yaml +6 -3
- package/dist/camera/events.d.ts +15 -0
- package/dist/camera/events.js +29 -0
- package/dist/camera/events.js.map +1 -0
- package/dist/camera/index.d.ts +16 -0
- package/dist/camera/index.js +17 -0
- package/dist/camera/types.d.ts +84 -0
- package/dist/{decorators/deckgl.d.ts → camera/types.js} +0 -7
- package/dist/camera/use-camera-state.d.ts +153 -0
- package/dist/camera/use-camera-state.js +418 -0
- package/dist/camera/use-camera-state.js.map +1 -0
- package/dist/cursor-coordinates/use-cursor-coordinates.js +1 -1
- package/dist/deckgl/base-map/constants.d.ts +14 -2
- package/dist/deckgl/base-map/constants.js +14 -2
- package/dist/deckgl/base-map/constants.js.map +1 -1
- package/dist/deckgl/base-map/controls.d.ts +34 -0
- package/dist/deckgl/base-map/controls.js +50 -0
- package/dist/deckgl/base-map/controls.js.map +1 -0
- package/dist/deckgl/base-map/events.d.ts +4 -0
- package/dist/deckgl/base-map/events.js +5 -1
- package/dist/deckgl/base-map/events.js.map +1 -1
- package/dist/deckgl/base-map/index.d.ts +10 -23
- package/dist/deckgl/base-map/index.js +81 -42
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/base-map/types.d.ts +48 -2
- package/dist/deckgl/index.d.ts +11 -4
- package/dist/deckgl/index.js +7 -2
- package/dist/deckgl/saved-viewports/index.d.ts +32 -0
- package/dist/deckgl/saved-viewports/index.js +51 -0
- package/dist/deckgl/saved-viewports/index.js.map +1 -0
- package/dist/deckgl/saved-viewports/storage.d.ts +21 -0
- package/dist/deckgl/saved-viewports/storage.js +39 -0
- package/dist/deckgl/saved-viewports/storage.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/constants.d.ts +44 -0
- package/dist/deckgl/shapes/display-shape-layer/constants.js +61 -0
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +25 -0
- package/dist/deckgl/shapes/display-shape-layer/fiber.js +21 -0
- package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/index.d.ts +206 -0
- package/dist/deckgl/shapes/display-shape-layer/index.js +416 -0
- package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.d.ts +66 -0
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +116 -0
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/store.d.ts +87 -0
- package/dist/deckgl/shapes/display-shape-layer/store.js +316 -0
- package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/types.d.ts +115 -0
- package/dist/deckgl/shapes/display-shape-layer/types.js +12 -0
- package/dist/deckgl/shapes/display-shape-layer/use-shape-selection.d.ts +89 -0
- package/dist/deckgl/shapes/display-shape-layer/use-shape-selection.js +88 -0
- package/dist/deckgl/shapes/display-shape-layer/use-shape-selection.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.d.ts +61 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +111 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +196 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +368 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -0
- package/dist/deckgl/shapes/index.d.ts +20 -0
- package/dist/deckgl/shapes/index.js +20 -0
- package/dist/deckgl/shapes/shared/constants.d.ts +78 -0
- package/dist/deckgl/shapes/shared/constants.js +109 -0
- package/dist/deckgl/shapes/shared/constants.js.map +1 -0
- package/dist/deckgl/shapes/shared/events.d.ts +73 -0
- package/dist/deckgl/shapes/shared/events.js +58 -0
- package/dist/deckgl/shapes/shared/events.js.map +1 -0
- package/dist/deckgl/shapes/shared/types.d.ts +158 -0
- package/dist/{decorators/deckgl.js → deckgl/shapes/shared/types.js} +12 -15
- package/dist/deckgl/shapes/shared/types.js.map +1 -0
- package/dist/deckgl/symbol-layer/index.d.ts +1 -1
- package/dist/maplibre/hooks/use-maplibre.d.ts +2 -2
- package/dist/maplibre/hooks/use-maplibre.js +2 -2
- package/dist/maplibre/hooks/use-maplibre.js.map +1 -1
- package/dist/viewport/viewport-size.d.ts +2 -2
- package/package.json +50 -23
- package/dist/decorators/deckgl.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ShapeId } from "../shared/types.js";
|
|
14
|
+
import { UniqueId } from "@accelint/core";
|
|
15
|
+
|
|
16
|
+
//#region src/deckgl/shapes/display-shape-layer/store.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* Creates or retrieves a cached subscription function for a given mapId.
|
|
19
|
+
* Uses a fan-out pattern: 1 bus listener -> N React subscribers.
|
|
20
|
+
* Automatically cleans up selection state when the last subscriber unsubscribes.
|
|
21
|
+
*
|
|
22
|
+
* @param mapId - The unique identifier for the map instance
|
|
23
|
+
* @returns A subscription function for useSyncExternalStore
|
|
24
|
+
*/
|
|
25
|
+
declare function getOrCreateSubscription(mapId: UniqueId): (onStoreChange: () => void) => () => void;
|
|
26
|
+
/**
|
|
27
|
+
* Creates or retrieves a cached snapshot function for a given mapId.
|
|
28
|
+
* The selection ID returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.
|
|
29
|
+
*
|
|
30
|
+
* @param mapId - The unique identifier for the map instance
|
|
31
|
+
* @returns A snapshot function for useSyncExternalStore
|
|
32
|
+
*/
|
|
33
|
+
declare function getOrCreateSnapshot(mapId: UniqueId): () => ShapeId | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Creates or retrieves a cached server snapshot function for a given mapId.
|
|
36
|
+
* Server snapshots always return undefined since selection state is client-only.
|
|
37
|
+
* Required for SSR/RSC compatibility with useSyncExternalStore.
|
|
38
|
+
*
|
|
39
|
+
* @param mapId - The unique identifier for the map instance
|
|
40
|
+
* @returns A server snapshot function for useSyncExternalStore
|
|
41
|
+
*/
|
|
42
|
+
declare function getOrCreateServerSnapshot(mapId: UniqueId): () => ShapeId | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Creates or retrieves a cached setSelectedId function for a given mapId.
|
|
45
|
+
* This maintains referential stability for the function reference.
|
|
46
|
+
*
|
|
47
|
+
* @param mapId - The unique identifier for the map instance
|
|
48
|
+
* @returns A setSelectedId function for this instance
|
|
49
|
+
*/
|
|
50
|
+
declare function getOrCreateSetSelectedId(mapId: UniqueId): (id: ShapeId | undefined) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Creates or retrieves a cached clearSelection function for a given mapId.
|
|
53
|
+
* This maintains referential stability for the function reference.
|
|
54
|
+
*
|
|
55
|
+
* Note: This always emits a deselected event, even if nothing is currently selected.
|
|
56
|
+
* This matches the original behavior and allows consumers to use clearSelection
|
|
57
|
+
* as a "force deselect" without needing to check selection state first.
|
|
58
|
+
*
|
|
59
|
+
* @param mapId - The unique identifier for the map instance
|
|
60
|
+
* @returns A clearSelection function for this instance
|
|
61
|
+
*/
|
|
62
|
+
declare function getOrCreateClearSelection(mapId: UniqueId): () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Get the current selected shape ID for a given map instance (direct access, not reactive).
|
|
65
|
+
* @internal - For internal map-toolkit use only
|
|
66
|
+
*
|
|
67
|
+
* @param mapId - The unique identifier for the map instance
|
|
68
|
+
* @returns The currently selected shape ID, or undefined
|
|
69
|
+
*/
|
|
70
|
+
declare function getSelectedShapeId(mapId: UniqueId): ShapeId | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Manually clear all selection state for a specific mapId.
|
|
73
|
+
* This is typically not needed as cleanup happens automatically when all subscribers unmount.
|
|
74
|
+
* Use this only in advanced scenarios where manual cleanup is required.
|
|
75
|
+
*
|
|
76
|
+
* @param mapId - The unique identifier for the map instance to clear
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* // Manual cleanup (rarely needed)
|
|
81
|
+
* clearSelectionState('my-map-instance');
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
declare function clearSelectionState(mapId: UniqueId): void;
|
|
85
|
+
//#endregion
|
|
86
|
+
export { clearSelectionState, getOrCreateClearSelection, getOrCreateServerSnapshot, getOrCreateSetSelectedId, getOrCreateSnapshot, getOrCreateSubscription, getSelectedShapeId };
|
|
87
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import { MapEvents } from "../../base-map/events.js";
|
|
15
|
+
import { ShapeEvents } from "../shared/events.js";
|
|
16
|
+
import { Broadcast } from "@accelint/bus";
|
|
17
|
+
|
|
18
|
+
//#region src/deckgl/shapes/display-shape-layer/store.ts
|
|
19
|
+
/**
|
|
20
|
+
* Shape Selection Store
|
|
21
|
+
*
|
|
22
|
+
* This module provides a store pattern for managing shape selection state,
|
|
23
|
+
* designed to work with React's `useSyncExternalStore` hook.
|
|
24
|
+
*
|
|
25
|
+
* ## Architecture
|
|
26
|
+
*
|
|
27
|
+
* The store uses a fan-out pattern similar to `map-cursor/store.ts` and `map-mode/store.ts`:
|
|
28
|
+
* - **1 bus listener per mapId** - Regardless of how many React components subscribe
|
|
29
|
+
* - **N React subscribers** - Each component gets notified via `useSyncExternalStore`
|
|
30
|
+
* - **Automatic cleanup** - Bus listeners are removed when the last subscriber unmounts
|
|
31
|
+
*
|
|
32
|
+
* ## Why This Pattern?
|
|
33
|
+
*
|
|
34
|
+
* Without this pattern, using `useOn` directly in each React component would
|
|
35
|
+
* create N bus listeners for N components subscribing to the same events.
|
|
36
|
+
* The fan-out pattern ensures exactly 1 bus listener per mapId, regardless
|
|
37
|
+
* of how many React components need the state. This also provides proper
|
|
38
|
+
* cleanup semantics that prevent listener accumulation during HMR.
|
|
39
|
+
*
|
|
40
|
+
* ## Usage
|
|
41
|
+
*
|
|
42
|
+
* This module is internal to `useShapeSelection`. Use the hook instead:
|
|
43
|
+
*
|
|
44
|
+
* ```tsx
|
|
45
|
+
* import { useShapeSelection } from '@accelint/map-toolkit/deckgl/shapes';
|
|
46
|
+
*
|
|
47
|
+
* const { selectedId, setSelectedId, clearSelection } = useShapeSelection(mapId);
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @module
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Typed event bus instances for shape and map events
|
|
54
|
+
*/
|
|
55
|
+
const shapeBus = Broadcast.getInstance();
|
|
56
|
+
const mapBus = Broadcast.getInstance();
|
|
57
|
+
/**
|
|
58
|
+
* Store for shape selection state keyed by mapId
|
|
59
|
+
*/
|
|
60
|
+
const selectionStore = /* @__PURE__ */ new Map();
|
|
61
|
+
/**
|
|
62
|
+
* Track React component subscribers per mapId (for fan-out notifications).
|
|
63
|
+
* Each Set contains onStoreChange callbacks from useSyncExternalStore.
|
|
64
|
+
*/
|
|
65
|
+
const componentSubscribers = /* @__PURE__ */ new Map();
|
|
66
|
+
/**
|
|
67
|
+
* Cache of bus unsubscribe functions (1 per mapId).
|
|
68
|
+
* This ensures we only have one bus listener per map instance, regardless of
|
|
69
|
+
* how many React components subscribe to it.
|
|
70
|
+
*/
|
|
71
|
+
const busUnsubscribers = /* @__PURE__ */ new Map();
|
|
72
|
+
/**
|
|
73
|
+
* Cache of subscription functions per mapId to avoid recreating on every render
|
|
74
|
+
*/
|
|
75
|
+
const subscriptionCache = /* @__PURE__ */ new Map();
|
|
76
|
+
/**
|
|
77
|
+
* Cache of snapshot functions per mapId to maintain referential stability
|
|
78
|
+
*/
|
|
79
|
+
const snapshotCache = /* @__PURE__ */ new Map();
|
|
80
|
+
/**
|
|
81
|
+
* Cache of server snapshot functions per mapId to maintain referential stability.
|
|
82
|
+
* Server snapshots always return undefined since selection state is client-only.
|
|
83
|
+
*/
|
|
84
|
+
const serverSnapshotCache = /* @__PURE__ */ new Map();
|
|
85
|
+
/**
|
|
86
|
+
* Cache of setSelectedId functions per mapId to maintain referential stability
|
|
87
|
+
*/
|
|
88
|
+
const setSelectedIdCache = /* @__PURE__ */ new Map();
|
|
89
|
+
/**
|
|
90
|
+
* Cache of clearSelection functions per mapId to maintain referential stability
|
|
91
|
+
*/
|
|
92
|
+
const clearSelectionCache = /* @__PURE__ */ new Map();
|
|
93
|
+
/**
|
|
94
|
+
* All state caches that need cleanup when an instance is removed
|
|
95
|
+
*/
|
|
96
|
+
const stateCaches = [
|
|
97
|
+
selectionStore,
|
|
98
|
+
componentSubscribers,
|
|
99
|
+
subscriptionCache,
|
|
100
|
+
snapshotCache,
|
|
101
|
+
serverSnapshotCache,
|
|
102
|
+
setSelectedIdCache,
|
|
103
|
+
clearSelectionCache
|
|
104
|
+
];
|
|
105
|
+
/**
|
|
106
|
+
* Clear all cached state for a given mapId
|
|
107
|
+
*/
|
|
108
|
+
function clearAllCaches(mapId) {
|
|
109
|
+
for (const cache of stateCaches) cache.delete(mapId);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get or create selection state for a given mapId
|
|
113
|
+
*/
|
|
114
|
+
function getOrCreateState(mapId) {
|
|
115
|
+
if (!selectionStore.has(mapId)) selectionStore.set(mapId, { selectedId: void 0 });
|
|
116
|
+
return selectionStore.get(mapId);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get current selection snapshot for a given map instance (for useSyncExternalStore)
|
|
120
|
+
*/
|
|
121
|
+
function getSnapshot(mapId) {
|
|
122
|
+
return selectionStore.get(mapId)?.selectedId;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Notify all React subscribers for a given mapId
|
|
126
|
+
*/
|
|
127
|
+
function notifySubscribers(mapId) {
|
|
128
|
+
const subscribers = componentSubscribers.get(mapId);
|
|
129
|
+
if (subscribers) for (const onStoreChange of subscribers) onStoreChange();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Ensures a single bus listener exists for the given mapId.
|
|
133
|
+
* All React subscribers will be notified via fan-out when the bus events fire.
|
|
134
|
+
* This prevents creating N bus listeners for N React components.
|
|
135
|
+
*
|
|
136
|
+
* @param mapId - The unique identifier for the map instance
|
|
137
|
+
*/
|
|
138
|
+
function ensureBusListener(mapId) {
|
|
139
|
+
if (busUnsubscribers.has(mapId)) return;
|
|
140
|
+
const state = getOrCreateState(mapId);
|
|
141
|
+
const unsubSelected = shapeBus.on(ShapeEvents.selected, (event) => {
|
|
142
|
+
if (event.payload.mapId !== mapId) return;
|
|
143
|
+
const previousId = state.selectedId;
|
|
144
|
+
state.selectedId = event.payload.shapeId;
|
|
145
|
+
if (previousId !== state.selectedId) notifySubscribers(mapId);
|
|
146
|
+
});
|
|
147
|
+
const unsubDeselected = shapeBus.on(ShapeEvents.deselected, (event) => {
|
|
148
|
+
if (event.payload.mapId !== mapId) return;
|
|
149
|
+
const previousId = state.selectedId;
|
|
150
|
+
state.selectedId = void 0;
|
|
151
|
+
if (previousId !== state.selectedId) notifySubscribers(mapId);
|
|
152
|
+
});
|
|
153
|
+
const unsubClick = mapBus.on(MapEvents.click, (event) => {
|
|
154
|
+
if (state.selectedId && event.payload.id === mapId && event.payload.info.index === -1) shapeBus.emit(ShapeEvents.deselected, { mapId });
|
|
155
|
+
});
|
|
156
|
+
busUnsubscribers.set(mapId, () => {
|
|
157
|
+
unsubSelected();
|
|
158
|
+
unsubDeselected();
|
|
159
|
+
unsubClick();
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Cleans up the bus listener if no React subscribers remain.
|
|
164
|
+
*
|
|
165
|
+
* @param mapId - The unique identifier for the map instance
|
|
166
|
+
*/
|
|
167
|
+
function cleanupBusListenerIfNeeded(mapId) {
|
|
168
|
+
const subscribers = componentSubscribers.get(mapId);
|
|
169
|
+
if (!subscribers || subscribers.size === 0) {
|
|
170
|
+
const unsub = busUnsubscribers.get(mapId);
|
|
171
|
+
if (unsub) {
|
|
172
|
+
unsub();
|
|
173
|
+
busUnsubscribers.delete(mapId);
|
|
174
|
+
}
|
|
175
|
+
clearAllCaches(mapId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Creates or retrieves a cached subscription function for a given mapId.
|
|
180
|
+
* Uses a fan-out pattern: 1 bus listener -> N React subscribers.
|
|
181
|
+
* Automatically cleans up selection state when the last subscriber unsubscribes.
|
|
182
|
+
*
|
|
183
|
+
* @param mapId - The unique identifier for the map instance
|
|
184
|
+
* @returns A subscription function for useSyncExternalStore
|
|
185
|
+
*/
|
|
186
|
+
function getOrCreateSubscription(mapId) {
|
|
187
|
+
let subscription = subscriptionCache.get(mapId);
|
|
188
|
+
if (!subscription) {
|
|
189
|
+
subscription = (onStoreChange) => {
|
|
190
|
+
getOrCreateState(mapId);
|
|
191
|
+
ensureBusListener(mapId);
|
|
192
|
+
let subscriberSet = componentSubscribers.get(mapId);
|
|
193
|
+
if (!subscriberSet) {
|
|
194
|
+
subscriberSet = /* @__PURE__ */ new Set();
|
|
195
|
+
componentSubscribers.set(mapId, subscriberSet);
|
|
196
|
+
}
|
|
197
|
+
subscriberSet.add(onStoreChange);
|
|
198
|
+
return () => {
|
|
199
|
+
const currentSubscriberSet = componentSubscribers.get(mapId);
|
|
200
|
+
if (currentSubscriberSet) currentSubscriberSet.delete(onStoreChange);
|
|
201
|
+
cleanupBusListenerIfNeeded(mapId);
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
subscriptionCache.set(mapId, subscription);
|
|
205
|
+
}
|
|
206
|
+
return subscription;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Creates or retrieves a cached snapshot function for a given mapId.
|
|
210
|
+
* The selection ID returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.
|
|
211
|
+
*
|
|
212
|
+
* @param mapId - The unique identifier for the map instance
|
|
213
|
+
* @returns A snapshot function for useSyncExternalStore
|
|
214
|
+
*/
|
|
215
|
+
function getOrCreateSnapshot(mapId) {
|
|
216
|
+
let snapshot = snapshotCache.get(mapId);
|
|
217
|
+
if (!snapshot) {
|
|
218
|
+
snapshot = () => getSnapshot(mapId);
|
|
219
|
+
snapshotCache.set(mapId, snapshot);
|
|
220
|
+
}
|
|
221
|
+
return snapshot;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Creates or retrieves a cached server snapshot function for a given mapId.
|
|
225
|
+
* Server snapshots always return undefined since selection state is client-only.
|
|
226
|
+
* Required for SSR/RSC compatibility with useSyncExternalStore.
|
|
227
|
+
*
|
|
228
|
+
* @param mapId - The unique identifier for the map instance
|
|
229
|
+
* @returns A server snapshot function for useSyncExternalStore
|
|
230
|
+
*/
|
|
231
|
+
function getOrCreateServerSnapshot(mapId) {
|
|
232
|
+
let serverSnapshot = serverSnapshotCache.get(mapId);
|
|
233
|
+
if (!serverSnapshot) {
|
|
234
|
+
serverSnapshot = () => void 0;
|
|
235
|
+
serverSnapshotCache.set(mapId, serverSnapshot);
|
|
236
|
+
}
|
|
237
|
+
return serverSnapshot;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Creates or retrieves a cached setSelectedId function for a given mapId.
|
|
241
|
+
* This maintains referential stability for the function reference.
|
|
242
|
+
*
|
|
243
|
+
* @param mapId - The unique identifier for the map instance
|
|
244
|
+
* @returns A setSelectedId function for this instance
|
|
245
|
+
*/
|
|
246
|
+
function getOrCreateSetSelectedId(mapId) {
|
|
247
|
+
let setSelectedId = setSelectedIdCache.get(mapId);
|
|
248
|
+
if (!setSelectedId) {
|
|
249
|
+
setSelectedId = (id) => {
|
|
250
|
+
const previousId = getOrCreateState(mapId).selectedId;
|
|
251
|
+
if (id === void 0 && previousId !== void 0) shapeBus.emit(ShapeEvents.deselected, { mapId });
|
|
252
|
+
else if (id !== void 0 && previousId !== id) shapeBus.emit(ShapeEvents.selected, {
|
|
253
|
+
shapeId: id,
|
|
254
|
+
mapId
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
setSelectedIdCache.set(mapId, setSelectedId);
|
|
258
|
+
}
|
|
259
|
+
return setSelectedId;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Creates or retrieves a cached clearSelection function for a given mapId.
|
|
263
|
+
* This maintains referential stability for the function reference.
|
|
264
|
+
*
|
|
265
|
+
* Note: This always emits a deselected event, even if nothing is currently selected.
|
|
266
|
+
* This matches the original behavior and allows consumers to use clearSelection
|
|
267
|
+
* as a "force deselect" without needing to check selection state first.
|
|
268
|
+
*
|
|
269
|
+
* @param mapId - The unique identifier for the map instance
|
|
270
|
+
* @returns A clearSelection function for this instance
|
|
271
|
+
*/
|
|
272
|
+
function getOrCreateClearSelection(mapId) {
|
|
273
|
+
let clearSelection = clearSelectionCache.get(mapId);
|
|
274
|
+
if (!clearSelection) {
|
|
275
|
+
clearSelection = () => {
|
|
276
|
+
shapeBus.emit(ShapeEvents.deselected, { mapId });
|
|
277
|
+
};
|
|
278
|
+
clearSelectionCache.set(mapId, clearSelection);
|
|
279
|
+
}
|
|
280
|
+
return clearSelection;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get the current selected shape ID for a given map instance (direct access, not reactive).
|
|
284
|
+
* @internal - For internal map-toolkit use only
|
|
285
|
+
*
|
|
286
|
+
* @param mapId - The unique identifier for the map instance
|
|
287
|
+
* @returns The currently selected shape ID, or undefined
|
|
288
|
+
*/
|
|
289
|
+
function getSelectedShapeId(mapId) {
|
|
290
|
+
return getSnapshot(mapId);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Manually clear all selection state for a specific mapId.
|
|
294
|
+
* This is typically not needed as cleanup happens automatically when all subscribers unmount.
|
|
295
|
+
* Use this only in advanced scenarios where manual cleanup is required.
|
|
296
|
+
*
|
|
297
|
+
* @param mapId - The unique identifier for the map instance to clear
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```tsx
|
|
301
|
+
* // Manual cleanup (rarely needed)
|
|
302
|
+
* clearSelectionState('my-map-instance');
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
function clearSelectionState(mapId) {
|
|
306
|
+
const unsub = busUnsubscribers.get(mapId);
|
|
307
|
+
if (unsub) {
|
|
308
|
+
unsub();
|
|
309
|
+
busUnsubscribers.delete(mapId);
|
|
310
|
+
}
|
|
311
|
+
clearAllCaches(mapId);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
//#endregion
|
|
315
|
+
export { clearSelectionState, getOrCreateClearSelection, getOrCreateServerSnapshot, getOrCreateSetSelectedId, getOrCreateSnapshot, getOrCreateSubscription, getSelectedShapeId };
|
|
316
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/store.ts"],"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/**\n * Shape Selection Store\n *\n * This module provides a store pattern for managing shape selection state,\n * designed to work with React's `useSyncExternalStore` hook.\n *\n * ## Architecture\n *\n * The store uses a fan-out pattern similar to `map-cursor/store.ts` and `map-mode/store.ts`:\n * - **1 bus listener per mapId** - Regardless of how many React components subscribe\n * - **N React subscribers** - Each component gets notified via `useSyncExternalStore`\n * - **Automatic cleanup** - Bus listeners are removed when the last subscriber unmounts\n *\n * ## Why This Pattern?\n *\n * Without this pattern, using `useOn` directly in each React component would\n * create N bus listeners for N components subscribing to the same events.\n * The fan-out pattern ensures exactly 1 bus listener per mapId, regardless\n * of how many React components need the state. This also provides proper\n * cleanup semantics that prevent listener accumulation during HMR.\n *\n * ## Usage\n *\n * This module is internal to `useShapeSelection`. Use the hook instead:\n *\n * ```tsx\n * import { useShapeSelection } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * const { selectedId, setSelectedId, clearSelection } = useShapeSelection(mapId);\n * ```\n *\n * @module\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { MapEvents } from '../../base-map/events';\nimport { type ShapeEvent, ShapeEvents } from '../shared/events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapClickEvent, MapEventType } from '../../base-map/types';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * Typed event bus instances for shape and map events\n */\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\nconst mapBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Type representing the state for a single shape selection instance\n */\ntype ShapeSelectionState = {\n /** Currently selected shape ID, or undefined if nothing selected */\n selectedId: ShapeId | undefined;\n};\n\n/**\n * Store for shape selection state keyed by mapId\n */\nconst selectionStore = new Map<UniqueId, ShapeSelectionState>();\n\n/**\n * Track React component subscribers per mapId (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 mapId).\n * This ensures we only have one bus listener per map instance, 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 mapId to avoid recreating on every render\n */\nconst subscriptionCache = new Map<UniqueId, Subscription>();\n\n/**\n * Cache of snapshot functions per mapId to maintain referential stability\n */\nconst snapshotCache = new Map<UniqueId, () => ShapeId | undefined>();\n\n/**\n * Cache of server snapshot functions per mapId to maintain referential stability.\n * Server snapshots always return undefined since selection state is client-only.\n */\nconst serverSnapshotCache = new Map<UniqueId, () => ShapeId | undefined>();\n\n/**\n * Cache of setSelectedId functions per mapId to maintain referential stability\n */\nconst setSelectedIdCache = new Map<\n UniqueId,\n (id: ShapeId | undefined) => void\n>();\n\n/**\n * Cache of clearSelection functions per mapId to maintain referential stability\n */\nconst clearSelectionCache = new Map<UniqueId, () => void>();\n\n/**\n * All state caches that need cleanup when an instance is removed\n */\nconst stateCaches = [\n selectionStore,\n componentSubscribers,\n subscriptionCache,\n snapshotCache,\n serverSnapshotCache,\n setSelectedIdCache,\n clearSelectionCache,\n] as const;\n\n/**\n * Clear all cached state for a given mapId\n */\nfunction clearAllCaches(mapId: UniqueId): void {\n for (const cache of stateCaches) {\n cache.delete(mapId);\n }\n}\n\n/**\n * Get or create selection state for a given mapId\n */\nfunction getOrCreateState(mapId: UniqueId): ShapeSelectionState {\n if (!selectionStore.has(mapId)) {\n selectionStore.set(mapId, {\n selectedId: undefined,\n });\n }\n // biome-ignore lint/style/noNonNullAssertion: State guaranteed to exist after has() check above\n return selectionStore.get(mapId)!;\n}\n\n/**\n * Get current selection snapshot for a given map instance (for useSyncExternalStore)\n */\nfunction getSnapshot(mapId: UniqueId): ShapeId | undefined {\n const state = selectionStore.get(mapId);\n return state?.selectedId;\n}\n\n/**\n * Notify all React subscribers for a given mapId\n */\nfunction notifySubscribers(mapId: UniqueId): void {\n const subscribers = componentSubscribers.get(mapId);\n if (subscribers) {\n for (const onStoreChange of subscribers) {\n onStoreChange();\n }\n }\n}\n\n/**\n * Ensures a single bus listener exists for the given mapId.\n * All React subscribers will be notified via fan-out when the bus events fire.\n * This prevents creating N bus listeners for N React components.\n *\n * @param mapId - The unique identifier for the map instance\n */\nfunction ensureBusListener(mapId: UniqueId): void {\n if (busUnsubscribers.has(mapId)) {\n return; // Already listening\n }\n\n const state = getOrCreateState(mapId);\n\n // Listen for shape selection events\n const unsubSelected = shapeBus.on(ShapeEvents.selected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n const previousId = state.selectedId;\n state.selectedId = event.payload.shapeId;\n if (previousId !== state.selectedId) {\n notifySubscribers(mapId);\n }\n });\n\n // Listen for shape deselection events\n const unsubDeselected = shapeBus.on(ShapeEvents.deselected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n const previousId = state.selectedId;\n state.selectedId = undefined;\n if (previousId !== state.selectedId) {\n notifySubscribers(mapId);\n }\n });\n\n // Listen for map clicks to detect clicks on empty space\n const unsubClick = mapBus.on(MapEvents.click, (event: MapClickEvent) => {\n // Only emit deselect if we have a selection and clicked on empty space\n // index is -1 when nothing is picked\n if (\n state.selectedId &&\n event.payload.id === mapId &&\n event.payload.info.index === -1\n ) {\n // Emit deselection event - this will be caught by our deselected listener above\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n }\n });\n\n // Store composite cleanup function\n busUnsubscribers.set(mapId, () => {\n unsubSelected();\n unsubDeselected();\n unsubClick();\n });\n}\n\n/**\n * Cleans up the bus listener if no React subscribers remain.\n *\n * @param mapId - The unique identifier for the map instance\n */\nfunction cleanupBusListenerIfNeeded(mapId: UniqueId): void {\n const subscribers = componentSubscribers.get(mapId);\n\n if (!subscribers || subscribers.size === 0) {\n // No more React subscribers - clean up bus listener\n const unsub = busUnsubscribers.get(mapId);\n if (unsub) {\n unsub();\n busUnsubscribers.delete(mapId);\n }\n\n // Clean up all state\n clearAllCaches(mapId);\n }\n}\n\n/**\n * Creates or retrieves a cached subscription function for a given mapId.\n * Uses a fan-out pattern: 1 bus listener -> N React subscribers.\n * Automatically cleans up selection state when the last subscriber unsubscribes.\n *\n * @param mapId - The unique identifier for the map instance\n * @returns A subscription function for useSyncExternalStore\n */\nexport function getOrCreateSubscription(\n mapId: UniqueId,\n): (onStoreChange: () => void) => () => void {\n let subscription = subscriptionCache.get(mapId);\n if (!subscription) {\n subscription = (onStoreChange: () => void) => {\n // Ensure state exists\n getOrCreateState(mapId);\n\n // Ensure single bus listener exists for this mapId\n ensureBusListener(mapId);\n\n // Get or create the subscriber set for this map instance, then add this component's callback\n let subscriberSet = componentSubscribers.get(mapId);\n if (!subscriberSet) {\n subscriberSet = new Set();\n componentSubscribers.set(mapId, subscriberSet);\n }\n subscriberSet.add(onStoreChange);\n\n // Return cleanup function to remove this component's subscription\n return () => {\n const currentSubscriberSet = componentSubscribers.get(mapId);\n if (currentSubscriberSet) {\n currentSubscriberSet.delete(onStoreChange);\n }\n\n // Clean up bus listener if this was the last React subscriber\n cleanupBusListenerIfNeeded(mapId);\n };\n };\n\n subscriptionCache.set(mapId, subscription);\n }\n\n return subscription;\n}\n\n/**\n * Creates or retrieves a cached snapshot function for a given mapId.\n * The selection ID returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.\n *\n * @param mapId - The unique identifier for the map instance\n * @returns A snapshot function for useSyncExternalStore\n */\nexport function getOrCreateSnapshot(\n mapId: UniqueId,\n): () => ShapeId | undefined {\n let snapshot = snapshotCache.get(mapId);\n if (!snapshot) {\n snapshot = () => getSnapshot(mapId);\n snapshotCache.set(mapId, snapshot);\n }\n\n return snapshot;\n}\n\n/**\n * Creates or retrieves a cached server snapshot function for a given mapId.\n * Server snapshots always return undefined since selection state is client-only.\n * Required for SSR/RSC compatibility with useSyncExternalStore.\n *\n * @param mapId - The unique identifier for the map instance\n * @returns A server snapshot function for useSyncExternalStore\n */\nexport function getOrCreateServerSnapshot(\n mapId: UniqueId,\n): () => ShapeId | undefined {\n let serverSnapshot = serverSnapshotCache.get(mapId);\n if (!serverSnapshot) {\n serverSnapshot = () => undefined;\n serverSnapshotCache.set(mapId, serverSnapshot);\n }\n\n return serverSnapshot;\n}\n\n/**\n * Creates or retrieves a cached setSelectedId function for a given mapId.\n * This maintains referential stability for the function reference.\n *\n * @param mapId - The unique identifier for the map instance\n * @returns A setSelectedId function for this instance\n */\nexport function getOrCreateSetSelectedId(\n mapId: UniqueId,\n): (id: ShapeId | undefined) => void {\n let setSelectedId = setSelectedIdCache.get(mapId);\n if (!setSelectedId) {\n setSelectedId = (id: ShapeId | undefined) => {\n const state = getOrCreateState(mapId);\n const previousId = state.selectedId;\n\n if (id === undefined && previousId !== undefined) {\n // Emit deselection event\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n } else if (id !== undefined && previousId !== id) {\n // Emit selection event\n shapeBus.emit(ShapeEvents.selected, { shapeId: id, mapId });\n }\n };\n\n setSelectedIdCache.set(mapId, setSelectedId);\n }\n\n return setSelectedId;\n}\n\n/**\n * Creates or retrieves a cached clearSelection function for a given mapId.\n * This maintains referential stability for the function reference.\n *\n * Note: This always emits a deselected event, even if nothing is currently selected.\n * This matches the original behavior and allows consumers to use clearSelection\n * as a \"force deselect\" without needing to check selection state first.\n *\n * @param mapId - The unique identifier for the map instance\n * @returns A clearSelection function for this instance\n */\nexport function getOrCreateClearSelection(mapId: UniqueId): () => void {\n let clearSelection = clearSelectionCache.get(mapId);\n if (!clearSelection) {\n clearSelection = () => {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n };\n\n clearSelectionCache.set(mapId, clearSelection);\n }\n\n return clearSelection;\n}\n\n/**\n * Get the current selected shape ID for a given map instance (direct access, not reactive).\n * @internal - For internal map-toolkit use only\n *\n * @param mapId - The unique identifier for the map instance\n * @returns The currently selected shape ID, or undefined\n */\nexport function getSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return getSnapshot(mapId);\n}\n\n/**\n * Manually clear all selection state for a specific mapId.\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 mapId - The unique identifier for the map instance to clear\n *\n * @example\n * ```tsx\n * // Manual cleanup (rarely needed)\n * clearSelectionState('my-map-instance');\n * ```\n */\nexport function clearSelectionState(mapId: UniqueId): void {\n // Unsubscribe from bus if listening\n const unsub = busUnsubscribers.get(mapId);\n if (unsub) {\n unsub();\n busUnsubscribers.delete(mapId);\n }\n\n // Clear all state\n clearAllCaches(mapId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAM,WAAW,UAAU,aAAyB;AACpD,MAAM,SAAS,UAAU,aAA2B;;;;AAapD,MAAM,iCAAiB,IAAI,KAAoC;;;;;AAM/D,MAAM,uCAAuB,IAAI,KAAgC;;;;;;AAOjE,MAAM,mCAAmB,IAAI,KAA2B;;;;AAMxD,MAAM,oCAAoB,IAAI,KAA6B;;;;AAK3D,MAAM,gCAAgB,IAAI,KAA0C;;;;;AAMpE,MAAM,sCAAsB,IAAI,KAA0C;;;;AAK1E,MAAM,qCAAqB,IAAI,KAG5B;;;;AAKH,MAAM,sCAAsB,IAAI,KAA2B;;;;AAK3D,MAAM,cAAc;CAClB;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,SAAS,eAAe,OAAuB;AAC7C,MAAK,MAAM,SAAS,YAClB,OAAM,OAAO,MAAM;;;;;AAOvB,SAAS,iBAAiB,OAAsC;AAC9D,KAAI,CAAC,eAAe,IAAI,MAAM,CAC5B,gBAAe,IAAI,OAAO,EACxB,YAAY,QACb,CAAC;AAGJ,QAAO,eAAe,IAAI,MAAM;;;;;AAMlC,SAAS,YAAY,OAAsC;AAEzD,QADc,eAAe,IAAI,MAAM,EACzB;;;;;AAMhB,SAAS,kBAAkB,OAAuB;CAChD,MAAM,cAAc,qBAAqB,IAAI,MAAM;AACnD,KAAI,YACF,MAAK,MAAM,iBAAiB,YAC1B,gBAAe;;;;;;;;;AAYrB,SAAS,kBAAkB,OAAuB;AAChD,KAAI,iBAAiB,IAAI,MAAM,CAC7B;CAGF,MAAM,QAAQ,iBAAiB,MAAM;CAGrC,MAAM,gBAAgB,SAAS,GAAG,YAAY,WAAW,UAAU;AACjE,MAAI,MAAM,QAAQ,UAAU,MAC1B;EAEF,MAAM,aAAa,MAAM;AACzB,QAAM,aAAa,MAAM,QAAQ;AACjC,MAAI,eAAe,MAAM,WACvB,mBAAkB,MAAM;GAE1B;CAGF,MAAM,kBAAkB,SAAS,GAAG,YAAY,aAAa,UAAU;AACrE,MAAI,MAAM,QAAQ,UAAU,MAC1B;EAEF,MAAM,aAAa,MAAM;AACzB,QAAM,aAAa;AACnB,MAAI,eAAe,MAAM,WACvB,mBAAkB,MAAM;GAE1B;CAGF,MAAM,aAAa,OAAO,GAAG,UAAU,QAAQ,UAAyB;AAGtE,MACE,MAAM,cACN,MAAM,QAAQ,OAAO,SACrB,MAAM,QAAQ,KAAK,UAAU,GAG7B,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;GAElD;AAGF,kBAAiB,IAAI,aAAa;AAChC,iBAAe;AACf,mBAAiB;AACjB,cAAY;GACZ;;;;;;;AAQJ,SAAS,2BAA2B,OAAuB;CACzD,MAAM,cAAc,qBAAqB,IAAI,MAAM;AAEnD,KAAI,CAAC,eAAe,YAAY,SAAS,GAAG;EAE1C,MAAM,QAAQ,iBAAiB,IAAI,MAAM;AACzC,MAAI,OAAO;AACT,UAAO;AACP,oBAAiB,OAAO,MAAM;;AAIhC,iBAAe,MAAM;;;;;;;;;;;AAYzB,SAAgB,wBACd,OAC2C;CAC3C,IAAI,eAAe,kBAAkB,IAAI,MAAM;AAC/C,KAAI,CAAC,cAAc;AACjB,kBAAgB,kBAA8B;AAE5C,oBAAiB,MAAM;AAGvB,qBAAkB,MAAM;GAGxB,IAAI,gBAAgB,qBAAqB,IAAI,MAAM;AACnD,OAAI,CAAC,eAAe;AAClB,oCAAgB,IAAI,KAAK;AACzB,yBAAqB,IAAI,OAAO,cAAc;;AAEhD,iBAAc,IAAI,cAAc;AAGhC,gBAAa;IACX,MAAM,uBAAuB,qBAAqB,IAAI,MAAM;AAC5D,QAAI,qBACF,sBAAqB,OAAO,cAAc;AAI5C,+BAA2B,MAAM;;;AAIrC,oBAAkB,IAAI,OAAO,aAAa;;AAG5C,QAAO;;;;;;;;;AAUT,SAAgB,oBACd,OAC2B;CAC3B,IAAI,WAAW,cAAc,IAAI,MAAM;AACvC,KAAI,CAAC,UAAU;AACb,mBAAiB,YAAY,MAAM;AACnC,gBAAc,IAAI,OAAO,SAAS;;AAGpC,QAAO;;;;;;;;;;AAWT,SAAgB,0BACd,OAC2B;CAC3B,IAAI,iBAAiB,oBAAoB,IAAI,MAAM;AACnD,KAAI,CAAC,gBAAgB;AACnB,yBAAuB;AACvB,sBAAoB,IAAI,OAAO,eAAe;;AAGhD,QAAO;;;;;;;;;AAUT,SAAgB,yBACd,OACmC;CACnC,IAAI,gBAAgB,mBAAmB,IAAI,MAAM;AACjD,KAAI,CAAC,eAAe;AAClB,mBAAiB,OAA4B;GAE3C,MAAM,aADQ,iBAAiB,MAAM,CACZ;AAEzB,OAAI,OAAO,UAAa,eAAe,OAErC,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;YACvC,OAAO,UAAa,eAAe,GAE5C,UAAS,KAAK,YAAY,UAAU;IAAE,SAAS;IAAI;IAAO,CAAC;;AAI/D,qBAAmB,IAAI,OAAO,cAAc;;AAG9C,QAAO;;;;;;;;;;;;;AAcT,SAAgB,0BAA0B,OAA6B;CACrE,IAAI,iBAAiB,oBAAoB,IAAI,MAAM;AACnD,KAAI,CAAC,gBAAgB;AACnB,yBAAuB;AACrB,YAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;;AAGlD,sBAAoB,IAAI,OAAO,eAAe;;AAGhD,QAAO;;;;;;;;;AAUT,SAAgB,mBAAmB,OAAsC;AACvE,QAAO,YAAY,MAAM;;;;;;;;;;;;;;;AAgB3B,SAAgB,oBAAoB,OAAuB;CAEzD,MAAM,QAAQ,iBAAiB,IAAI,MAAM;AACzC,KAAI,OAAO;AACT,SAAO;AACP,mBAAiB,OAAO,MAAM;;AAIhC,gBAAe,MAAM"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { EditableShape, ShapeId, StyledFeature as StyledFeature$1 } from "../shared/types.js";
|
|
14
|
+
import { LabelPositionOptions } from "./utils/labels.js";
|
|
15
|
+
import { UniqueId } from "@accelint/core";
|
|
16
|
+
import { CompositeLayerProps } from "@deck.gl/core";
|
|
17
|
+
|
|
18
|
+
//#region src/deckgl/shapes/display-shape-layer/types.d.ts
|
|
19
|
+
/**
|
|
20
|
+
* Re-export StyledFeature from shared types
|
|
21
|
+
*/
|
|
22
|
+
type StyledFeature = StyledFeature$1;
|
|
23
|
+
/**
|
|
24
|
+
* Re-export StyledFeatureProperties from shared types
|
|
25
|
+
*/
|
|
26
|
+
type StyledFeatureProperties = StyledFeature$1['properties'];
|
|
27
|
+
/**
|
|
28
|
+
* Props for DisplayShapeLayer
|
|
29
|
+
*
|
|
30
|
+
* @example Basic usage
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const props: DisplayShapeLayerProps = {
|
|
33
|
+
* id: 'my-shapes',
|
|
34
|
+
* data: myShapes,
|
|
35
|
+
* pickable: true,
|
|
36
|
+
* showLabels: true,
|
|
37
|
+
* };
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
interface DisplayShapeLayerProps extends CompositeLayerProps {
|
|
41
|
+
/** Unique layer ID - required for deck.gl layer management */
|
|
42
|
+
id: string;
|
|
43
|
+
/**
|
|
44
|
+
* Map instance ID for event bus isolation in multi-map scenarios.
|
|
45
|
+
* Shape events will include this ID in their payload,
|
|
46
|
+
* allowing listeners to filter events by map instance.
|
|
47
|
+
*/
|
|
48
|
+
mapId: UniqueId;
|
|
49
|
+
/**
|
|
50
|
+
* Array of shapes to display
|
|
51
|
+
* Each shape must have a GeoJSON feature with styleProperties
|
|
52
|
+
*/
|
|
53
|
+
data: EditableShape[];
|
|
54
|
+
/**
|
|
55
|
+
* Currently selected shape ID (for highlighting)
|
|
56
|
+
* When set, renders a highlight layer around the selected shape
|
|
57
|
+
*/
|
|
58
|
+
selectedShapeId?: ShapeId;
|
|
59
|
+
/**
|
|
60
|
+
* Callback when a shape is clicked
|
|
61
|
+
* Also triggers a shapes:selected event on the event bus
|
|
62
|
+
* @param shape - The clicked shape with full properties
|
|
63
|
+
*/
|
|
64
|
+
onShapeClick?: (shape: EditableShape) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Callback when a shape is hovered
|
|
67
|
+
* Called with null when hover ends
|
|
68
|
+
* @param shape - The hovered shape, or null when hover ends
|
|
69
|
+
*/
|
|
70
|
+
onShapeHover?: (shape: EditableShape | null) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Whether to show labels on shapes
|
|
73
|
+
* Labels use the shape's `label` property, or `name` if label is not set
|
|
74
|
+
* @default true
|
|
75
|
+
*/
|
|
76
|
+
showLabels?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Global label positioning options
|
|
79
|
+
* Can be overridden per-shape via styleProperties
|
|
80
|
+
* Priority: per-shape properties > labelOptions > defaults
|
|
81
|
+
* @see LabelPositionOptions for available options
|
|
82
|
+
*/
|
|
83
|
+
labelOptions?: LabelPositionOptions;
|
|
84
|
+
/**
|
|
85
|
+
* Whether to show the highlight layer around selected shapes
|
|
86
|
+
* When false, no highlight effect is rendered even when a shape is selected
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
showHighlight?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Custom highlight color for selected shapes [r, g, b, a]
|
|
92
|
+
* Each channel is 0-255
|
|
93
|
+
* @default [40, 245, 190, 100] - Turquoise at ~39% opacity
|
|
94
|
+
* @example Custom red highlight
|
|
95
|
+
* ```tsx
|
|
96
|
+
* highlightColor={[255, 0, 0, 128]} // Red at 50% opacity
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
highlightColor?: [number, number, number, number];
|
|
100
|
+
/**
|
|
101
|
+
* When true (default), applies 60% opacity multiplier to fill colors for standard semi-transparent look.
|
|
102
|
+
* This is a convenience prop for achieving the standard map shape appearance.
|
|
103
|
+
* When false, colors are rendered exactly as specified in styleProperties.
|
|
104
|
+
* @default true
|
|
105
|
+
* @example Standard semi-transparent fills
|
|
106
|
+
* ```tsx
|
|
107
|
+
* <DisplayShapeLayer data={shapes} applyBaseOpacity />
|
|
108
|
+
* // Shape with fillColor [98, 166, 255, 255] renders at alpha 153
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
applyBaseOpacity?: boolean;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { DisplayShapeLayerProps, StyledFeature, StyledFeatureProperties };
|
|
115
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ShapeId } from "../shared/types.js";
|
|
14
|
+
import { UniqueId } from "@accelint/core";
|
|
15
|
+
|
|
16
|
+
//#region src/deckgl/shapes/display-shape-layer/use-shape-selection.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* Return type for useShapeSelection hook
|
|
19
|
+
*/
|
|
20
|
+
interface UseShapeSelectionReturn {
|
|
21
|
+
/** Currently selected shape ID, or undefined if nothing selected */
|
|
22
|
+
selectedId: ShapeId | undefined;
|
|
23
|
+
/** Manually set the selected shape ID (useful for programmatic selection) */
|
|
24
|
+
setSelectedId: (id: ShapeId | undefined) => void;
|
|
25
|
+
/** Manually clear the selection */
|
|
26
|
+
clearSelection: () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hook to manage shape selection state with automatic deselection
|
|
30
|
+
*
|
|
31
|
+
* This hook encapsulates the common pattern of:
|
|
32
|
+
* 1. Listening to `shapes:selected` events and updating state
|
|
33
|
+
* 2. Listening to `shapes:deselected` events and clearing state
|
|
34
|
+
* 3. Listening to map clicks on empty space and emitting `shapes:deselected`
|
|
35
|
+
*
|
|
36
|
+
* Uses a store pattern with `useSyncExternalStore` for proper listener cleanup
|
|
37
|
+
* during HMR (Hot Module Replacement) in development. The store ensures only
|
|
38
|
+
* one bus listener exists per map instance, regardless of how many React
|
|
39
|
+
* components subscribe.
|
|
40
|
+
*
|
|
41
|
+
* @param mapId - The map instance ID for event filtering
|
|
42
|
+
* @returns Selection state and control functions
|
|
43
|
+
*
|
|
44
|
+
* @example Basic usage
|
|
45
|
+
* ```tsx
|
|
46
|
+
* import { useShapeSelection } from '@accelint/map-toolkit/deckgl/shapes';
|
|
47
|
+
*
|
|
48
|
+
* function MapWithShapes() {
|
|
49
|
+
* const { selectedId } = useShapeSelection(MAP_ID);
|
|
50
|
+
*
|
|
51
|
+
* return (
|
|
52
|
+
* <BaseMap id={MAP_ID}>
|
|
53
|
+
* <displayShapeLayer
|
|
54
|
+
* id="shapes"
|
|
55
|
+
* mapId={MAP_ID}
|
|
56
|
+
* data={shapes}
|
|
57
|
+
* selectedShapeId={selectedId}
|
|
58
|
+
* />
|
|
59
|
+
* </BaseMap>
|
|
60
|
+
* );
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @example With programmatic selection control
|
|
65
|
+
* ```tsx
|
|
66
|
+
* function MapWithShapes() {
|
|
67
|
+
* const { selectedId, setSelectedId, clearSelection } = useShapeSelection(MAP_ID);
|
|
68
|
+
*
|
|
69
|
+
* return (
|
|
70
|
+
* <>
|
|
71
|
+
* <button onClick={() => setSelectedId(shapes[0].id)}>Select First Shape</button>
|
|
72
|
+
* <button onClick={clearSelection}>Clear Selection</button>
|
|
73
|
+
* <BaseMap id={MAP_ID}>
|
|
74
|
+
* <displayShapeLayer
|
|
75
|
+
* id="shapes"
|
|
76
|
+
* mapId={MAP_ID}
|
|
77
|
+
* data={shapes}
|
|
78
|
+
* selectedShapeId={selectedId}
|
|
79
|
+
* />
|
|
80
|
+
* </BaseMap>
|
|
81
|
+
* </>
|
|
82
|
+
* );
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
declare function useShapeSelection(mapId: UniqueId): UseShapeSelectionReturn;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { UseShapeSelectionReturn, useShapeSelection };
|
|
89
|
+
//# sourceMappingURL=use-shape-selection.d.ts.map
|