@accelint/map-toolkit 0.3.0 → 0.4.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 (104) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/catalog-info.yaml +5 -5
  3. package/dist/cursor-coordinates/index.d.ts +14 -3
  4. package/dist/cursor-coordinates/index.js +16 -3
  5. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +20 -6
  6. package/dist/cursor-coordinates/use-cursor-coordinates.js +247 -128
  7. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  8. package/dist/deckgl/base-map/constants.d.ts +14 -12
  9. package/dist/deckgl/base-map/constants.js +26 -12
  10. package/dist/deckgl/base-map/constants.js.map +1 -1
  11. package/dist/deckgl/base-map/events.d.ts +6 -4
  12. package/dist/deckgl/base-map/events.js +18 -4
  13. package/dist/deckgl/base-map/events.js.map +1 -1
  14. package/dist/deckgl/base-map/index.d.ts +45 -18
  15. package/dist/deckgl/base-map/index.js +216 -148
  16. package/dist/deckgl/base-map/index.js.map +1 -1
  17. package/dist/deckgl/base-map/provider.d.ts +48 -32
  18. package/dist/deckgl/base-map/provider.js +122 -11
  19. package/dist/deckgl/base-map/provider.js.map +1 -1
  20. package/dist/deckgl/base-map/types.d.ts +49 -39
  21. package/dist/deckgl/base-map/types.js +11 -2
  22. package/dist/deckgl/index.d.ts +18 -13
  23. package/dist/deckgl/index.js +19 -6
  24. package/dist/deckgl/symbol-layer/fiber.d.ts +21 -10
  25. package/dist/deckgl/symbol-layer/fiber.js +18 -3
  26. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  27. package/dist/deckgl/symbol-layer/index.d.ts +68 -54
  28. package/dist/deckgl/symbol-layer/index.js +105 -85
  29. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  30. package/dist/deckgl/text-layer/character-sets.d.ts +19 -17
  31. package/dist/deckgl/text-layer/character-sets.js +40 -19
  32. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  33. package/dist/deckgl/text-layer/default-settings.d.ts +16 -2
  34. package/dist/deckgl/text-layer/default-settings.js +42 -18
  35. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  36. package/dist/deckgl/text-layer/fiber.d.ts +38 -27
  37. package/dist/deckgl/text-layer/fiber.js +18 -3
  38. package/dist/deckgl/text-layer/fiber.js.map +1 -1
  39. package/dist/deckgl/text-layer/index.d.ts +39 -25
  40. package/dist/deckgl/text-layer/index.js +47 -29
  41. package/dist/deckgl/text-layer/index.js.map +1 -1
  42. package/dist/decorators/deckgl.d.ts +16 -2
  43. package/dist/decorators/deckgl.js +25 -7
  44. package/dist/decorators/deckgl.js.map +1 -1
  45. package/dist/map-cursor/events.d.ts +16 -0
  46. package/dist/map-cursor/events.js +27 -0
  47. package/dist/map-cursor/events.js.map +1 -0
  48. package/dist/map-cursor/index.d.ts +17 -0
  49. package/dist/map-cursor/index.js +18 -0
  50. package/dist/map-cursor/store.d.ts +93 -0
  51. package/dist/map-cursor/store.js +351 -0
  52. package/dist/map-cursor/store.js.map +1 -0
  53. package/dist/map-cursor/types.d.ts +81 -0
  54. package/dist/map-cursor/types.js +12 -0
  55. package/dist/map-cursor/use-map-cursor.d.ts +99 -0
  56. package/dist/map-cursor/use-map-cursor.js +116 -0
  57. package/dist/map-cursor/use-map-cursor.js.map +1 -0
  58. package/dist/map-mode/events.d.ts +11 -9
  59. package/dist/map-mode/events.js +43 -9
  60. package/dist/map-mode/events.js.map +1 -1
  61. package/dist/map-mode/index.d.ts +17 -6
  62. package/dist/map-mode/index.js +18 -5
  63. package/dist/map-mode/store.d.ts +26 -3
  64. package/dist/map-mode/store.js +329 -265
  65. package/dist/map-mode/store.js.map +1 -1
  66. package/dist/map-mode/types.d.ts +49 -35
  67. package/dist/map-mode/types.js +11 -2
  68. package/dist/map-mode/use-map-mode.d.ts +21 -7
  69. package/dist/map-mode/use-map-mode.js +66 -23
  70. package/dist/map-mode/use-map-mode.js.map +1 -1
  71. package/dist/maplibre/constants.d.ts +10 -8
  72. package/dist/maplibre/constants.js +22 -8
  73. package/dist/maplibre/constants.js.map +1 -1
  74. package/dist/maplibre/hooks/use-maplibre.d.ts +17 -2
  75. package/dist/maplibre/hooks/use-maplibre.js +77 -31
  76. package/dist/maplibre/hooks/use-maplibre.js.map +1 -1
  77. package/dist/maplibre/index.d.ts +15 -3
  78. package/dist/maplibre/index.js +17 -4
  79. package/dist/viewport/constants.d.ts +8 -6
  80. package/dist/viewport/constants.js +20 -6
  81. package/dist/viewport/constants.js.map +1 -1
  82. package/dist/viewport/index.d.ts +18 -13
  83. package/dist/viewport/index.js +19 -6
  84. package/dist/viewport/types.d.ts +27 -17
  85. package/dist/viewport/types.js +11 -2
  86. package/dist/viewport/use-viewport-state.d.ts +29 -14
  87. package/dist/viewport/use-viewport-state.js +200 -87
  88. package/dist/viewport/use-viewport-state.js.map +1 -1
  89. package/dist/viewport/utils.d.ts +25 -10
  90. package/dist/viewport/utils.js +67 -37
  91. package/dist/viewport/utils.js.map +1 -1
  92. package/dist/viewport/viewport-size.d.ts +27 -15
  93. package/dist/viewport/viewport-size.js +54 -11
  94. package/dist/viewport/viewport-size.js.map +1 -1
  95. package/package.json +54 -27
  96. package/dist/cursor-coordinates/index.js.map +0 -1
  97. package/dist/deckgl/base-map/types.js.map +0 -1
  98. package/dist/deckgl/index.js.map +0 -1
  99. package/dist/map-mode/index.js.map +0 -1
  100. package/dist/map-mode/types.js.map +0 -1
  101. package/dist/maplibre/index.js.map +0 -1
  102. package/dist/metafile-esm.json +0 -1
  103. package/dist/viewport/index.js.map +0 -1
  104. package/dist/viewport/types.js.map +0 -1
@@ -1,306 +1,370 @@
1
- import { Broadcast } from '@accelint/bus';
2
- import { uuid } from '@accelint/core';
3
- import { MapModeEvents } from './events.js';
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
+ */
4
12
 
13
+
14
+ import { MapModeEvents } from "./events.js";
15
+ import { Broadcast } from "@accelint/bus";
16
+ import { uuid } from "@accelint/core";
17
+
18
+ //#region src/map-mode/store.ts
5
19
  const DEFAULT_MODE = "default";
20
+ /**
21
+ * Typed event bus instance for map mode events.
22
+ * Provides type-safe event emission and listening for all map mode state changes.
23
+ */
6
24
  const mapModeBus = Broadcast.getInstance();
25
+ /**
26
+ * Store for map mode state keyed by instanceId
27
+ */
7
28
  const modeStore = /* @__PURE__ */ new Map();
29
+ /**
30
+ * Track React component subscribers per instanceId (for fan-out notifications).
31
+ * Each Set contains onStoreChange callbacks from useSyncExternalStore.
32
+ */
8
33
  const componentSubscribers = /* @__PURE__ */ new Map();
34
+ /**
35
+ * Cache of bus unsubscribe functions (1 per instanceId).
36
+ * This ensures we only have one bus listener per map mode instance, regardless of
37
+ * how many React components subscribe to it.
38
+ */
9
39
  const busUnsubscribers = /* @__PURE__ */ new Map();
40
+ /**
41
+ * Cache of subscription functions per instanceId to avoid recreating on every render
42
+ */
10
43
  const subscriptionCache = /* @__PURE__ */ new Map();
44
+ /**
45
+ * Cache of snapshot functions per instanceId to maintain referential stability
46
+ */
11
47
  const snapshotCache = /* @__PURE__ */ new Map();
48
+ /**
49
+ * Cache of server snapshot functions per instanceId to maintain referential stability.
50
+ * Server snapshots always return default mode since mode state is client-only.
51
+ */
52
+ const serverSnapshotCache = /* @__PURE__ */ new Map();
53
+ /**
54
+ * Cache of requestModeChange functions per instanceId to maintain referential stability
55
+ */
12
56
  const requestModeChangeCache = /* @__PURE__ */ new Map();
57
+ /**
58
+ * Get or create mode state for a given instanceId
59
+ */
13
60
  function getOrCreateState(instanceId) {
14
- if (!modeStore.has(instanceId)) {
15
- modeStore.set(instanceId, {
16
- mode: DEFAULT_MODE,
17
- modeOwners: /* @__PURE__ */ new Map(),
18
- pendingRequests: /* @__PURE__ */ new Map()
19
- });
20
- }
21
- return modeStore.get(instanceId);
61
+ if (!modeStore.has(instanceId)) modeStore.set(instanceId, {
62
+ mode: DEFAULT_MODE,
63
+ modeOwners: /* @__PURE__ */ new Map(),
64
+ pendingRequests: /* @__PURE__ */ new Map()
65
+ });
66
+ return modeStore.get(instanceId);
22
67
  }
68
+ /**
69
+ * Notify all React subscribers for a given instanceId
70
+ */
23
71
  function notifySubscribers(instanceId) {
24
- const subscribers = componentSubscribers.get(instanceId);
25
- if (subscribers) {
26
- for (const onStoreChange of subscribers) {
27
- onStoreChange();
28
- }
29
- }
72
+ const subscribers = componentSubscribers.get(instanceId);
73
+ if (subscribers) for (const onStoreChange of subscribers) onStoreChange();
30
74
  }
75
+ /**
76
+ * Determine if a mode change request should be auto-accepted without authorization
77
+ */
31
78
  function shouldAutoAcceptRequest(state, desiredMode, requestOwner) {
32
- const currentModeOwner = state.modeOwners.get(state.mode);
33
- const desiredModeOwner = state.modeOwners.get(desiredMode);
34
- if (desiredMode === DEFAULT_MODE && requestOwner === currentModeOwner) {
35
- return true;
36
- }
37
- if (requestOwner === currentModeOwner) {
38
- return true;
39
- }
40
- if (!(currentModeOwner || desiredModeOwner)) {
41
- return true;
42
- }
43
- if (state.mode === DEFAULT_MODE && requestOwner === desiredModeOwner) {
44
- return true;
45
- }
46
- return false;
79
+ const currentModeOwner = state.modeOwners.get(state.mode);
80
+ const desiredModeOwner = state.modeOwners.get(desiredMode);
81
+ if (desiredMode === DEFAULT_MODE && requestOwner === currentModeOwner) return true;
82
+ if (requestOwner === currentModeOwner) return true;
83
+ if (!(currentModeOwner || desiredModeOwner)) return true;
84
+ if (state.mode === DEFAULT_MODE && requestOwner === desiredModeOwner) return true;
85
+ return false;
47
86
  }
87
+ /**
88
+ * Set mode and emit change event
89
+ */
48
90
  function setMode(instanceId, state, newMode) {
49
- const previousMode = state.mode;
50
- state.mode = newMode;
51
- mapModeBus.emit(MapModeEvents.changed, {
52
- previousMode,
53
- currentMode: newMode,
54
- id: instanceId
55
- });
56
- notifySubscribers(instanceId);
91
+ const previousMode = state.mode;
92
+ state.mode = newMode;
93
+ mapModeBus.emit(MapModeEvents.changed, {
94
+ previousMode,
95
+ currentMode: newMode,
96
+ id: instanceId
97
+ });
98
+ notifySubscribers(instanceId);
57
99
  }
100
+ /**
101
+ * Approve a request and reject all others
102
+ */
58
103
  function approveRequestAndRejectOthers(instanceId, state, approvedRequest, excludeAuthId, decisionOwner, reason, emitApproval) {
59
- const requestsToReject = [];
60
- for (const request of state.pendingRequests.values()) {
61
- if (request.authId !== excludeAuthId) {
62
- requestsToReject.push(request);
63
- }
64
- }
65
- state.pendingRequests.clear();
66
- setMode(instanceId, state, approvedRequest.desiredMode);
67
- if (approvedRequest.desiredMode !== DEFAULT_MODE) {
68
- state.modeOwners.set(
69
- approvedRequest.desiredMode,
70
- approvedRequest.requestOwner
71
- );
72
- }
73
- if (emitApproval) {
74
- mapModeBus.emit(MapModeEvents.changeDecision, {
75
- authId: approvedRequest.authId,
76
- approved: true,
77
- owner: decisionOwner,
78
- reason,
79
- id: instanceId
80
- });
81
- }
82
- for (const request of requestsToReject) {
83
- mapModeBus.emit(MapModeEvents.changeDecision, {
84
- authId: request.authId,
85
- approved: false,
86
- owner: decisionOwner,
87
- reason: "Request auto-rejected because another request was approved",
88
- id: instanceId
89
- });
90
- }
104
+ const requestsToReject = [];
105
+ for (const request of state.pendingRequests.values()) if (request.authId !== excludeAuthId) requestsToReject.push(request);
106
+ state.pendingRequests.clear();
107
+ setMode(instanceId, state, approvedRequest.desiredMode);
108
+ if (approvedRequest.desiredMode !== DEFAULT_MODE) state.modeOwners.set(approvedRequest.desiredMode, approvedRequest.requestOwner);
109
+ if (emitApproval) mapModeBus.emit(MapModeEvents.changeDecision, {
110
+ authId: approvedRequest.authId,
111
+ approved: true,
112
+ owner: decisionOwner,
113
+ reason,
114
+ id: instanceId
115
+ });
116
+ for (const request of requestsToReject) mapModeBus.emit(MapModeEvents.changeDecision, {
117
+ authId: request.authId,
118
+ approved: false,
119
+ owner: decisionOwner,
120
+ reason: "Request auto-rejected because another request was approved",
121
+ id: instanceId
122
+ });
91
123
  }
124
+ /**
125
+ * Handle pending requests when returning to default mode
126
+ */
92
127
  function handlePendingRequestsOnDefaultMode(instanceId, state, previousMode) {
93
- const firstEntry = Array.from(state.pendingRequests.values())[0];
94
- if (!firstEntry) {
95
- return;
96
- }
97
- const previousModeOwner = state.modeOwners.get(previousMode);
98
- if (!previousModeOwner) {
99
- return;
100
- }
101
- if (firstEntry.desiredMode === DEFAULT_MODE) {
102
- const allRequests = Array.from(state.pendingRequests.values());
103
- state.pendingRequests.clear();
104
- for (const request of allRequests) {
105
- mapModeBus.emit(MapModeEvents.changeDecision, {
106
- authId: request.authId,
107
- approved: false,
108
- owner: previousModeOwner,
109
- reason: "Request rejected - already in requested mode",
110
- id: instanceId
111
- });
112
- }
113
- } else {
114
- approveRequestAndRejectOthers(
115
- instanceId,
116
- state,
117
- firstEntry,
118
- firstEntry.authId,
119
- previousModeOwner,
120
- "Auto-accepted when mode owner returned to default",
121
- true
122
- );
123
- }
128
+ const firstEntry = Array.from(state.pendingRequests.values())[0];
129
+ if (!firstEntry) return;
130
+ const previousModeOwner = state.modeOwners.get(previousMode);
131
+ if (!previousModeOwner) return;
132
+ if (firstEntry.desiredMode === DEFAULT_MODE) {
133
+ const allRequests = Array.from(state.pendingRequests.values());
134
+ state.pendingRequests.clear();
135
+ for (const request of allRequests) mapModeBus.emit(MapModeEvents.changeDecision, {
136
+ authId: request.authId,
137
+ approved: false,
138
+ owner: previousModeOwner,
139
+ reason: "Request rejected - already in requested mode",
140
+ id: instanceId
141
+ });
142
+ } else approveRequestAndRejectOthers(instanceId, state, firstEntry, firstEntry.authId, previousModeOwner, "Auto-accepted when mode owner returned to default", true);
124
143
  }
144
+ /**
145
+ * Handle authorization decision
146
+ *
147
+ * Processes approval/rejection decisions from mode owners. Only the current mode's owner
148
+ * can make authorization decisions. If a decision comes from a non-owner, a warning is
149
+ * logged and the decision is ignored to prevent unauthorized mode changes.
150
+ *
151
+ * @param instanceId - The unique identifier for this map instance
152
+ * @param state - The mode state for this instance
153
+ * @param payload - The authorization decision containing authId, approved status, and owner
154
+ */
125
155
  function handleAuthorizationDecision(instanceId, state, payload) {
126
- const { approved, authId, owner: decisionOwner } = payload;
127
- const currentModeOwner = state.modeOwners.get(state.mode);
128
- if (decisionOwner !== currentModeOwner) {
129
- console.warn(
130
- `[MapMode] Authorization decision from "${decisionOwner}" ignored - not the owner of mode "${state.mode}" (owner: ${currentModeOwner || "none"})`
131
- );
132
- return;
133
- }
134
- let matchingRequestOwner = null;
135
- let matchingRequest = null;
136
- for (const [requestOwner, request] of state.pendingRequests.entries()) {
137
- if (request.authId === authId) {
138
- matchingRequestOwner = requestOwner;
139
- matchingRequest = request;
140
- break;
141
- }
142
- }
143
- if (!(matchingRequest && matchingRequestOwner)) {
144
- return;
145
- }
146
- if (approved) {
147
- approveRequestAndRejectOthers(
148
- instanceId,
149
- state,
150
- matchingRequest,
151
- authId,
152
- decisionOwner,
153
- "",
154
- false
155
- );
156
- } else {
157
- state.pendingRequests.delete(matchingRequestOwner);
158
- }
156
+ const { approved, authId, owner: decisionOwner } = payload;
157
+ const currentModeOwner = state.modeOwners.get(state.mode);
158
+ if (decisionOwner !== currentModeOwner) {
159
+ console.warn(`[MapMode] Authorization decision from "${decisionOwner}" ignored - not the owner of mode "${state.mode}" (owner: ${currentModeOwner || "none"})`);
160
+ return;
161
+ }
162
+ let matchingRequestOwner = null;
163
+ let matchingRequest = null;
164
+ for (const [requestOwner, request] of state.pendingRequests.entries()) if (request.authId === authId) {
165
+ matchingRequestOwner = requestOwner;
166
+ matchingRequest = request;
167
+ break;
168
+ }
169
+ if (!(matchingRequest && matchingRequestOwner)) return;
170
+ if (approved) approveRequestAndRejectOthers(instanceId, state, matchingRequest, authId, decisionOwner, "", false);
171
+ else state.pendingRequests.delete(matchingRequestOwner);
159
172
  }
173
+ /**
174
+ * Handle mode change request logic
175
+ */
160
176
  function handleModeChangeRequest(instanceId, state, desiredMode, requestOwner) {
161
- const desiredModeOwner = state.modeOwners.get(desiredMode);
162
- if (shouldAutoAcceptRequest(state, desiredMode, requestOwner)) {
163
- setMode(instanceId, state, desiredMode);
164
- if (desiredMode !== DEFAULT_MODE && !desiredModeOwner) {
165
- state.modeOwners.set(desiredMode, requestOwner);
166
- }
167
- state.pendingRequests.delete(requestOwner);
168
- return;
169
- }
170
- const authId = uuid();
171
- state.pendingRequests.set(requestOwner, {
172
- authId,
173
- desiredMode,
174
- currentMode: state.mode,
175
- requestOwner
176
- });
177
- mapModeBus.emit(MapModeEvents.changeAuthorization, {
178
- authId,
179
- desiredMode,
180
- currentMode: state.mode,
181
- id: instanceId
182
- });
177
+ const desiredModeOwner = state.modeOwners.get(desiredMode);
178
+ if (shouldAutoAcceptRequest(state, desiredMode, requestOwner)) {
179
+ setMode(instanceId, state, desiredMode);
180
+ if (desiredMode !== DEFAULT_MODE && !desiredModeOwner) state.modeOwners.set(desiredMode, requestOwner);
181
+ state.pendingRequests.delete(requestOwner);
182
+ return;
183
+ }
184
+ const authId = uuid();
185
+ state.pendingRequests.set(requestOwner, {
186
+ authId,
187
+ desiredMode,
188
+ currentMode: state.mode,
189
+ requestOwner
190
+ });
191
+ mapModeBus.emit(MapModeEvents.changeAuthorization, {
192
+ authId,
193
+ desiredMode,
194
+ currentMode: state.mode,
195
+ id: instanceId
196
+ });
183
197
  }
198
+ /**
199
+ * Ensures a single bus listener exists for the given instanceId.
200
+ * All React subscribers will be notified via fan-out when the bus events fire.
201
+ * This prevents creating N bus listeners for N React components.
202
+ *
203
+ * @param instanceId - The unique identifier for the map mode instance
204
+ */
184
205
  function ensureBusListener(instanceId) {
185
- if (busUnsubscribers.has(instanceId)) {
186
- return;
187
- }
188
- const state = getOrCreateState(instanceId);
189
- const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {
190
- const { desiredMode, owner: requestOwner, id } = event.payload;
191
- if (id !== instanceId || desiredMode === state.mode) {
192
- return;
193
- }
194
- handleModeChangeRequest(instanceId, state, desiredMode, requestOwner);
195
- });
196
- const unsubDecision = mapModeBus.on(MapModeEvents.changeDecision, (event) => {
197
- const { id, approved, authId, owner } = event.payload;
198
- if (id !== instanceId) {
199
- return;
200
- }
201
- handleAuthorizationDecision(instanceId, state, { approved, authId, owner });
202
- });
203
- const unsubChanged = mapModeBus.on(MapModeEvents.changed, (event) => {
204
- const { currentMode, previousMode, id } = event.payload;
205
- if (id !== instanceId) {
206
- return;
207
- }
208
- if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) {
209
- handlePendingRequestsOnDefaultMode(instanceId, state, previousMode);
210
- }
211
- });
212
- busUnsubscribers.set(instanceId, () => {
213
- unsubRequest();
214
- unsubDecision();
215
- unsubChanged();
216
- });
206
+ if (busUnsubscribers.has(instanceId)) return;
207
+ const state = getOrCreateState(instanceId);
208
+ const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {
209
+ const { desiredMode, owner: requestOwner, id } = event.payload;
210
+ if (id !== instanceId || desiredMode === state.mode) return;
211
+ handleModeChangeRequest(instanceId, state, desiredMode, requestOwner);
212
+ });
213
+ const unsubDecision = mapModeBus.on(MapModeEvents.changeDecision, (event) => {
214
+ const { id, approved, authId, owner } = event.payload;
215
+ if (id !== instanceId) return;
216
+ handleAuthorizationDecision(instanceId, state, {
217
+ approved,
218
+ authId,
219
+ owner
220
+ });
221
+ });
222
+ const unsubChanged = mapModeBus.on(MapModeEvents.changed, (event) => {
223
+ const { currentMode, previousMode, id } = event.payload;
224
+ if (id !== instanceId) return;
225
+ if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) handlePendingRequestsOnDefaultMode(instanceId, state, previousMode);
226
+ });
227
+ busUnsubscribers.set(instanceId, () => {
228
+ unsubRequest();
229
+ unsubDecision();
230
+ unsubChanged();
231
+ });
217
232
  }
233
+ /**
234
+ * Cleans up the bus listener if no React subscribers remain.
235
+ *
236
+ * @param instanceId - The unique identifier for the map mode instance
237
+ */
218
238
  function cleanupBusListenerIfNeeded(instanceId) {
219
- const subscribers = componentSubscribers.get(instanceId);
220
- if (!subscribers || subscribers.size === 0) {
221
- const unsub = busUnsubscribers.get(instanceId);
222
- if (unsub) {
223
- unsub();
224
- busUnsubscribers.delete(instanceId);
225
- }
226
- modeStore.delete(instanceId);
227
- componentSubscribers.delete(instanceId);
228
- subscriptionCache.delete(instanceId);
229
- snapshotCache.delete(instanceId);
230
- requestModeChangeCache.delete(instanceId);
231
- }
239
+ const subscribers = componentSubscribers.get(instanceId);
240
+ if (!subscribers || subscribers.size === 0) {
241
+ const unsub = busUnsubscribers.get(instanceId);
242
+ if (unsub) {
243
+ unsub();
244
+ busUnsubscribers.delete(instanceId);
245
+ }
246
+ modeStore.delete(instanceId);
247
+ componentSubscribers.delete(instanceId);
248
+ subscriptionCache.delete(instanceId);
249
+ snapshotCache.delete(instanceId);
250
+ serverSnapshotCache.delete(instanceId);
251
+ requestModeChangeCache.delete(instanceId);
252
+ }
232
253
  }
254
+ /**
255
+ * Creates or retrieves a cached subscription function for a given instanceId.
256
+ * Uses a fan-out pattern: 1 bus listener -> N React subscribers.
257
+ * Automatically cleans up map mode state when the last subscriber unsubscribes.
258
+ *
259
+ * @param instanceId - The unique identifier for the map mode instance
260
+ * @returns A subscription function for useSyncExternalStore
261
+ */
233
262
  function getOrCreateSubscription(instanceId) {
234
- const subscription = subscriptionCache.get(instanceId) ?? ((onStoreChange) => {
235
- getOrCreateState(instanceId);
236
- ensureBusListener(instanceId);
237
- let subscriberSet = componentSubscribers.get(instanceId);
238
- if (!subscriberSet) {
239
- subscriberSet = /* @__PURE__ */ new Set();
240
- componentSubscribers.set(instanceId, subscriberSet);
241
- }
242
- subscriberSet.add(onStoreChange);
243
- return () => {
244
- const currentSubscriberSet = componentSubscribers.get(instanceId);
245
- if (currentSubscriberSet) {
246
- currentSubscriberSet.delete(onStoreChange);
247
- }
248
- cleanupBusListenerIfNeeded(instanceId);
249
- };
250
- });
251
- subscriptionCache.set(instanceId, subscription);
252
- return subscription;
263
+ const subscription = subscriptionCache.get(instanceId) ?? ((onStoreChange) => {
264
+ getOrCreateState(instanceId);
265
+ ensureBusListener(instanceId);
266
+ let subscriberSet = componentSubscribers.get(instanceId);
267
+ if (!subscriberSet) {
268
+ subscriberSet = /* @__PURE__ */ new Set();
269
+ componentSubscribers.set(instanceId, subscriberSet);
270
+ }
271
+ subscriberSet.add(onStoreChange);
272
+ return () => {
273
+ const currentSubscriberSet = componentSubscribers.get(instanceId);
274
+ if (currentSubscriberSet) currentSubscriberSet.delete(onStoreChange);
275
+ cleanupBusListenerIfNeeded(instanceId);
276
+ };
277
+ });
278
+ subscriptionCache.set(instanceId, subscription);
279
+ return subscription;
253
280
  }
281
+ /**
282
+ * Creates or retrieves a cached snapshot function for a given instanceId.
283
+ * The string returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.
284
+ *
285
+ * @param instanceId - The unique identifier for the map mode instance
286
+ * @returns A snapshot function for useSyncExternalStore
287
+ */
254
288
  function getOrCreateSnapshot(instanceId) {
255
- const snapshot = snapshotCache.get(instanceId) ?? (() => {
256
- const state = modeStore.get(instanceId);
257
- if (!state) {
258
- return DEFAULT_MODE;
259
- }
260
- return state.mode;
261
- });
262
- snapshotCache.set(instanceId, snapshot);
263
- return snapshot;
289
+ const snapshot = snapshotCache.get(instanceId) ?? (() => {
290
+ const state = modeStore.get(instanceId);
291
+ if (!state) return DEFAULT_MODE;
292
+ return state.mode;
293
+ });
294
+ snapshotCache.set(instanceId, snapshot);
295
+ return snapshot;
296
+ }
297
+ /**
298
+ * Creates or retrieves a cached server snapshot function for a given instanceId.
299
+ * Server snapshots always return the default mode since mode state is client-only.
300
+ * Required for SSR/RSC compatibility with useSyncExternalStore.
301
+ *
302
+ * @param instanceId - The unique identifier for the map mode instance
303
+ * @returns A server snapshot function for useSyncExternalStore
304
+ */
305
+ function getOrCreateServerSnapshot(instanceId) {
306
+ const serverSnapshot = serverSnapshotCache.get(instanceId) ?? (() => DEFAULT_MODE);
307
+ serverSnapshotCache.set(instanceId, serverSnapshot);
308
+ return serverSnapshot;
264
309
  }
310
+ /**
311
+ * Creates or retrieves a cached requestModeChange function for a given instanceId.
312
+ * This maintains referential stability for the function reference.
313
+ *
314
+ * @param instanceId - The unique identifier for the map mode instance
315
+ * @returns A requestModeChange function for this instance
316
+ */
265
317
  function getOrCreateRequestModeChange(instanceId) {
266
- const requestModeChange = requestModeChangeCache.get(instanceId) ?? ((desiredMode, requestOwner) => {
267
- const trimmedDesiredMode = desiredMode.trim();
268
- const trimmedRequestOwner = requestOwner.trim();
269
- if (!trimmedDesiredMode) {
270
- throw new Error("requestModeChange requires non-empty desiredMode");
271
- }
272
- if (!trimmedRequestOwner) {
273
- throw new Error("requestModeChange requires non-empty requestOwner");
274
- }
275
- mapModeBus.emit(MapModeEvents.changeRequest, {
276
- desiredMode: trimmedDesiredMode,
277
- owner: trimmedRequestOwner,
278
- id: instanceId
279
- });
280
- });
281
- requestModeChangeCache.set(instanceId, requestModeChange);
282
- return requestModeChange;
318
+ const requestModeChange = requestModeChangeCache.get(instanceId) ?? ((desiredMode, requestOwner) => {
319
+ const trimmedDesiredMode = desiredMode.trim();
320
+ const trimmedRequestOwner = requestOwner.trim();
321
+ if (!trimmedDesiredMode) throw new Error("requestModeChange requires non-empty desiredMode");
322
+ if (!trimmedRequestOwner) throw new Error("requestModeChange requires non-empty requestOwner");
323
+ mapModeBus.emit(MapModeEvents.changeRequest, {
324
+ desiredMode: trimmedDesiredMode,
325
+ owner: trimmedRequestOwner,
326
+ id: instanceId
327
+ });
328
+ });
329
+ requestModeChangeCache.set(instanceId, requestModeChange);
330
+ return requestModeChange;
283
331
  }
332
+ /**
333
+ * Get the owner of the current mode for a given map instance
334
+ * @internal - For internal map-toolkit use only
335
+ */
284
336
  function getCurrentModeOwner(instanceId) {
285
- const state = modeStore.get(instanceId);
286
- if (!state) {
287
- return void 0;
288
- }
289
- return state.modeOwners.get(state.mode);
337
+ const state = modeStore.get(instanceId);
338
+ if (!state) return;
339
+ return state.modeOwners.get(state.mode);
290
340
  }
341
+ /**
342
+ * Manually clear map mode state for a specific instanceId.
343
+ * This is typically not needed as cleanup happens automatically when all subscribers unmount.
344
+ * Use this only in advanced scenarios where manual cleanup is required.
345
+ *
346
+ * @param instanceId - The unique identifier for the map mode instance to clear
347
+ *
348
+ * @example
349
+ * ```tsx
350
+ * // Manual cleanup (rarely needed)
351
+ * clearMapModeState('my-map-instance');
352
+ * ```
353
+ */
291
354
  function clearMapModeState(instanceId) {
292
- const unsub = busUnsubscribers.get(instanceId);
293
- if (unsub) {
294
- unsub();
295
- busUnsubscribers.delete(instanceId);
296
- }
297
- modeStore.delete(instanceId);
298
- componentSubscribers.delete(instanceId);
299
- subscriptionCache.delete(instanceId);
300
- snapshotCache.delete(instanceId);
301
- requestModeChangeCache.delete(instanceId);
355
+ const unsub = busUnsubscribers.get(instanceId);
356
+ if (unsub) {
357
+ unsub();
358
+ busUnsubscribers.delete(instanceId);
359
+ }
360
+ modeStore.delete(instanceId);
361
+ componentSubscribers.delete(instanceId);
362
+ subscriptionCache.delete(instanceId);
363
+ snapshotCache.delete(instanceId);
364
+ serverSnapshotCache.delete(instanceId);
365
+ requestModeChangeCache.delete(instanceId);
302
366
  }
303
367
 
304
- export { clearMapModeState, getCurrentModeOwner, getOrCreateRequestModeChange, getOrCreateSnapshot, getOrCreateSubscription };
305
- //# sourceMappingURL=store.js.map
368
+ //#endregion
369
+ export { clearMapModeState, getCurrentModeOwner, getOrCreateRequestModeChange, getOrCreateServerSnapshot, getOrCreateSnapshot, getOrCreateSubscription };
306
370
  //# sourceMappingURL=store.js.map