@accelint/map-toolkit 0.2.0 → 0.3.1

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 +81 -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 +16 -21
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/map-mode/store.ts"],"names":[],"mappings":"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","file":"store.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\nimport { Broadcast } from '@accelint/bus';\nimport { uuid } from '@accelint/core';\nimport { MapModeEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapModeEventType, ModeChangeDecisionPayload } from './types';\n\nconst DEFAULT_MODE = 'default';\n\n/**\n * Typed event bus instance for map mode events.\n * Provides type-safe event emission and listening for all map mode state changes.\n */\nconst mapModeBus = Broadcast.getInstance<MapModeEventType>();\n\n/**\n * Internal type for tracking pending authorization requests.\n * @internal\n */\ntype PendingRequest = {\n authId: string;\n desiredMode: string;\n currentMode: string;\n requestOwner: string;\n};\n\n/**\n * External store for managing map mode state.\n *\n * This store implements the observable pattern for use with React's `useSyncExternalStore` hook.\n * It manages all mode state, ownership tracking, authorization flow, and event bus communication\n * outside of React's component tree.\n *\n * Each store instance is identified by a unique `id` and operates independently,\n * enabling scenarios with multiple isolated map instances (e.g., main map + minimap).\n * Stores communicate via the event bus and filter events by `id` to ensure isolation.\n *\n * The store always initializes in 'default' mode and does not accept a custom default mode.\n *\n * @see {getOrCreateStore} - Creates or retrieves a store for a given map instance\n * @see {destroyStore} - Destroys a store and cleans up its resources\n */\nexport class MapModeStore {\n private mode = DEFAULT_MODE;\n private readonly defaultMode = DEFAULT_MODE;\n private readonly modeOwners = new Map<string, string>();\n private readonly pendingRequests = new Map<string, PendingRequest>();\n private readonly listeners = new Set<() => void>();\n private readonly bus = mapModeBus;\n private readonly unsubscribers: Array<() => void> = [];\n\n constructor(private readonly id: UniqueId) {\n this.id = id;\n // Subscribe to bus events\n this.setupEventListeners();\n }\n\n /**\n * Get current mode snapshot (for useSyncExternalStore)\n */\n getSnapshot = (): string => {\n return this.mode;\n };\n\n /**\n * Subscribe to mode changes (for useSyncExternalStore)\n */\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n /**\n * Request a mode change\n *\n * If the mode change can be auto-accepted (no ownership conflicts), the mode changes immediately.\n * Otherwise, an authorization request is emitted and stored as a pending request.\n *\n * **Important**: If the requester already has a pending authorization request, it will be replaced\n * with this new request. Only one pending request per requester is maintained at a time.\n *\n * @param desiredMode - The mode to switch to (automatically trimmed of whitespace)\n * @param requestOwner - Unique identifier of the component requesting the change (automatically trimmed of whitespace)\n * @throws Error if either parameter is empty or whitespace-only\n *\n * @example\n * ```ts\n * // First request from 'drawing-tool'\n * store.requestModeChange('drawing', 'drawing-tool');\n * // → Creates pending request with authId 'abc-123'\n *\n * // Second request from same 'drawing-tool' before first is resolved\n * store.requestModeChange('measuring', 'drawing-tool');\n * // → Replaces pending request, new authId 'def-456', old 'abc-123' is discarded\n * ```\n */\n requestModeChange = (desiredMode: string, requestOwner: string): void => {\n const trimmedDesiredMode = desiredMode.trim();\n const trimmedRequestOwner = requestOwner.trim();\n\n if (!trimmedDesiredMode) {\n throw new Error('requestModeChange requires non-empty desiredMode');\n }\n if (!trimmedRequestOwner) {\n throw new Error('requestModeChange requires non-empty requestOwner');\n }\n\n this.bus.emit(MapModeEvents.changeRequest, {\n desiredMode: trimmedDesiredMode,\n owner: trimmedRequestOwner,\n id: this.id,\n });\n };\n\n /**\n * Notify all subscribers of state change\n */\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n\n /**\n * Setup event listeners for bus events\n *\n * Note: Event listeners remain active even after early returns in handlers.\n * This is by design - cleanup happens in destroy() which is called automatically\n * by MapProvider on unmount. Consumers don't need to manually manage cleanup.\n */\n private setupEventListeners(): void {\n // Listen for mode change requests\n const unsubRequest = this.bus.on(MapModeEvents.changeRequest, (event) => {\n const { desiredMode, owner: requestOwner, id } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== this.id || desiredMode === this.mode) {\n return;\n }\n\n this.handleModeChangeRequest(desiredMode, requestOwner);\n });\n this.unsubscribers.push(unsubRequest);\n\n // Listen for authorization decisions\n const unsubDecision = this.bus.on(MapModeEvents.changeDecision, (event) => {\n const { id, approved, authId, owner } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== this.id) {\n return;\n }\n\n this.handleAuthorizationDecision({ approved, authId, owner });\n });\n this.unsubscribers.push(unsubDecision);\n\n // Listen for mode changes to handle pending requests\n const unsubChanged = this.bus.on(MapModeEvents.changed, (event) => {\n const { currentMode, previousMode, id } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== this.id) {\n return;\n }\n\n // When mode owner changes to default mode, handle pending requests\n if (currentMode === this.defaultMode && this.pendingRequests.size > 0) {\n this.handlePendingRequestsOnDefaultMode(previousMode);\n }\n });\n this.unsubscribers.push(unsubChanged);\n }\n\n /**\n * Determine if a mode change request should be auto-accepted without authorization\n */\n private shouldAutoAcceptRequest(\n desiredMode: string,\n requestOwner: string,\n currentModeOwner: string | undefined,\n desiredModeOwner: string | undefined,\n ): boolean {\n // Owner returning to default mode\n if (desiredMode === this.defaultMode && requestOwner === currentModeOwner) {\n return true;\n }\n\n // Owner switching between their own modes\n if (requestOwner === currentModeOwner) {\n return true;\n }\n\n // No ownership conflicts exist\n if (!(currentModeOwner || desiredModeOwner)) {\n return true;\n }\n\n // Entering an owned mode from default mode\n if (this.mode === this.defaultMode && requestOwner === desiredModeOwner) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Handle mode change request logic\n */\n private handleModeChangeRequest(\n desiredMode: string,\n requestOwner: string,\n ): void {\n const currentModeOwner = this.modeOwners.get(this.mode);\n const desiredModeOwner = this.modeOwners.get(desiredMode);\n\n // Check if this request should be auto-accepted\n if (\n this.shouldAutoAcceptRequest(\n desiredMode,\n requestOwner,\n currentModeOwner,\n desiredModeOwner,\n )\n ) {\n this.setMode(desiredMode);\n\n // Store the desired mode's owner unless it's default\n if (desiredMode !== this.defaultMode && !desiredModeOwner) {\n this.modeOwners.set(desiredMode, requestOwner);\n }\n\n // Clear requester's pending request since mode changed successfully\n this.pendingRequests.delete(requestOwner);\n return;\n }\n\n // Otherwise, send authorization request\n const authId = uuid();\n\n this.pendingRequests.set(requestOwner, {\n authId,\n desiredMode,\n currentMode: this.mode,\n requestOwner,\n });\n\n this.bus.emit(MapModeEvents.changeAuthorization, {\n authId,\n desiredMode,\n currentMode: this.mode,\n id: this.id,\n });\n }\n\n /**\n * Handle authorization decision\n *\n * Processes approval/rejection decisions from mode owners. Only the current mode's owner\n * can make authorization decisions. If a decision comes from a non-owner, a warning is\n * logged and the decision is ignored to prevent unauthorized mode changes.\n *\n * @param payload - The authorization decision containing authId, approved status, and owner\n */\n private handleAuthorizationDecision(payload: {\n approved: boolean;\n authId: string;\n owner: string;\n }): void {\n const { approved, authId, owner: decisionOwner } = payload;\n\n // Verify decision is from current mode's owner\n // Logs a warning if unauthorized component attempts to make decisions\n const currentModeOwner = this.modeOwners.get(this.mode);\n if (decisionOwner !== currentModeOwner) {\n console.warn(\n `[MapMode] Authorization decision from \"${decisionOwner}\" ignored - not the owner of mode \"${this.mode}\" (owner: ${currentModeOwner || 'none'})`,\n );\n return;\n }\n\n // Find the request with matching authId\n let matchingRequestOwner: string | null = null;\n let matchingRequest: PendingRequest | null = null;\n\n for (const [requestOwner, request] of this.pendingRequests.entries()) {\n if (request.authId === authId) {\n matchingRequestOwner = requestOwner;\n matchingRequest = request;\n break;\n }\n }\n\n if (!(matchingRequest && matchingRequestOwner)) {\n return;\n }\n\n if (approved) {\n this.approveRequestAndRejectOthers(\n matchingRequest,\n authId,\n decisionOwner,\n '',\n false,\n );\n } else {\n this.pendingRequests.delete(matchingRequestOwner);\n }\n }\n\n /**\n * Approve a request and reject all others\n */\n private approveRequestAndRejectOthers(\n approvedRequest: PendingRequest,\n excludeAuthId: string,\n decisionOwner: string,\n reason: string,\n emitApproval: boolean,\n ): void {\n // Collect all other pending requests to emit rejections for\n const requestsToReject: PendingRequest[] = [];\n for (const request of this.pendingRequests.values()) {\n if (request.authId !== excludeAuthId) {\n requestsToReject.push(request);\n }\n }\n\n // Clear all pending requests BEFORE changing mode\n this.pendingRequests.clear();\n\n // Change mode\n this.setMode(approvedRequest.desiredMode);\n\n // Store the new mode's owner (unless it's default mode)\n if (approvedRequest.desiredMode !== this.defaultMode) {\n this.modeOwners.set(\n approvedRequest.desiredMode,\n approvedRequest.requestOwner,\n );\n }\n\n // Emit approval decision if requested\n if (emitApproval) {\n this.bus.emit(MapModeEvents.changeDecision, {\n authId: approvedRequest.authId,\n approved: true,\n owner: decisionOwner,\n reason,\n id: this.id,\n });\n }\n\n // Emit rejection events for all other pending requests\n for (const request of requestsToReject) {\n this.bus.emit(MapModeEvents.changeDecision, {\n authId: request.authId,\n approved: false,\n owner: decisionOwner,\n reason: 'Request auto-rejected because another request was approved',\n id: this.id,\n });\n }\n }\n\n /**\n * Handle pending requests when returning to default mode\n */\n private handlePendingRequestsOnDefaultMode(previousMode: string): void {\n const firstEntry = Array.from(this.pendingRequests.values())[0];\n if (!firstEntry) {\n return;\n }\n\n const previousModeOwner = this.modeOwners.get(previousMode);\n\n if (!previousModeOwner) {\n return;\n }\n\n // If the first pending request is for default mode, reject all requests\n if (firstEntry.desiredMode === this.defaultMode) {\n const allRequests = Array.from(this.pendingRequests.values());\n this.pendingRequests.clear();\n\n for (const request of allRequests) {\n this.bus.emit(MapModeEvents.changeDecision, {\n authId: request.authId,\n approved: false,\n owner: previousModeOwner,\n reason: 'Request rejected - already in requested mode',\n id: this.id,\n } satisfies ModeChangeDecisionPayload);\n }\n } else {\n // Auto-accept the first pending request for a different mode\n this.approveRequestAndRejectOthers(\n firstEntry,\n firstEntry.authId,\n previousModeOwner,\n 'Auto-accepted when mode owner returned to default',\n true,\n );\n }\n }\n\n /**\n * Set mode and notify listeners\n */\n private setMode(newMode: string): void {\n const previousMode = this.mode;\n this.mode = newMode;\n\n this.bus.emit(MapModeEvents.changed, {\n previousMode,\n currentMode: newMode,\n id: this.id,\n });\n\n this.notify();\n }\n\n /**\n * Clean up store resources\n */\n destroy(): void {\n // Unsubscribe from all bus events\n for (const unsubscribe of this.unsubscribers) {\n unsubscribe();\n }\n this.unsubscribers.length = 0;\n\n // Clear all state\n this.modeOwners.clear();\n this.pendingRequests.clear();\n this.listeners.clear();\n }\n}\n\n/**\n * Global store registry\n */\nconst storeRegistry = new Map<UniqueId, MapModeStore>();\n\n/**\n * Get or create a store for a given map instance\n */\nexport function getOrCreateStore(id: UniqueId): MapModeStore {\n if (!storeRegistry.has(id)) {\n storeRegistry.set(id, new MapModeStore(id));\n }\n // biome-ignore lint/style/noNonNullAssertion: Store guaranteed to exist after has() check above\n return storeRegistry.get(id)!;\n}\n\n/**\n * Destroy and remove a store from the registry\n */\nexport function destroyStore(id: UniqueId): void {\n const store = storeRegistry.get(id);\n if (store) {\n store.destroy();\n storeRegistry.delete(id);\n }\n}\n\n/**\n * Get a store by map ID (for testing/advanced use)\n */\nexport function getStore(id: UniqueId): MapModeStore | undefined {\n return storeRegistry.get(id);\n}\n"]}
1
+ {"version":3,"sources":["../../src/map-mode/store.ts"],"names":[],"mappings":"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","file":"store.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\nimport { Broadcast } from '@accelint/bus';\nimport { uuid } from '@accelint/core';\nimport { MapModeEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapModeEventType, ModeChangeDecisionPayload } from './types';\n\nconst DEFAULT_MODE = 'default';\n\n/**\n * Typed event bus instance for map mode events.\n * Provides type-safe event emission and listening for all map mode state changes.\n */\nconst mapModeBus = Broadcast.getInstance<MapModeEventType>();\n\n/**\n * Internal type for tracking pending authorization requests.\n * @internal\n */\ntype PendingRequest = {\n authId: string;\n desiredMode: string;\n currentMode: string;\n requestOwner: string;\n};\n\n/**\n * Type representing the state for a single map mode instance\n */\ntype MapModeState = {\n mode: string;\n modeOwners: Map<string, string>;\n pendingRequests: Map<string, PendingRequest>;\n};\n\n/**\n * Store for map mode state keyed by instanceId\n */\nconst modeStore = new Map<UniqueId, MapModeState>();\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 mode 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 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 * Cache of requestModeChange functions per instanceId to maintain referential stability\n */\nconst requestModeChangeCache = new Map<\n UniqueId,\n (desiredMode: string, requestOwner: string) => void\n>();\n\n/**\n * Get or create mode state for a given instanceId\n */\nfunction getOrCreateState(instanceId: UniqueId): MapModeState {\n if (!modeStore.has(instanceId)) {\n modeStore.set(instanceId, {\n mode: DEFAULT_MODE,\n modeOwners: new Map(),\n pendingRequests: new Map(),\n });\n }\n // biome-ignore lint/style/noNonNullAssertion: State guaranteed to exist after has() check above\n return modeStore.get(instanceId)!;\n}\n\n/**\n * Notify all React subscribers for a given instanceId\n */\nfunction notifySubscribers(instanceId: UniqueId): void {\n const subscribers = componentSubscribers.get(instanceId);\n if (subscribers) {\n for (const onStoreChange of subscribers) {\n onStoreChange();\n }\n }\n}\n\n/**\n * Determine if a mode change request should be auto-accepted without authorization\n */\nfunction shouldAutoAcceptRequest(\n state: MapModeState,\n desiredMode: string,\n requestOwner: string,\n): boolean {\n const currentModeOwner = state.modeOwners.get(state.mode);\n const desiredModeOwner = state.modeOwners.get(desiredMode);\n\n // Owner returning to default mode\n if (desiredMode === DEFAULT_MODE && requestOwner === currentModeOwner) {\n return true;\n }\n\n // Owner switching between their own modes\n if (requestOwner === currentModeOwner) {\n return true;\n }\n\n // No ownership conflicts exist\n if (!(currentModeOwner || desiredModeOwner)) {\n return true;\n }\n\n // Entering an owned mode from default mode\n if (state.mode === DEFAULT_MODE && requestOwner === desiredModeOwner) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Set mode and emit change event\n */\nfunction setMode(\n instanceId: UniqueId,\n state: MapModeState,\n newMode: string,\n): void {\n const previousMode = state.mode;\n state.mode = newMode;\n\n mapModeBus.emit(MapModeEvents.changed, {\n previousMode,\n currentMode: newMode,\n id: instanceId,\n });\n\n notifySubscribers(instanceId);\n}\n\n/**\n * Approve a request and reject all others\n */\nfunction approveRequestAndRejectOthers(\n instanceId: UniqueId,\n state: MapModeState,\n approvedRequest: PendingRequest,\n excludeAuthId: string,\n decisionOwner: string,\n reason: string,\n emitApproval: boolean,\n): void {\n // Collect all other pending requests to emit rejections for\n const requestsToReject: PendingRequest[] = [];\n for (const request of state.pendingRequests.values()) {\n if (request.authId !== excludeAuthId) {\n requestsToReject.push(request);\n }\n }\n\n // Clear all pending requests BEFORE changing mode\n state.pendingRequests.clear();\n\n // Change mode\n setMode(instanceId, state, approvedRequest.desiredMode);\n\n // Store the new mode's owner (unless it's default mode)\n if (approvedRequest.desiredMode !== DEFAULT_MODE) {\n state.modeOwners.set(\n approvedRequest.desiredMode,\n approvedRequest.requestOwner,\n );\n }\n\n // Emit approval decision if requested\n if (emitApproval) {\n mapModeBus.emit(MapModeEvents.changeDecision, {\n authId: approvedRequest.authId,\n approved: true,\n owner: decisionOwner,\n reason,\n id: instanceId,\n });\n }\n\n // Emit rejection events for all other pending requests\n for (const request of requestsToReject) {\n mapModeBus.emit(MapModeEvents.changeDecision, {\n authId: request.authId,\n approved: false,\n owner: decisionOwner,\n reason: 'Request auto-rejected because another request was approved',\n id: instanceId,\n });\n }\n}\n\n/**\n * Handle pending requests when returning to default mode\n */\nfunction handlePendingRequestsOnDefaultMode(\n instanceId: UniqueId,\n state: MapModeState,\n previousMode: string,\n): void {\n const firstEntry = Array.from(state.pendingRequests.values())[0];\n if (!firstEntry) {\n return;\n }\n\n const previousModeOwner = state.modeOwners.get(previousMode);\n\n if (!previousModeOwner) {\n return;\n }\n\n // If the first pending request is for default mode, reject all requests\n if (firstEntry.desiredMode === DEFAULT_MODE) {\n const allRequests = Array.from(state.pendingRequests.values());\n state.pendingRequests.clear();\n\n for (const request of allRequests) {\n mapModeBus.emit(MapModeEvents.changeDecision, {\n authId: request.authId,\n approved: false,\n owner: previousModeOwner,\n reason: 'Request rejected - already in requested mode',\n id: instanceId,\n } satisfies ModeChangeDecisionPayload);\n }\n } else {\n // Auto-accept the first pending request for a different mode\n approveRequestAndRejectOthers(\n instanceId,\n state,\n firstEntry,\n firstEntry.authId,\n previousModeOwner,\n 'Auto-accepted when mode owner returned to default',\n true,\n );\n }\n}\n\n/**\n * Handle authorization decision\n *\n * Processes approval/rejection decisions from mode owners. Only the current mode's owner\n * can make authorization decisions. If a decision comes from a non-owner, a warning is\n * logged and the decision is ignored to prevent unauthorized mode changes.\n *\n * @param instanceId - The unique identifier for this map instance\n * @param state - The mode state for this instance\n * @param payload - The authorization decision containing authId, approved status, and owner\n */\nfunction handleAuthorizationDecision(\n instanceId: UniqueId,\n state: MapModeState,\n payload: {\n approved: boolean;\n authId: string;\n owner: string;\n },\n): void {\n const { approved, authId, owner: decisionOwner } = payload;\n\n // Verify decision is from current mode's owner\n // Logs a warning if unauthorized component attempts to make decisions\n const currentModeOwner = state.modeOwners.get(state.mode);\n if (decisionOwner !== currentModeOwner) {\n console.warn(\n `[MapMode] Authorization decision from \"${decisionOwner}\" ignored - not the owner of mode \"${state.mode}\" (owner: ${currentModeOwner || 'none'})`,\n );\n return;\n }\n\n // Find the request with matching authId\n let matchingRequestOwner: string | null = null;\n let matchingRequest: PendingRequest | null = null;\n\n for (const [requestOwner, request] of state.pendingRequests.entries()) {\n if (request.authId === authId) {\n matchingRequestOwner = requestOwner;\n matchingRequest = request;\n break;\n }\n }\n\n if (!(matchingRequest && matchingRequestOwner)) {\n return;\n }\n\n if (approved) {\n approveRequestAndRejectOthers(\n instanceId,\n state,\n matchingRequest,\n authId,\n decisionOwner,\n '',\n false,\n );\n } else {\n state.pendingRequests.delete(matchingRequestOwner);\n }\n}\n\n/**\n * Handle mode change request logic\n */\nfunction handleModeChangeRequest(\n instanceId: UniqueId,\n state: MapModeState,\n desiredMode: string,\n requestOwner: string,\n): void {\n const desiredModeOwner = state.modeOwners.get(desiredMode);\n\n // Check if this request should be auto-accepted\n if (shouldAutoAcceptRequest(state, desiredMode, requestOwner)) {\n setMode(instanceId, state, desiredMode);\n\n // Store the desired mode's owner unless it's default\n if (desiredMode !== DEFAULT_MODE && !desiredModeOwner) {\n state.modeOwners.set(desiredMode, requestOwner);\n }\n\n // Clear requester's pending request since mode changed successfully\n state.pendingRequests.delete(requestOwner);\n return;\n }\n\n // Otherwise, send authorization request\n const authId = uuid();\n\n state.pendingRequests.set(requestOwner, {\n authId,\n desiredMode,\n currentMode: state.mode,\n requestOwner,\n });\n\n mapModeBus.emit(MapModeEvents.changeAuthorization, {\n authId,\n desiredMode,\n currentMode: state.mode,\n id: instanceId,\n });\n}\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 events fire.\n * This prevents creating N bus listeners for N React components.\n *\n * @param instanceId - The unique identifier for the map mode instance\n */\nfunction ensureBusListener(instanceId: UniqueId): void {\n if (busUnsubscribers.has(instanceId)) {\n return; // Already listening\n }\n\n const state = getOrCreateState(instanceId);\n\n // Listen for mode change requests\n const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {\n const { desiredMode, owner: requestOwner, id } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== instanceId || desiredMode === state.mode) {\n return;\n }\n\n handleModeChangeRequest(instanceId, state, desiredMode, requestOwner);\n });\n\n // Listen for authorization decisions\n const unsubDecision = mapModeBus.on(MapModeEvents.changeDecision, (event) => {\n const { id, approved, authId, owner } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== instanceId) {\n return;\n }\n\n handleAuthorizationDecision(instanceId, state, { approved, authId, owner });\n });\n\n // Listen for mode changes to handle pending requests\n const unsubChanged = mapModeBus.on(MapModeEvents.changed, (event) => {\n const { currentMode, previousMode, id } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== instanceId) {\n return;\n }\n\n // When mode owner changes to default mode, handle pending requests\n if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) {\n handlePendingRequestsOnDefaultMode(instanceId, state, previousMode);\n }\n });\n\n // Store composite cleanup function\n busUnsubscribers.set(instanceId, () => {\n unsubRequest();\n unsubDecision();\n unsubChanged();\n });\n}\n\n/**\n * Cleans up the bus listener if no React subscribers remain.\n *\n * @param instanceId - The unique identifier for the map mode instance\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 modeStore.delete(instanceId);\n componentSubscribers.delete(instanceId);\n subscriptionCache.delete(instanceId);\n snapshotCache.delete(instanceId);\n requestModeChangeCache.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 map mode state when the last subscriber unsubscribes.\n *\n * @param instanceId - The unique identifier for the map mode instance\n * @returns A subscription function for useSyncExternalStore\n */\nexport function getOrCreateSubscription(\n instanceId: UniqueId,\n): (onStoreChange: () => void) => () => void {\n const subscription =\n subscriptionCache.get(instanceId) ??\n ((onStoreChange: () => void) => {\n // Ensure state exists\n getOrCreateState(instanceId);\n\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 string returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.\n *\n * @param instanceId - The unique identifier for the map mode instance\n * @returns A snapshot function for useSyncExternalStore\n */\nexport function getOrCreateSnapshot(instanceId: UniqueId): () => string {\n const snapshot =\n snapshotCache.get(instanceId) ??\n (() => {\n const state = modeStore.get(instanceId);\n if (!state) {\n return DEFAULT_MODE;\n }\n return state.mode;\n });\n\n snapshotCache.set(instanceId, snapshot);\n\n return snapshot;\n}\n\n/**\n * Creates or retrieves a cached requestModeChange function for a given instanceId.\n * This maintains referential stability for the function reference.\n *\n * @param instanceId - The unique identifier for the map mode instance\n * @returns A requestModeChange function for this instance\n */\nexport function getOrCreateRequestModeChange(\n instanceId: UniqueId,\n): (desiredMode: string, requestOwner: string) => void {\n const requestModeChange =\n requestModeChangeCache.get(instanceId) ??\n ((desiredMode: string, requestOwner: string) => {\n const trimmedDesiredMode = desiredMode.trim();\n const trimmedRequestOwner = requestOwner.trim();\n\n if (!trimmedDesiredMode) {\n throw new Error('requestModeChange requires non-empty desiredMode');\n }\n if (!trimmedRequestOwner) {\n throw new Error('requestModeChange requires non-empty requestOwner');\n }\n\n mapModeBus.emit(MapModeEvents.changeRequest, {\n desiredMode: trimmedDesiredMode,\n owner: trimmedRequestOwner,\n id: instanceId,\n });\n });\n\n requestModeChangeCache.set(instanceId, requestModeChange);\n\n return requestModeChange;\n}\n\n/**\n * Get the owner of the current mode for a given map instance\n * @internal - For internal map-toolkit use only\n */\nexport function getCurrentModeOwner(instanceId: UniqueId): string | undefined {\n const state = modeStore.get(instanceId);\n if (!state) {\n return undefined;\n }\n return state.modeOwners.get(state.mode);\n}\n\n/**\n * Manually clear map mode 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 mode instance to clear\n *\n * @example\n * ```tsx\n * // Manual cleanup (rarely needed)\n * clearMapModeState('my-map-instance');\n * ```\n */\nexport function clearMapModeState(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 modeStore.delete(instanceId);\n componentSubscribers.delete(instanceId);\n subscriptionCache.delete(instanceId);\n snapshotCache.delete(instanceId);\n requestModeChangeCache.delete(instanceId);\n}\n"]}
@@ -12,15 +12,13 @@ type UseMapModeReturn = {
12
12
  /**
13
13
  * Hook to access the map mode state and actions.
14
14
  *
15
- * This hook uses `useSyncExternalStore` to subscribe to the external `MapModeStore`,
16
- * providing concurrent-safe mode state updates. The hybrid architecture separates:
17
- * - Map instance identity (from `MapContext` or parameter)
18
- * - Mode state management (from `MapModeStore` via `useSyncExternalStore`)
15
+ * This hook uses `useSyncExternalStore` to subscribe to map mode state changes,
16
+ * providing concurrent-safe mode state updates. Uses a fan-out pattern where
17
+ * a single bus listener per map instance notifies N React component subscribers.
19
18
  *
20
19
  * @param id - Optional map instance ID. If not provided, will use the ID from `MapContext`.
21
20
  * @returns The current map mode and requestModeChange function
22
21
  * @throws Error if no `id` is provided and hook is used outside of `MapProvider`
23
- * @throws Error if store doesn't exist for the given map ID
24
22
  *
25
23
  * @example
26
24
  * ```tsx
@@ -1,6 +1,7 @@
1
+ import 'client-only';
1
2
  import { useContext, useSyncExternalStore, useMemo } from 'react';
2
3
  import { MapContext } from '../deckgl/base-map/provider.js';
3
- import { getStore } from './store.js';
4
+ import { getOrCreateSubscription, getOrCreateSnapshot, getOrCreateRequestModeChange } from './store.js';
4
5
 
5
6
  function useMapMode(id) {
6
7
  const contextId = useContext(MapContext);
@@ -10,19 +11,16 @@ function useMapMode(id) {
10
11
  "useMapMode requires either an id parameter or to be used within a MapProvider"
11
12
  );
12
13
  }
13
- const store = getStore(actualId);
14
- if (!store) {
15
- throw new Error(
16
- `MapModeStore not found for map instance: ${actualId}. Ensure a store has been created for this map instance (e.g., via MapProvider or getOrCreateStore).`
17
- );
18
- }
19
- const mode = useSyncExternalStore(store.subscribe, store.getSnapshot);
14
+ const mode = useSyncExternalStore(
15
+ getOrCreateSubscription(actualId),
16
+ getOrCreateSnapshot(actualId)
17
+ );
20
18
  return useMemo(
21
19
  () => ({
22
20
  mode,
23
- requestModeChange: store.requestModeChange
21
+ requestModeChange: getOrCreateRequestModeChange(actualId)
24
22
  }),
25
- [mode, store]
23
+ [mode, actualId]
26
24
  );
27
25
  }
28
26
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/map-mode/use-map-mode.ts"],"names":[],"mappings":";;;;AAmEO,SAAS,WAAW,EAAA,EAAiC;AAC1D,EAAA,MAAM,SAAA,GAAY,WAAW,UAAU,CAAA;AACvC,EAAA,MAAM,WAAW,EAAA,IAAM,SAAA;AAEvB,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAGA,EAAA,MAAM,KAAA,GAAQ,SAAS,QAAQ,CAAA;AAE/B,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,4CAA4C,QAAQ,CAAA,oGAAA;AAAA,KACtD;AAAA,EACF;AAGA,EAAA,MAAM,IAAA,GAAO,oBAAA,CAAqB,KAAA,CAAM,SAAA,EAAW,MAAM,WAAW,CAAA;AAGpE,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,IAAA;AAAA,MACA,mBAAmB,KAAA,CAAM;AAAA,KAC3B,CAAA;AAAA,IACA,CAAC,MAAM,KAAK;AAAA,GACd;AACF","file":"use-map-mode.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\nimport { useContext, useMemo, useSyncExternalStore } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport { getStore } from './store';\nimport type { UniqueId } from '@accelint/core';\n\n/**\n * Return value for the useMapMode hook\n */\nexport type UseMapModeReturn = {\n /** The current active map mode */\n mode: string;\n /** Function to request a mode change with ownership */\n requestModeChange: (desiredMode: string, requestOwner: string) => void;\n};\n\n/**\n * Hook to access the map mode state and actions.\n *\n * This hook uses `useSyncExternalStore` to subscribe to the external `MapModeStore`,\n * providing concurrent-safe mode state updates. The hybrid architecture separates:\n * - Map instance identity (from `MapContext` or parameter)\n * - Mode state management (from `MapModeStore` via `useSyncExternalStore`)\n *\n * @param id - Optional map instance ID. If not provided, will use the ID from `MapContext`.\n * @returns The current map mode and requestModeChange function\n * @throws Error if no `id` is provided and hook is used outside of `MapProvider`\n * @throws Error if store doesn't exist for the given map ID\n *\n * @example\n * ```tsx\n * // Inside MapProvider (within BaseMap children) - uses context\n * // Only Deck.gl layer components can be children\n * function CustomDeckLayer() {\n * const { mode, requestModeChange } = useMapMode();\n *\n * const handleClick = (info: PickingInfo) => {\n * requestModeChange('editing', 'custom-layer-id');\n * };\n *\n * return <ScatterplotLayer onClick={handleClick} />;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Outside MapProvider - pass id directly\n * function ExternalControl({ mapId }: { mapId: UniqueId }) {\n * const { mode, requestModeChange } = useMapMode(mapId);\n *\n * return <button onClick={() => requestModeChange('default', 'external')}>\n * Reset to Default (current: {mode})\n * </button>;\n * }\n * ```\n */\nexport function useMapMode(id?: UniqueId): UseMapModeReturn {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useMapMode requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n // Get the store for this map instance\n const store = getStore(actualId);\n\n if (!store) {\n throw new Error(\n `MapModeStore not found for map instance: ${actualId}. Ensure a store has been created for this map instance (e.g., via MapProvider or getOrCreateStore).`,\n );\n }\n\n // Subscribe to store using useSyncExternalStore\n const mode = useSyncExternalStore(store.subscribe, store.getSnapshot);\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n mode,\n requestModeChange: store.requestModeChange,\n }),\n [mode, store],\n );\n}\n"]}
1
+ {"version":3,"sources":["../../src/map-mode/use-map-mode.ts"],"names":[],"mappings":";;;;;AAuEO,SAAS,WAAW,EAAA,EAAiC;AAC1D,EAAA,MAAM,SAAA,GAAY,WAAW,UAAU,CAAA;AACvC,EAAA,MAAM,WAAW,EAAA,IAAM,SAAA;AAEvB,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAGA,EAAA,MAAM,IAAA,GAAO,oBAAA;AAAA,IACX,wBAAwB,QAAQ,CAAA;AAAA,IAChC,oBAAoB,QAAQ;AAAA,GAC9B;AAGA,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,IAAA;AAAA,MACA,iBAAA,EAAmB,6BAA6B,QAAQ;AAAA,KAC1D,CAAA;AAAA,IACA,CAAC,MAAM,QAAQ;AAAA,GACjB;AACF","file":"use-map-mode.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 { useContext, useMemo, useSyncExternalStore } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport {\n getOrCreateRequestModeChange,\n getOrCreateSnapshot,\n getOrCreateSubscription,\n} from './store';\nimport type { UniqueId } from '@accelint/core';\n\n/**\n * Return value for the useMapMode hook\n */\nexport type UseMapModeReturn = {\n /** The current active map mode */\n mode: string;\n /** Function to request a mode change with ownership */\n requestModeChange: (desiredMode: string, requestOwner: string) => void;\n};\n\n/**\n * Hook to access the map mode state and actions.\n *\n * This hook uses `useSyncExternalStore` to subscribe to map mode state changes,\n * providing concurrent-safe mode state updates. Uses a fan-out pattern where\n * a single bus listener per map instance notifies N React component subscribers.\n *\n * @param id - Optional map instance ID. If not provided, will use the ID from `MapContext`.\n * @returns The current map mode and requestModeChange function\n * @throws Error if no `id` is provided and hook is used outside of `MapProvider`\n *\n * @example\n * ```tsx\n * // Inside MapProvider (within BaseMap children) - uses context\n * // Only Deck.gl layer components can be children\n * function CustomDeckLayer() {\n * const { mode, requestModeChange } = useMapMode();\n *\n * const handleClick = (info: PickingInfo) => {\n * requestModeChange('editing', 'custom-layer-id');\n * };\n *\n * return <ScatterplotLayer onClick={handleClick} />;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Outside MapProvider - pass id directly\n * function ExternalControl({ mapId }: { mapId: UniqueId }) {\n * const { mode, requestModeChange } = useMapMode(mapId);\n *\n * return <button onClick={() => requestModeChange('default', 'external')}>\n * Reset to Default (current: {mode})\n * </button>;\n * }\n * ```\n */\nexport function useMapMode(id?: UniqueId): UseMapModeReturn {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useMapMode requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n // Subscribe to store using useSyncExternalStore with fan-out pattern\n const mode = useSyncExternalStore(\n getOrCreateSubscription(actualId),\n getOrCreateSnapshot(actualId),\n );\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n mode,\n requestModeChange: getOrCreateRequestModeChange(actualId),\n }),\n [mode, actualId],\n );\n}\n"]}
@@ -1 +1 @@
1
- {"inputs":{"src/deckgl/index.ts":{"bytes":1037,"imports":[],"format":"esm"},"src/decorators/deckgl.tsx":{"bytes":1096,"imports":[],"format":"esm"},"src/map-mode/events.ts":{"bytes":1961,"imports":[],"format":"esm"},"src/map-mode/index.ts":{"bytes":1142,"imports":[],"format":"esm"},"src/map-mode/store.ts":{"bytes":14668,"imports":[],"format":"esm"},"src/map-mode/types.ts":{"bytes":3586,"imports":[],"format":"esm"},"src/map-mode/use-map-mode.ts":{"bytes":3446,"imports":[],"format":"esm"},"src/deckgl/base-map/constants.ts":{"bytes":1129,"imports":[],"format":"esm"},"src/deckgl/base-map/events.ts":{"bytes":809,"imports":[],"format":"esm"},"src/deckgl/base-map/index.tsx":{"bytes":7788,"imports":[],"format":"esm"},"src/deckgl/base-map/provider.tsx":{"bytes":6766,"imports":[],"format":"esm"},"src/deckgl/base-map/types.ts":{"bytes":3327,"imports":[],"format":"esm"},"src/deckgl/symbol-layer/fiber.ts":{"bytes":1020,"imports":[],"format":"esm"},"src/deckgl/symbol-layer/index.ts":{"bytes":4786,"imports":[],"format":"esm"},"src/maplibre/constants.ts":{"bytes":819,"imports":[],"format":"esm"},"src/maplibre/index.ts":{"bytes":750,"imports":[],"format":"esm"},"src/deckgl/text-layer/character-sets.ts":{"bytes":2052,"imports":[],"format":"esm"},"src/deckgl/text-layer/default-settings.ts":{"bytes":1198,"imports":[],"format":"esm"},"src/deckgl/text-layer/fiber.ts":{"bytes":1516,"imports":[],"format":"esm"},"src/deckgl/text-layer/index.ts":{"bytes":2455,"imports":[],"format":"esm"},"src/maplibre/hooks/use-maplibre.ts":{"bytes":3030,"imports":[],"format":"esm"}},"outputs":{"dist/deckgl/text-layer/character-sets.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3258},"dist/deckgl/text-layer/character-sets.js":{"imports":[],"exports":["CHARACTER_SETS"],"entryPoint":"src/deckgl/text-layer/character-sets.ts","inputs":{"src/deckgl/text-layer/character-sets.ts":{"bytesInOutput":1507}},"bytes":1536},"dist/deckgl/text-layer/default-settings.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1712},"dist/deckgl/text-layer/default-settings.js":{"imports":[],"exports":["defaultSettings"],"entryPoint":"src/deckgl/text-layer/default-settings.ts","inputs":{"src/deckgl/text-layer/default-settings.ts":{"bytesInOutput":389}},"bytes":419},"dist/deckgl/text-layer/fiber.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1761},"dist/deckgl/text-layer/fiber.js":{"imports":[{"path":"@deckgl-fiber-renderer/dom","kind":"import-statement","external":true},{"path":"./index","kind":"import-statement","external":true}],"exports":[],"entryPoint":"src/deckgl/text-layer/fiber.ts","inputs":{"src/deckgl/text-layer/fiber.ts":{"bytesInOutput":113}},"bytes":113},"dist/deckgl/text-layer/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3088},"dist/deckgl/text-layer/index.js":{"imports":[{"path":"@deck.gl/layers","kind":"import-statement","external":true},{"path":"./character-sets.js","kind":"import-statement","external":true},{"path":"./default-settings.js","kind":"import-statement","external":true}],"exports":["TextLayer"],"entryPoint":"src/deckgl/text-layer/index.ts","inputs":{"src/deckgl/text-layer/index.ts":{"bytesInOutput":832}},"bytes":856},"dist/maplibre/hooks/use-maplibre.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3948},"dist/maplibre/hooks/use-maplibre.js":{"imports":[{"path":"maplibre-gl","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true}],"exports":["useMapLibre"],"entryPoint":"src/maplibre/hooks/use-maplibre.ts","inputs":{"src/maplibre/hooks/use-maplibre.ts":{"bytesInOutput":913}},"bytes":939},"dist/deckgl/base-map/events.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1061},"dist/deckgl/base-map/events.js":{"imports":[],"exports":["MapEvents","MapEventsNamespace"],"entryPoint":"src/deckgl/base-map/events.ts","inputs":{"src/deckgl/base-map/events.ts":{"bytesInOutput":136}},"bytes":182},"dist/deckgl/base-map/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":9708},"dist/deckgl/base-map/index.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"client-only","kind":"import-statement","external":true},{"path":"@accelint/bus/react","kind":"import-statement","external":true},{"path":"@deckgl-fiber-renderer/dom","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../../maplibre/constants","kind":"import-statement","external":true},{"path":"../../maplibre/hooks/use-maplibre","kind":"import-statement","external":true},{"path":"./constants","kind":"import-statement","external":true},{"path":"./events","kind":"import-statement","external":true},{"path":"./provider","kind":"import-statement","external":true}],"exports":["BaseMap"],"entryPoint":"src/deckgl/base-map/index.tsx","inputs":{"src/deckgl/base-map/index.tsx":{"bytesInOutput":2484}},"bytes":2520},"dist/deckgl/base-map/provider.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":7459},"dist/deckgl/base-map/provider.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"client-only","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../../map-mode/store","kind":"import-statement","external":true}],"exports":["MapContext","MapProvider"],"entryPoint":"src/deckgl/base-map/provider.tsx","inputs":{"src/deckgl/base-map/provider.tsx":{"bytesInOutput":573}},"bytes":627},"dist/deckgl/base-map/types.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":93},"dist/deckgl/base-map/types.js":{"imports":[],"exports":[],"entryPoint":"src/deckgl/base-map/types.ts","inputs":{"src/deckgl/base-map/types.ts":{"bytesInOutput":0}},"bytes":0},"dist/deckgl/symbol-layer/fiber.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1248},"dist/deckgl/symbol-layer/fiber.js":{"imports":[{"path":"@deckgl-fiber-renderer/dom","kind":"import-statement","external":true},{"path":"./index","kind":"import-statement","external":true}],"exports":[],"entryPoint":"src/deckgl/symbol-layer/fiber.ts","inputs":{"src/deckgl/symbol-layer/fiber.ts":{"bytesInOutput":117}},"bytes":117},"dist/deckgl/symbol-layer/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":6569},"dist/deckgl/symbol-layer/index.js":{"imports":[{"path":"@deck.gl/layers","kind":"import-statement","external":true},{"path":"milsymbol","kind":"import-statement","external":true}],"exports":["SymbolLayer"],"entryPoint":"src/deckgl/symbol-layer/index.ts","inputs":{"src/deckgl/symbol-layer/index.ts":{"bytesInOutput":2593}},"bytes":2619},"dist/maplibre/constants.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1102},"dist/maplibre/constants.js":{"imports":[],"exports":["INITIAL_VIEW_STATE"],"entryPoint":"src/maplibre/constants.ts","inputs":{"src/maplibre/constants.ts":{"bytesInOutput":141}},"bytes":174},"dist/maplibre/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":927},"dist/maplibre/index.js":{"imports":[{"path":"./constants","kind":"import-statement","external":true},{"path":"./hooks/use-maplibre","kind":"import-statement","external":true}],"exports":["INITIAL_VIEW_STATE","useMapLibre"],"entryPoint":"src/maplibre/index.ts","inputs":{"src/maplibre/index.ts":{"bytesInOutput":102}},"bytes":150},"dist/deckgl/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1268},"dist/deckgl/index.js":{"imports":[{"path":"./base-map","kind":"import-statement","external":true},{"path":"./base-map/constants","kind":"import-statement","external":true},{"path":"./base-map/events","kind":"import-statement","external":true},{"path":"./symbol-layer","kind":"import-statement","external":true}],"exports":["BASE_MAP_STYLE","BaseMap","MapEvents","MapEventsNamespace","PARAMETERS","SymbolLayer"],"entryPoint":"src/deckgl/index.ts","inputs":{"src/deckgl/index.ts":{"bytesInOutput":218}},"bytes":322},"dist/decorators/deckgl.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1436},"dist/decorators/deckgl.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"@accelint/core","kind":"import-statement","external":true},{"path":"../deckgl/base-map","kind":"import-statement","external":true}],"exports":["withDeckGL"],"entryPoint":"src/decorators/deckgl.tsx","inputs":{"src/decorators/deckgl.tsx":{"bytesInOutput":351}},"bytes":376},"dist/map-mode/events.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":2316},"dist/map-mode/events.js":{"imports":[],"exports":["MapModeEvents","MapModeEventsNamespace"],"entryPoint":"src/map-mode/events.ts","inputs":{"src/map-mode/events.ts":{"bytesInOutput":570}},"bytes":624},"dist/map-mode/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1396},"dist/map-mode/index.js":{"imports":[{"path":"./events","kind":"import-statement","external":true},{"path":"./store","kind":"import-statement","external":true},{"path":"./use-map-mode","kind":"import-statement","external":true}],"exports":["MapModeEvents","MapModeEventsNamespace","MapModeStore","destroyStore","getOrCreateStore","getStore","useMapMode"],"entryPoint":"src/map-mode/index.ts","inputs":{"src/map-mode/index.ts":{"bytesInOutput":201}},"bytes":333},"dist/map-mode/store.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":20593},"dist/map-mode/store.js":{"imports":[{"path":"@accelint/bus","kind":"import-statement","external":true},{"path":"@accelint/core","kind":"import-statement","external":true},{"path":"./events","kind":"import-statement","external":true}],"exports":["MapModeStore","destroyStore","getOrCreateStore","getStore"],"entryPoint":"src/map-mode/store.ts","inputs":{"src/map-mode/store.ts":{"bytesInOutput":10062}},"bytes":10137},"dist/map-mode/types.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":93},"dist/map-mode/types.js":{"imports":[],"exports":[],"entryPoint":"src/map-mode/types.ts","inputs":{"src/map-mode/types.ts":{"bytesInOutput":0}},"bytes":0},"dist/map-mode/use-map-mode.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":4133},"dist/map-mode/use-map-mode.js":{"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"../deckgl/base-map/provider","kind":"import-statement","external":true},{"path":"./store","kind":"import-statement","external":true}],"exports":["useMapMode"],"entryPoint":"src/map-mode/use-map-mode.ts","inputs":{"src/map-mode/use-map-mode.ts":{"bytesInOutput":847}},"bytes":872},"dist/deckgl/base-map/constants.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1523},"dist/deckgl/base-map/constants.js":{"imports":[],"exports":["BASE_MAP_STYLE","PARAMETERS"],"entryPoint":"src/deckgl/base-map/constants.ts","inputs":{"src/deckgl/base-map/constants.ts":{"bytesInOutput":442}},"bytes":485}}}
1
+ {"inputs":{"src/cursor-coordinates/index.ts":{"bytes":1035,"imports":[],"format":"esm"},"src/cursor-coordinates/use-cursor-coordinates.ts":{"bytes":13198,"imports":[],"format":"esm"},"src/deckgl/index.ts":{"bytes":1037,"imports":[],"format":"esm"},"src/decorators/deckgl.tsx":{"bytes":1101,"imports":[],"format":"esm"},"src/map-mode/events.ts":{"bytes":1961,"imports":[],"format":"esm"},"src/map-mode/index.ts":{"bytes":1117,"imports":[],"format":"esm"},"src/map-mode/store.ts":{"bytes":17572,"imports":[],"format":"esm"},"src/map-mode/types.ts":{"bytes":3586,"imports":[],"format":"esm"},"src/map-mode/use-map-mode.ts":{"bytes":3222,"imports":[],"format":"esm"},"src/maplibre/constants.ts":{"bytes":819,"imports":[],"format":"esm"},"src/maplibre/index.ts":{"bytes":750,"imports":[],"format":"esm"},"src/viewport/constants.ts":{"bytes":773,"imports":[],"format":"esm"},"src/viewport/index.tsx":{"bytes":1012,"imports":[],"format":"esm"},"src/viewport/types.ts":{"bytes":1120,"imports":[],"format":"esm"},"src/viewport/use-viewport-state.ts":{"bytes":9491,"imports":[],"format":"esm"},"src/viewport/utils.ts":{"bytes":3891,"imports":[],"format":"esm"},"src/viewport/viewport-size.tsx":{"bytes":2070,"imports":[],"format":"esm"},"src/deckgl/base-map/constants.ts":{"bytes":1129,"imports":[],"format":"esm"},"src/deckgl/base-map/events.ts":{"bytes":855,"imports":[],"format":"esm"},"src/deckgl/base-map/index.tsx":{"bytes":9952,"imports":[],"format":"esm"},"src/deckgl/base-map/provider.tsx":{"bytes":6192,"imports":[],"format":"esm"},"src/deckgl/base-map/types.ts":{"bytes":4067,"imports":[],"format":"esm"},"src/deckgl/symbol-layer/fiber.ts":{"bytes":1020,"imports":[],"format":"esm"},"src/deckgl/symbol-layer/index.ts":{"bytes":4786,"imports":[],"format":"esm"},"src/deckgl/text-layer/character-sets.ts":{"bytes":2340,"imports":[],"format":"esm"},"src/deckgl/text-layer/default-settings.ts":{"bytes":1222,"imports":[],"format":"esm"},"src/deckgl/text-layer/fiber.ts":{"bytes":1516,"imports":[],"format":"esm"},"src/deckgl/text-layer/index.ts":{"bytes":2455,"imports":[],"format":"esm"},"src/maplibre/hooks/use-maplibre.ts":{"bytes":3030,"imports":[],"format":"esm"}},"outputs":{"dist/deckgl/text-layer/character-sets.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3553},"dist/deckgl/text-layer/character-sets.js":{"imports":[],"exports":["CHARACTER_SETS"],"entryPoint":"src/deckgl/text-layer/character-sets.ts","inputs":{"src/deckgl/text-layer/character-sets.ts":{"bytesInOutput":1507}},"bytes":1536},"dist/deckgl/text-layer/default-settings.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1736},"dist/deckgl/text-layer/default-settings.js":{"imports":[],"exports":["defaultSettings"],"entryPoint":"src/deckgl/text-layer/default-settings.ts","inputs":{"src/deckgl/text-layer/default-settings.ts":{"bytesInOutput":389}},"bytes":419},"dist/deckgl/text-layer/fiber.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1761},"dist/deckgl/text-layer/fiber.js":{"imports":[{"path":"@deckgl-fiber-renderer/dom","kind":"import-statement","external":true},{"path":"./index","kind":"import-statement","external":true}],"exports":[],"entryPoint":"src/deckgl/text-layer/fiber.ts","inputs":{"src/deckgl/text-layer/fiber.ts":{"bytesInOutput":113}},"bytes":113},"dist/deckgl/text-layer/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3088},"dist/deckgl/text-layer/index.js":{"imports":[{"path":"@deck.gl/layers","kind":"import-statement","external":true},{"path":"./character-sets.js","kind":"import-statement","external":true},{"path":"./default-settings.js","kind":"import-statement","external":true}],"exports":["TextLayer"],"entryPoint":"src/deckgl/text-layer/index.ts","inputs":{"src/deckgl/text-layer/index.ts":{"bytesInOutput":832}},"bytes":856},"dist/maplibre/hooks/use-maplibre.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3948},"dist/maplibre/hooks/use-maplibre.js":{"imports":[{"path":"maplibre-gl","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true}],"exports":["useMapLibre"],"entryPoint":"src/maplibre/hooks/use-maplibre.ts","inputs":{"src/maplibre/hooks/use-maplibre.ts":{"bytesInOutput":913}},"bytes":939},"dist/viewport/viewport-size.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":2534},"dist/viewport/viewport-size.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"./use-viewport-state","kind":"import-statement","external":true},{"path":"./utils","kind":"import-statement","external":true}],"exports":["ViewportSize"],"entryPoint":"src/viewport/viewport-size.tsx","inputs":{"src/viewport/viewport-size.tsx":{"bytesInOutput":404}},"bytes":431},"dist/deckgl/base-map/constants.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1523},"dist/deckgl/base-map/constants.js":{"imports":[],"exports":["BASE_MAP_STYLE","PARAMETERS"],"entryPoint":"src/deckgl/base-map/constants.ts","inputs":{"src/deckgl/base-map/constants.ts":{"bytesInOutput":442}},"bytes":485},"dist/deckgl/base-map/events.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1136},"dist/deckgl/base-map/events.js":{"imports":[],"exports":["MapEvents","MapEventsNamespace"],"entryPoint":"src/deckgl/base-map/events.ts","inputs":{"src/deckgl/base-map/events.ts":{"bytesInOutput":182}},"bytes":228},"dist/deckgl/base-map/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":12983},"dist/deckgl/base-map/index.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"client-only","kind":"import-statement","external":true},{"path":"@accelint/bus/react","kind":"import-statement","external":true},{"path":"@deckgl-fiber-renderer/dom","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../../maplibre/constants","kind":"import-statement","external":true},{"path":"../../maplibre/hooks/use-maplibre","kind":"import-statement","external":true},{"path":"./constants","kind":"import-statement","external":true},{"path":"./events","kind":"import-statement","external":true},{"path":"./provider","kind":"import-statement","external":true}],"exports":["BaseMap"],"entryPoint":"src/deckgl/base-map/index.tsx","inputs":{"src/deckgl/base-map/index.tsx":{"bytesInOutput":3979}},"bytes":4015},"dist/deckgl/base-map/provider.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":6757},"dist/deckgl/base-map/provider.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"client-only","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../../map-mode/store","kind":"import-statement","external":true}],"exports":["MapContext","MapProvider"],"entryPoint":"src/deckgl/base-map/provider.tsx","inputs":{"src/deckgl/base-map/provider.tsx":{"bytesInOutput":417}},"bytes":471},"dist/deckgl/base-map/types.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":93},"dist/deckgl/base-map/types.js":{"imports":[],"exports":[],"entryPoint":"src/deckgl/base-map/types.ts","inputs":{"src/deckgl/base-map/types.ts":{"bytesInOutput":0}},"bytes":0},"dist/deckgl/symbol-layer/fiber.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1248},"dist/deckgl/symbol-layer/fiber.js":{"imports":[{"path":"@deckgl-fiber-renderer/dom","kind":"import-statement","external":true},{"path":"./index","kind":"import-statement","external":true}],"exports":[],"entryPoint":"src/deckgl/symbol-layer/fiber.ts","inputs":{"src/deckgl/symbol-layer/fiber.ts":{"bytesInOutput":117}},"bytes":117},"dist/deckgl/symbol-layer/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":6569},"dist/deckgl/symbol-layer/index.js":{"imports":[{"path":"@deck.gl/layers","kind":"import-statement","external":true},{"path":"milsymbol","kind":"import-statement","external":true}],"exports":["SymbolLayer"],"entryPoint":"src/deckgl/symbol-layer/index.ts","inputs":{"src/deckgl/symbol-layer/index.ts":{"bytesInOutput":2593}},"bytes":2619},"dist/map-mode/use-map-mode.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":3877},"dist/map-mode/use-map-mode.js":{"imports":[{"path":"client-only","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../deckgl/base-map/provider","kind":"import-statement","external":true},{"path":"./store","kind":"import-statement","external":true}],"exports":["useMapMode"],"entryPoint":"src/map-mode/use-map-mode.ts","inputs":{"src/map-mode/use-map-mode.ts":{"bytesInOutput":756}},"bytes":795},"dist/maplibre/constants.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1102},"dist/maplibre/constants.js":{"imports":[],"exports":["INITIAL_VIEW_STATE"],"entryPoint":"src/maplibre/constants.ts","inputs":{"src/maplibre/constants.ts":{"bytesInOutput":141}},"bytes":174},"dist/maplibre/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":927},"dist/maplibre/index.js":{"imports":[{"path":"./constants","kind":"import-statement","external":true},{"path":"./hooks/use-maplibre","kind":"import-statement","external":true}],"exports":["INITIAL_VIEW_STATE","useMapLibre"],"entryPoint":"src/maplibre/index.ts","inputs":{"src/maplibre/index.ts":{"bytesInOutput":102}},"bytes":150},"dist/viewport/constants.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1020},"dist/viewport/constants.js":{"imports":[],"exports":["UNIT_MAP"],"entryPoint":"src/viewport/constants.ts","inputs":{"src/viewport/constants.ts":{"bytesInOutput":108}},"bytes":131},"dist/viewport/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1254},"dist/viewport/index.js":{"imports":[{"path":"./constants","kind":"import-statement","external":true},{"path":"./use-viewport-state","kind":"import-statement","external":true},{"path":"./utils","kind":"import-statement","external":true},{"path":"./viewport-size","kind":"import-statement","external":true}],"exports":["UNIT_MAP","ViewportSize","clearViewportState","getViewportSize","useViewportState"],"entryPoint":"src/viewport/index.tsx","inputs":{"src/viewport/index.tsx":{"bytesInOutput":212}},"bytes":312},"dist/viewport/types.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":93},"dist/viewport/types.js":{"imports":[],"exports":[],"entryPoint":"src/viewport/types.ts","inputs":{"src/viewport/types.ts":{"bytesInOutput":0}},"bytes":0},"dist/viewport/use-viewport-state.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":12064},"dist/viewport/use-viewport-state.js":{"imports":[{"path":"@accelint/bus","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../deckgl/base-map/events","kind":"import-statement","external":true}],"exports":["clearViewportState","useViewportState"],"entryPoint":"src/viewport/use-viewport-state.ts","inputs":{"src/viewport/use-viewport-state.ts":{"bytesInOutput":3400}},"bytes":3453},"dist/viewport/utils.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":5100},"dist/viewport/utils.js":{"imports":[{"path":"./constants","kind":"import-statement","external":true}],"exports":["getViewportSize"],"entryPoint":"src/viewport/utils.ts","inputs":{"src/viewport/utils.ts":{"bytesInOutput":1401}},"bytes":1431},"dist/cursor-coordinates/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1234},"dist/cursor-coordinates/index.js":{"imports":[{"path":"./use-cursor-coordinates","kind":"import-statement","external":true}],"exports":["clearCursorCoordinateState","useCursorCoordinates"],"entryPoint":"src/cursor-coordinates/index.ts","inputs":{"src/cursor-coordinates/index.ts":{"bytesInOutput":97}},"bytes":162},"dist/cursor-coordinates/use-cursor-coordinates.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":17042},"dist/cursor-coordinates/use-cursor-coordinates.js":{"imports":[{"path":"client-only","kind":"import-statement","external":true},{"path":"@accelint/bus","kind":"import-statement","external":true},{"path":"@accelint/geo","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"../deckgl/base-map/events","kind":"import-statement","external":true},{"path":"../deckgl/base-map/provider","kind":"import-statement","external":true}],"exports":["clearCursorCoordinateState","useCursorCoordinates"],"entryPoint":"src/cursor-coordinates/use-cursor-coordinates.ts","inputs":{"src/cursor-coordinates/use-cursor-coordinates.ts":{"bytesInOutput":5069}},"bytes":5148},"dist/deckgl/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1268},"dist/deckgl/index.js":{"imports":[{"path":"./base-map","kind":"import-statement","external":true},{"path":"./base-map/constants","kind":"import-statement","external":true},{"path":"./base-map/events","kind":"import-statement","external":true},{"path":"./symbol-layer","kind":"import-statement","external":true}],"exports":["BASE_MAP_STYLE","BaseMap","MapEvents","MapEventsNamespace","PARAMETERS","SymbolLayer"],"entryPoint":"src/deckgl/index.ts","inputs":{"src/deckgl/index.ts":{"bytesInOutput":218}},"bytes":322},"dist/decorators/deckgl.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1441},"dist/decorators/deckgl.js":{"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"@accelint/core","kind":"import-statement","external":true},{"path":"../deckgl/base-map","kind":"import-statement","external":true}],"exports":["withDeckGL"],"entryPoint":"src/decorators/deckgl.tsx","inputs":{"src/decorators/deckgl.tsx":{"bytesInOutput":351}},"bytes":376},"dist/map-mode/events.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":2316},"dist/map-mode/events.js":{"imports":[],"exports":["MapModeEvents","MapModeEventsNamespace"],"entryPoint":"src/map-mode/events.ts","inputs":{"src/map-mode/events.ts":{"bytesInOutput":570}},"bytes":624},"dist/map-mode/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":1336},"dist/map-mode/index.js":{"imports":[{"path":"./events","kind":"import-statement","external":true},{"path":"./store","kind":"import-statement","external":true},{"path":"./use-map-mode","kind":"import-statement","external":true}],"exports":["MapModeEvents","MapModeEventsNamespace","clearMapModeState","getCurrentModeOwner","useMapMode"],"entryPoint":"src/map-mode/index.ts","inputs":{"src/map-mode/index.ts":{"bytesInOutput":177}},"bytes":289},"dist/map-mode/store.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":24245},"dist/map-mode/store.js":{"imports":[{"path":"@accelint/bus","kind":"import-statement","external":true},{"path":"@accelint/core","kind":"import-statement","external":true},{"path":"./events","kind":"import-statement","external":true}],"exports":["clearMapModeState","getCurrentModeOwner","getOrCreateRequestModeChange","getOrCreateSnapshot","getOrCreateSubscription"],"entryPoint":"src/map-mode/store.ts","inputs":{"src/map-mode/store.ts":{"bytesInOutput":9557}},"bytes":9694},"dist/map-mode/types.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":93},"dist/map-mode/types.js":{"imports":[],"exports":[],"entryPoint":"src/map-mode/types.ts","inputs":{"src/map-mode/types.ts":{"bytesInOutput":0}},"bytes":0}}}
@@ -0,0 +1,9 @@
1
+ declare const UNIT_MAP: {
2
+ readonly km: "kilometers";
3
+ readonly m: "meters";
4
+ readonly nm: "nauticalmiles";
5
+ readonly mi: "miles";
6
+ readonly ft: "feet";
7
+ };
8
+
9
+ export { UNIT_MAP };
@@ -0,0 +1,11 @@
1
+ const UNIT_MAP = {
2
+ km: "kilometers",
3
+ m: "meters",
4
+ nm: "nauticalmiles",
5
+ mi: "miles",
6
+ ft: "feet"
7
+ };
8
+
9
+ export { UNIT_MAP };
10
+ //# sourceMappingURL=constants.js.map
11
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/viewport/constants.ts"],"names":[],"mappings":"AAYO,MAAM,QAAA,GAAW;AAAA,EACtB,EAAA,EAAI,YAAA;AAAA,EACJ,CAAA,EAAG,QAAA;AAAA,EACH,EAAA,EAAI,eAAA;AAAA,EACJ,EAAA,EAAI,OAAA;AAAA,EACJ,EAAA,EAAI;AACN","file":"constants.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 UNIT_MAP = {\n km: 'kilometers',\n m: 'meters',\n nm: 'nauticalmiles',\n mi: 'miles',\n ft: 'feet',\n} as const;\n"]}
@@ -0,0 +1,13 @@
1
+ export { UNIT_MAP } from './constants.js';
2
+ export { UseViewportStateProps, clearViewportState, useViewportState } from './use-viewport-state.js';
3
+ export { getViewportSize } from './utils.js';
4
+ export { ViewportSize, ViewportSizeProps } from './viewport-size.js';
5
+ export { GeoCoordinate, GetViewportSizeArgs, SupportedDistanceUnit } from './types.js';
6
+ import 'react';
7
+ import '@accelint/core';
8
+ import '../deckgl/base-map/types.js';
9
+ import '@accelint/bus';
10
+ import '@deck.gl/core';
11
+ import 'mjolnir.js';
12
+ import '../deckgl/base-map/events.js';
13
+ import 'react/jsx-runtime';
@@ -0,0 +1,6 @@
1
+ export { UNIT_MAP } from './constants.js';
2
+ export { clearViewportState, useViewportState } from './use-viewport-state.js';
3
+ export { getViewportSize } from './utils.js';
4
+ export { ViewportSize } from './viewport-size.js';
5
+ //# sourceMappingURL=index.js.map
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
@@ -0,0 +1,22 @@
1
+ import { Bounds } from '../deckgl/base-map/types.js';
2
+ import { UNIT_MAP } from './constants.js';
3
+ import '@accelint/bus';
4
+ import '@accelint/core';
5
+ import '@deck.gl/core';
6
+ import 'mjolnir.js';
7
+ import '../deckgl/base-map/events.js';
8
+
9
+ type SupportedDistanceUnit = keyof typeof UNIT_MAP;
10
+ type GetViewportSizeArgs = {
11
+ bounds: Bounds;
12
+ zoom: number;
13
+ /** Viewport width in pixels */
14
+ width: number;
15
+ /** Viewport height in pixels */
16
+ height: number;
17
+ unit?: SupportedDistanceUnit;
18
+ formatter?: Intl.NumberFormat;
19
+ };
20
+ type GeoCoordinate = [longitude: number, latitude: number];
21
+
22
+ export type { GeoCoordinate, GetViewportSizeArgs, SupportedDistanceUnit };
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=types.js.map
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"types.js"}
@@ -0,0 +1,85 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { UniqueId } from '@accelint/core';
3
+ import { MapViewportPayload } from '../deckgl/base-map/types.js';
4
+ import '@accelint/bus';
5
+ import '@deck.gl/core';
6
+ import 'mjolnir.js';
7
+ import '../deckgl/base-map/events.js';
8
+
9
+ type UseViewportStateProps = {
10
+ instanceId: UniqueId;
11
+ subscribe?: Parameters<typeof useSyncExternalStore<MapViewportPayload>>[0];
12
+ getSnapshot?: Parameters<typeof useSyncExternalStore<MapViewportPayload>>[1];
13
+ getServerSnapshot?: Parameters<typeof useSyncExternalStore<MapViewportPayload>>[2];
14
+ };
15
+ /**
16
+ * Hook to subscribe to viewport state changes for a specific view.
17
+ *
18
+ * By default, subscribes to MapEvents.viewport events from the event bus and
19
+ * automatically cleans up when all subscribers unmount. For advanced use cases,
20
+ * custom subscribe/getSnapshot functions can be provided.
21
+ *
22
+ * A thin wrapper around [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore).
23
+ *
24
+ * @param instanceId - Unique identifier for the viewport to track
25
+ * @param subscribe - Optional custom subscription function
26
+ * @param getSnapshot - Optional custom snapshot getter
27
+ * @param getServerSnapshot - Optional server-side snapshot getter
28
+ * @returns Current viewport state including bounds, latitude, longitude, zoom
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function MapInfo({ instanceId }) {
33
+ * const { bounds, latitude, longitude, zoom } = useViewportState({
34
+ * instanceId
35
+ * });
36
+ *
37
+ * return (
38
+ * <div>
39
+ * Lat: {latitude?.toFixed(2)}, Lon: {longitude?.toFixed(2)}, Zoom: {zoom}
40
+ * </div>
41
+ * );
42
+ * }
43
+ * ```
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * // With custom subscribe/getSnapshot for advanced use cases
48
+ * function CustomMapInfo() {
49
+ * const customSubscribe = (onStoreChange) => {
50
+ * // Custom subscription logic
51
+ * return () => { // cleanup }
52
+ * }
53
+ *
54
+ * const customGetSnapshot = () => {
55
+ * // Custom snapshot logic
56
+ * return { latitude: 0, longitude: 0, zoom: 1 };
57
+ * };
58
+ *
59
+ * const viewState = useViewportState({
60
+ * instanceId: 'some-uuid',
61
+ * subscribe: customSubscribe,
62
+ * getSnapshot: customGetSnapshot,
63
+ * });
64
+ *
65
+ * return <div>Custom viewport state</div>;
66
+ * }
67
+ * ```
68
+ */
69
+ declare function useViewportState({ instanceId, subscribe, getSnapshot, getServerSnapshot, }: UseViewportStateProps): MapViewportPayload;
70
+ /**
71
+ * Manually clear viewport state for a specific instanceId.
72
+ * This is typically not needed as cleanup happens automatically when all subscribers unmount.
73
+ * Use this only in advanced scenarios where manual cleanup is required.
74
+ *
75
+ * @param instanceId - The unique identifier for the viewport to clear
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * // Manual cleanup (rarely needed)
80
+ * clearViewportState('my-map-instance');
81
+ * ```
82
+ */
83
+ declare function clearViewportState(instanceId: UniqueId): void;
84
+
85
+ export { type UseViewportStateProps, clearViewportState, useViewportState };
@@ -0,0 +1,109 @@
1
+ import { Broadcast } from '@accelint/bus';
2
+ import { useSyncExternalStore } from 'react';
3
+ import { MapEvents } from '../deckgl/base-map/events.js';
4
+
5
+ const bus = Broadcast.getInstance();
6
+ const viewportStore = /* @__PURE__ */ new Map();
7
+ const componentSubscribers = /* @__PURE__ */ new Map();
8
+ const busUnsubscribers = /* @__PURE__ */ new Map();
9
+ const subscriptionCache = /* @__PURE__ */ new Map();
10
+ const snapshotCache = /* @__PURE__ */ new Map();
11
+ const fallbackCache = /* @__PURE__ */ new Map();
12
+ function ensureBusListener(instanceId) {
13
+ if (busUnsubscribers.has(instanceId)) {
14
+ return;
15
+ }
16
+ const unsub = bus.on(MapEvents.viewport, ({ payload }) => {
17
+ if (instanceId === payload.id) {
18
+ viewportStore.set(instanceId, payload);
19
+ const subscribers = componentSubscribers.get(instanceId);
20
+ if (subscribers) {
21
+ for (const onStoreChange of subscribers) {
22
+ onStoreChange();
23
+ }
24
+ }
25
+ }
26
+ });
27
+ busUnsubscribers.set(instanceId, unsub);
28
+ }
29
+ function cleanupBusListenerIfNeeded(instanceId) {
30
+ const subscribers = componentSubscribers.get(instanceId);
31
+ if (!subscribers || subscribers.size === 0) {
32
+ const unsub = busUnsubscribers.get(instanceId);
33
+ if (unsub) {
34
+ unsub();
35
+ busUnsubscribers.delete(instanceId);
36
+ }
37
+ viewportStore.delete(instanceId);
38
+ componentSubscribers.delete(instanceId);
39
+ subscriptionCache.delete(instanceId);
40
+ snapshotCache.delete(instanceId);
41
+ fallbackCache.delete(instanceId);
42
+ }
43
+ }
44
+ function getOrCreateSubscription(instanceId) {
45
+ const subscription = subscriptionCache.get(instanceId) ?? ((onStoreChange) => {
46
+ ensureBusListener(instanceId);
47
+ let subscriberSet = componentSubscribers.get(instanceId);
48
+ if (!subscriberSet) {
49
+ subscriberSet = /* @__PURE__ */ new Set();
50
+ componentSubscribers.set(instanceId, subscriberSet);
51
+ }
52
+ subscriberSet.add(onStoreChange);
53
+ return () => {
54
+ const currentSubscriberSet = componentSubscribers.get(instanceId);
55
+ if (currentSubscriberSet) {
56
+ currentSubscriberSet.delete(onStoreChange);
57
+ }
58
+ cleanupBusListenerIfNeeded(instanceId);
59
+ };
60
+ });
61
+ subscriptionCache.set(instanceId, subscription);
62
+ return subscription;
63
+ }
64
+ function getOrCreateSnapshot(instanceId) {
65
+ const fallback = fallbackCache.get(instanceId) ?? (() => {
66
+ const newFallback = {
67
+ id: instanceId,
68
+ bounds: [Number.NaN, Number.NaN, Number.NaN, Number.NaN],
69
+ latitude: Number.NaN,
70
+ longitude: Number.NaN,
71
+ zoom: Number.NaN,
72
+ width: 0,
73
+ height: 0
74
+ };
75
+ fallbackCache.set(instanceId, newFallback);
76
+ return newFallback;
77
+ })();
78
+ const snapshot = snapshotCache.get(instanceId) ?? (() => viewportStore.get(instanceId) ?? fallback);
79
+ snapshotCache.set(instanceId, snapshot);
80
+ return snapshot;
81
+ }
82
+ function useViewportState({
83
+ instanceId,
84
+ subscribe,
85
+ getSnapshot,
86
+ getServerSnapshot
87
+ }) {
88
+ return useSyncExternalStore(
89
+ subscribe ?? getOrCreateSubscription(instanceId),
90
+ getSnapshot ?? getOrCreateSnapshot(instanceId),
91
+ getServerSnapshot
92
+ );
93
+ }
94
+ function clearViewportState(instanceId) {
95
+ const unsub = busUnsubscribers.get(instanceId);
96
+ if (unsub) {
97
+ unsub();
98
+ busUnsubscribers.delete(instanceId);
99
+ }
100
+ viewportStore.delete(instanceId);
101
+ componentSubscribers.delete(instanceId);
102
+ subscriptionCache.delete(instanceId);
103
+ snapshotCache.delete(instanceId);
104
+ fallbackCache.delete(instanceId);
105
+ }
106
+
107
+ export { clearViewportState, useViewportState };
108
+ //# sourceMappingURL=use-viewport-state.js.map
109
+ //# sourceMappingURL=use-viewport-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/viewport/use-viewport-state.ts"],"names":[],"mappings":"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","file":"use-viewport-state.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\nimport { Broadcast } from '@accelint/bus';\nimport { useSyncExternalStore } from 'react';\nimport { MapEvents } from '../deckgl/base-map/events';\nimport type { UniqueId } from '@accelint/core';\nimport type {\n MapEventType,\n MapViewportPayload,\n} from '../deckgl/base-map/types';\n\nconst bus = Broadcast.getInstance<MapEventType>();\n\nexport type UseViewportStateProps = {\n instanceId: UniqueId;\n subscribe?: Parameters<typeof useSyncExternalStore<MapViewportPayload>>[0];\n getSnapshot?: Parameters<typeof useSyncExternalStore<MapViewportPayload>>[1];\n getServerSnapshot?: Parameters<\n typeof useSyncExternalStore<MapViewportPayload>\n >[2];\n};\n\n/**\n * Store for viewport state keyed by instanceId\n */\nconst viewportStore = new Map<UniqueId, MapViewportPayload>();\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 viewport, 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, () => MapViewportPayload>();\n\n/**\n * Cache of fallback snapshots per instanceId to maintain referential stability.\n * This prevents unnecessary re-renders when no viewport data exists yet.\n */\nconst fallbackCache = new Map<UniqueId, MapViewportPayload>();\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 viewport\n */\nfunction ensureBusListener(instanceId: UniqueId): void {\n if (busUnsubscribers.has(instanceId)) {\n return; // Already listening\n }\n\n const unsub = bus.on(MapEvents.viewport, ({ payload }) => {\n if (instanceId === payload.id) {\n // Write to store once\n viewportStore.set(instanceId, payload);\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 viewport\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 viewportStore.delete(instanceId);\n componentSubscribers.delete(instanceId);\n subscriptionCache.delete(instanceId);\n snapshotCache.delete(instanceId);\n fallbackCache.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 viewport state when the last subscriber unsubscribes.\n *\n * @param instanceId - The unique identifier for the viewport\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 object returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.\n *\n * @param instanceId - The unique identifier for the viewport\n * @returns A snapshot function for useSyncExternalStore\n */\nfunction getOrCreateSnapshot(instanceId: UniqueId): () => MapViewportPayload {\n // Get or create stable fallback reference for this instanceId\n const fallback =\n fallbackCache.get(instanceId) ??\n (() => {\n const newFallback: MapViewportPayload = {\n id: instanceId,\n bounds: [Number.NaN, Number.NaN, Number.NaN, Number.NaN],\n latitude: Number.NaN,\n longitude: Number.NaN,\n zoom: Number.NaN,\n width: 0,\n height: 0,\n };\n fallbackCache.set(instanceId, newFallback);\n return newFallback;\n })();\n\n const snapshot =\n snapshotCache.get(instanceId) ??\n (() => viewportStore.get(instanceId) ?? fallback);\n\n snapshotCache.set(instanceId, snapshot);\n\n return snapshot;\n}\n\n/**\n * Hook to subscribe to viewport state changes for a specific view.\n *\n * By default, subscribes to MapEvents.viewport events from the event bus and\n * automatically cleans up when all subscribers unmount. For advanced use cases,\n * custom subscribe/getSnapshot functions can be provided.\n *\n * A thin wrapper around [useSyncExternalStore](https://react.dev/reference/react/useSyncExternalStore).\n *\n * @param instanceId - Unique identifier for the viewport to track\n * @param subscribe - Optional custom subscription function\n * @param getSnapshot - Optional custom snapshot getter\n * @param getServerSnapshot - Optional server-side snapshot getter\n * @returns Current viewport state including bounds, latitude, longitude, zoom\n *\n * @example\n * ```tsx\n * function MapInfo({ instanceId }) {\n * const { bounds, latitude, longitude, zoom } = useViewportState({\n * instanceId\n * });\n *\n * return (\n * <div>\n * Lat: {latitude?.toFixed(2)}, Lon: {longitude?.toFixed(2)}, Zoom: {zoom}\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With custom subscribe/getSnapshot for advanced use cases\n * function CustomMapInfo() {\n * const customSubscribe = (onStoreChange) => {\n * // Custom subscription logic\n * return () => { // cleanup }\n * }\n *\n * const customGetSnapshot = () => {\n * // Custom snapshot logic\n * return { latitude: 0, longitude: 0, zoom: 1 };\n * };\n *\n * const viewState = useViewportState({\n * instanceId: 'some-uuid',\n * subscribe: customSubscribe,\n * getSnapshot: customGetSnapshot,\n * });\n *\n * return <div>Custom viewport state</div>;\n * }\n * ```\n */\nexport function useViewportState({\n instanceId,\n subscribe,\n getSnapshot,\n getServerSnapshot,\n}: UseViewportStateProps) {\n return useSyncExternalStore<MapViewportPayload>(\n subscribe ?? getOrCreateSubscription(instanceId),\n getSnapshot ?? getOrCreateSnapshot(instanceId),\n getServerSnapshot,\n );\n}\n\n/**\n * Manually clear viewport 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 viewport to clear\n *\n * @example\n * ```tsx\n * // Manual cleanup (rarely needed)\n * clearViewportState('my-map-instance');\n * ```\n */\nexport function clearViewportState(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 viewportStore.delete(instanceId);\n componentSubscribers.delete(instanceId);\n subscriptionCache.delete(instanceId);\n snapshotCache.delete(instanceId);\n fallbackCache.delete(instanceId);\n}\n"]}