@accelint/map-toolkit 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/catalog-info.yaml +3 -3
  3. package/dist/camera/events.d.ts +45 -0
  4. package/dist/camera/events.js +45 -0
  5. package/dist/camera/events.js.map +1 -1
  6. package/dist/camera/store.d.ts +47 -0
  7. package/dist/camera/store.js +81 -0
  8. package/dist/camera/store.js.map +1 -1
  9. package/dist/camera/types.d.ts +81 -0
  10. package/dist/cursor-coordinates/constants.d.ts +8 -0
  11. package/dist/cursor-coordinates/constants.js +22 -0
  12. package/dist/cursor-coordinates/constants.js.map +1 -0
  13. package/dist/cursor-coordinates/store.d.ts +1 -0
  14. package/dist/cursor-coordinates/store.js +1 -0
  15. package/dist/cursor-coordinates/store.js.map +1 -1
  16. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +5 -0
  17. package/dist/cursor-coordinates/use-cursor-coordinates.js +21 -6
  18. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  19. package/dist/deckgl/base-map/constants.d.ts +12 -0
  20. package/dist/deckgl/base-map/constants.js +12 -0
  21. package/dist/deckgl/base-map/constants.js.map +1 -1
  22. package/dist/deckgl/base-map/controls.d.ts +10 -0
  23. package/dist/deckgl/base-map/controls.js +5 -0
  24. package/dist/deckgl/base-map/controls.js.map +1 -1
  25. package/dist/deckgl/base-map/events.d.ts +30 -0
  26. package/dist/deckgl/base-map/events.js +30 -0
  27. package/dist/deckgl/base-map/events.js.map +1 -1
  28. package/dist/deckgl/base-map/index.d.ts +2 -2
  29. package/dist/deckgl/base-map/index.js +30 -0
  30. package/dist/deckgl/base-map/index.js.map +1 -1
  31. package/dist/deckgl/saved-viewports/index.d.ts +75 -0
  32. package/dist/deckgl/saved-viewports/index.js +58 -0
  33. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  34. package/dist/deckgl/saved-viewports/storage.d.ts +51 -0
  35. package/dist/deckgl/saved-viewports/storage.js +64 -0
  36. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  37. package/dist/deckgl/shapes/display-shape-layer/constants.js +18 -6
  38. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  39. package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +7 -0
  40. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
  41. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +61 -4
  42. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  43. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +22 -8
  44. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +75 -4
  45. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  46. package/dist/deckgl/shapes/draw-shape-layer/constants.js +30 -0
  47. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -1
  48. package/dist/deckgl/shapes/draw-shape-layer/fiber.js +36 -0
  49. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -1
  50. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +32 -1
  51. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  52. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +37 -8
  53. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  54. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +43 -1
  55. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  56. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +44 -1
  57. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  58. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +46 -3
  59. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  60. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +37 -1
  61. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
  62. package/dist/deckgl/shapes/draw-shape-layer/store.js +50 -2
  63. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
  64. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +137 -16
  65. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
  66. package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
  67. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -1
  68. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +2 -2
  69. package/dist/deckgl/shapes/edit-shape-layer/index.js +14 -0
  70. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  71. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +56 -8
  72. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
  73. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +26 -4
  74. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  75. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +28 -3
  76. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  77. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +24 -0
  78. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
  79. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +33 -4
  80. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
  81. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +21 -2
  82. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
  83. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +35 -11
  84. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
  85. package/dist/deckgl/shapes/edit-shape-layer/store.js +1 -1
  86. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  87. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +12 -0
  88. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
  89. package/dist/deckgl/shapes/shared/types.d.ts +3 -3
  90. package/dist/deckgl/shapes/shared/types.js +2 -2
  91. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  92. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +3 -3
  93. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  94. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +1 -1
  95. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
  96. package/dist/deckgl/symbol-layer/fiber.d.ts +18 -0
  97. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  98. package/dist/deckgl/symbol-layer/index.d.ts +79 -1
  99. package/dist/deckgl/symbol-layer/index.js +72 -1
  100. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  101. package/dist/deckgl/text-layer/character-sets.d.ts +30 -0
  102. package/dist/deckgl/text-layer/character-sets.js +26 -0
  103. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  104. package/dist/deckgl/text-layer/default-settings.d.ts +29 -0
  105. package/dist/deckgl/text-layer/default-settings.js +28 -0
  106. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  107. package/dist/deckgl/text-layer/index.d.ts +65 -0
  108. package/dist/deckgl/text-layer/index.js +56 -0
  109. package/dist/deckgl/text-layer/index.js.map +1 -1
  110. package/dist/map-cursor/events.d.ts +19 -0
  111. package/dist/map-cursor/events.js +19 -0
  112. package/dist/map-cursor/events.js.map +1 -1
  113. package/dist/map-cursor/store.d.ts +34 -2
  114. package/dist/map-cursor/store.js +44 -3
  115. package/dist/map-cursor/store.js.map +1 -1
  116. package/dist/map-mode/store.d.ts +43 -4
  117. package/dist/map-mode/store.js +55 -5
  118. package/dist/map-mode/store.js.map +1 -1
  119. package/dist/shared/create-map-store.d.ts +14 -0
  120. package/dist/shared/create-map-store.js +26 -2
  121. package/dist/shared/create-map-store.js.map +1 -1
  122. package/dist/shared/units.d.ts +24 -0
  123. package/dist/shared/units.js +24 -0
  124. package/dist/shared/units.js.map +1 -1
  125. package/dist/viewport/store.d.ts +1 -0
  126. package/dist/viewport/store.js +4 -0
  127. package/dist/viewport/store.js.map +1 -1
  128. package/package.json +7 -7
@@ -40,7 +40,21 @@ type CursorActions = {
40
40
  */
41
41
  declare const cursorStore: MapStore<CursorState, CursorActions>;
42
42
  /**
43
- * Get effective cursor (computed from state)
43
+ * Get effective cursor value for a map instance.
44
+ *
45
+ * Computes the effective cursor from the current state by applying
46
+ * the priority system (mode owner > most recent > default).
47
+ *
48
+ * @param mapId - Unique identifier for the map instance
49
+ * @returns The effective cursor to display
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { getCursor } from '@accelint/map-toolkit/map-cursor';
54
+ *
55
+ * const currentCursor = getCursor(mapId);
56
+ * console.log('Current cursor:', currentCursor); // 'default', 'pointer', etc.
57
+ * ```
44
58
  */
45
59
  declare function getCursor(mapId: UniqueId): CSSCursorType;
46
60
  /**
@@ -53,10 +67,28 @@ declare function getCursor(mapId: UniqueId): CSSCursorType;
53
67
  * - Better ergonomics for consumers
54
68
  *
55
69
  * This hook exists for internal composition (used by useMapCursor).
70
+ *
71
+ * @param mapId - Unique identifier for the map instance
72
+ * @returns The effective cursor to display
56
73
  */
57
74
  declare function useCursor(mapId: UniqueId): CSSCursorType;
58
75
  /**
59
- * Clear cursor state
76
+ * Clear cursor state for a specific map instance.
77
+ *
78
+ * Removes all cursor ownership data and resets to default state.
79
+ * This is typically not needed as cleanup happens automatically.
80
+ * Use only in advanced scenarios where manual cleanup is required.
81
+ *
82
+ * @param mapId - Unique identifier for the map instance to clear
83
+ * @returns void
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * import { clearCursorState } from '@accelint/map-toolkit/map-cursor';
88
+ *
89
+ * // Manual cleanup when destroying a map
90
+ * clearCursorState(mapId);
91
+ * ```
60
92
  */
61
93
  declare function clearCursorState(mapId: UniqueId): void;
62
94
  //#endregion
@@ -73,7 +73,16 @@ function isRegisteredOwner(mapId, owner) {
73
73
  return isRegisteredModeOwnerFn?.(mapId, owner) ?? false;
74
74
  }
75
75
  /**
76
- * Calculate effective cursor based on priority
76
+ * Calculate effective cursor based on priority.
77
+ *
78
+ * Priority order:
79
+ * 1. Mode owner's cursor (if mode is owned)
80
+ * 2. Most recent cursor request (if owner still has entry)
81
+ * 3. Default cursor
82
+ *
83
+ * @param mapId - Unique identifier for the map instance
84
+ * @param state - Current cursor state
85
+ * @returns The effective cursor to display
77
86
  */
78
87
  function getEffectiveCursor(mapId, state) {
79
88
  const modeOwner = getModeOwner?.(mapId);
@@ -191,7 +200,21 @@ const cursorStore = createMapStore({
191
200
  }
192
201
  });
193
202
  /**
194
- * Get effective cursor (computed from state)
203
+ * Get effective cursor value for a map instance.
204
+ *
205
+ * Computes the effective cursor from the current state by applying
206
+ * the priority system (mode owner > most recent > default).
207
+ *
208
+ * @param mapId - Unique identifier for the map instance
209
+ * @returns The effective cursor to display
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * import { getCursor } from '@accelint/map-toolkit/map-cursor';
214
+ *
215
+ * const currentCursor = getCursor(mapId);
216
+ * console.log('Current cursor:', currentCursor); // 'default', 'pointer', etc.
217
+ * ```
195
218
  */
196
219
  function getCursor(mapId) {
197
220
  return getEffectiveCursor(mapId, cursorStore.get(mapId));
@@ -206,12 +229,30 @@ function getCursor(mapId) {
206
229
  * - Better ergonomics for consumers
207
230
  *
208
231
  * This hook exists for internal composition (used by useMapCursor).
232
+ *
233
+ * @param mapId - Unique identifier for the map instance
234
+ * @returns The effective cursor to display
209
235
  */
210
236
  function useCursor(mapId) {
211
237
  return cursorStore.useSelector(mapId, (state) => getEffectiveCursor(mapId, state));
212
238
  }
213
239
  /**
214
- * Clear cursor state
240
+ * Clear cursor state for a specific map instance.
241
+ *
242
+ * Removes all cursor ownership data and resets to default state.
243
+ * This is typically not needed as cleanup happens automatically.
244
+ * Use only in advanced scenarios where manual cleanup is required.
245
+ *
246
+ * @param mapId - Unique identifier for the map instance to clear
247
+ * @returns void
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * import { clearCursorState } from '@accelint/map-toolkit/map-cursor';
252
+ *
253
+ * // Manual cleanup when destroying a map
254
+ * clearCursorState(mapId);
255
+ * ```
215
256
  */
216
257
  function clearCursorState(mapId) {
217
258
  cursorStore.clear(mapId);
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":["DEFAULT_CURSOR: CSSCursorType","getModeOwnerFn: ((mapId: UniqueId) => string | undefined) | null","isRegisteredModeOwnerFn:\n | ((mapId: UniqueId, owner: string) => boolean)\n | null","importPromise: Promise<void> | null","updates: Partial<CursorState>","newState: CursorState"],"sources":["../../src/map-cursor/store.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Map Cursor Store\n *\n * Manages cursor state with ownership-based priority.\n *\n * Priority order:\n * 1. Mode owner's cursor (if mode is owned)\n * 2. Most recent cursor request\n * 3. Default cursor\n *\n * @example\n * ```tsx\n * import { cursorStore } from '@accelint/map-toolkit/map-cursor';\n *\n * function MapContainer({ mapId }) {\n * const cursor = cursorStore.useSelector(mapId, (s) => getEffectiveCursor(mapId, s));\n * return <div style={{ cursor }}>...</div>;\n * }\n *\n * // Request cursor change from a layer:\n * cursorStore.actions(mapId).requestCursorChange('crosshair', 'draw-layer');\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { MapModeEvents } from '../map-mode/events';\nimport { createMapStore, mapDelete, mapSet } from '../shared/create-map-store';\nimport { MapCursorEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { ModeChangedEvent } from '../map-mode/types';\nimport type { CSSCursorType, MapCursorEventType } from './types';\n\nconst DEFAULT_CURSOR: CSSCursorType = 'default';\n\n/**\n * State shape for map cursor\n */\ntype CursorState = {\n /** Map of owner -> cursor */\n cursorOwners: Map<string, CSSCursorType>;\n /** Current active cursor (for priority tracking) */\n currentCursor: CSSCursorType | null;\n /** Current cursor owner (for priority tracking) */\n currentOwner: string | null;\n};\n\n/**\n * Actions for cursor management\n */\ntype CursorActions = {\n /** Request a cursor change */\n requestCursorChange: (cursor: CSSCursorType, owner: string) => void;\n /** Clear cursor for an owner */\n clearCursor: (owner: string) => void;\n};\n\nconst cursorBus = Broadcast.getInstance<MapCursorEventType>();\nconst modeBus = Broadcast.getInstance<ModeChangedEvent>();\n\n/**\n * Lazy import to avoid circular dependency between cursor and mode stores.\n * The mode store doesn't depend on cursor store, but importing it synchronously\n * at module load time can cause initialization order issues.\n *\n * This pattern ensures the import is resolved before first use (bus listeners\n * are set up on first subscriber, not at module load time).\n */\nlet getModeOwnerFn: ((mapId: UniqueId) => string | undefined) | null = null;\nlet isRegisteredModeOwnerFn:\n | ((mapId: UniqueId, owner: string) => boolean)\n | null = null;\nlet importPromise: Promise<void> | null = null;\nlet importFailed = false;\n\nfunction ensureModeStoreImported(): void {\n if (getModeOwnerFn !== null || importFailed) {\n return;\n }\n if (importPromise === null) {\n importPromise = import('../map-mode/store')\n .then((mod) => {\n getModeOwnerFn = mod.getCurrentModeOwner;\n isRegisteredModeOwnerFn = mod.isRegisteredModeOwner;\n })\n .catch((error) => {\n importFailed = true;\n // Log error in development only - in production this is a silent fallback\n if (process.env.NODE_ENV !== 'production') {\n console.error('[MapCursor] Failed to import mode store:', error);\n }\n });\n }\n}\n\n// Start the import immediately so it's likely resolved by first use\nensureModeStoreImported();\n\nfunction getModeOwner(mapId: UniqueId): string | undefined {\n // getModeOwnerFn will be available by the time bus listeners are set up\n // (which happens on first React subscriber, not at module load)\n return getModeOwnerFn?.(mapId);\n}\n\nfunction isRegisteredOwner(mapId: UniqueId, owner: string): boolean {\n return isRegisteredModeOwnerFn?.(mapId, owner) ?? false;\n}\n\n/**\n * Calculate effective cursor based on priority\n */\nfunction getEffectiveCursor(\n mapId: UniqueId,\n state: CursorState,\n): CSSCursorType {\n // Priority 1: Mode owner's cursor\n const modeOwner = getModeOwner?.(mapId);\n if (modeOwner) {\n const modeOwnerCursor = state.cursorOwners.get(modeOwner);\n if (modeOwnerCursor) {\n return modeOwnerCursor;\n }\n }\n\n // Priority 2: Current cursor (if owner still has entry)\n if (state.currentCursor && state.currentOwner) {\n if (state.cursorOwners.has(state.currentOwner)) {\n return state.currentCursor;\n }\n }\n\n // Priority 3: Default\n return DEFAULT_CURSOR;\n}\n\n/**\n * Cursor store\n */\nexport const cursorStore = createMapStore<CursorState, CursorActions>({\n defaultState: {\n cursorOwners: new Map(),\n currentCursor: null,\n currentOwner: null,\n },\n\n actions: (mapId, { get, set }) => ({\n requestCursorChange: (cursor: CSSCursorType, owner: string) => {\n const trimmedCursor = cursor.trim() as CSSCursorType;\n const trimmedOwner = owner.trim();\n\n if (!trimmedCursor) {\n throw new Error('requestCursorChange requires non-empty cursor');\n }\n if (!trimmedOwner) {\n throw new Error('requestCursorChange requires non-empty owner');\n }\n\n cursorBus.emit(MapCursorEvents.changeRequest, {\n cursor: trimmedCursor,\n owner: trimmedOwner,\n id: mapId,\n });\n },\n\n clearCursor: (owner: string) => {\n const state = get();\n const hadCursor = state.cursorOwners.has(owner);\n\n if (hadCursor) {\n // Clear current tracking if this was the owner\n const updates: Partial<CursorState> = {\n cursorOwners: mapDelete(state.cursorOwners, owner),\n };\n if (state.currentOwner === owner) {\n updates.currentCursor = null;\n updates.currentOwner = null;\n }\n\n set(updates);\n }\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Handle cursor change requests\n const unsubRequest = cursorBus.on(\n MapCursorEvents.changeRequest,\n (event) => {\n const { cursor, owner: requestOwner, id } = event.payload;\n if (id !== mapId) {\n return;\n }\n\n const state = get();\n const previousCursor = getEffectiveCursor(mapId, state);\n\n // Skip if same cursor already stored for this owner\n if (state.cursorOwners.get(requestOwner) === cursor) {\n return;\n }\n\n // Create new Map for immutable update\n const newCursorOwners = mapSet(\n state.cursorOwners,\n requestOwner,\n cursor,\n );\n\n // Check ownership\n const currentModeOwner = getModeOwner?.(mapId);\n const isOwnerless = !currentModeOwner;\n const isCurrentModeOwner = requestOwner === currentModeOwner;\n const isAnyModeOwner = isRegisteredOwner(mapId, requestOwner);\n\n if (isOwnerless || isCurrentModeOwner) {\n // Accept: apply cursor with immutable update\n const newState: CursorState = {\n cursorOwners: newCursorOwners,\n currentCursor: cursor,\n currentOwner: requestOwner,\n };\n\n // Calculate new cursor with updated state\n const newCursor = getEffectiveCursor(mapId, newState);\n\n if (previousCursor !== newCursor) {\n set(newState);\n cursorBus.emit(MapCursorEvents.changed, {\n previousCursor,\n currentCursor: newCursor,\n owner: requestOwner,\n id: mapId,\n });\n } else {\n // Still need to update state even if cursor didn't change visually\n set(newState);\n }\n } else if (isAnyModeOwner) {\n // Store but don't apply: requester owns a different mode (pending or not current).\n // When their mode becomes active, getEffectiveCursor will find their cursor.\n set({ cursorOwners: newCursorOwners });\n\n cursorBus.emit(MapCursorEvents.rejected, {\n rejectedCursor: cursor,\n rejectedOwner: requestOwner,\n currentOwner: state.currentOwner || currentModeOwner || 'unknown',\n reason: 'not-current-owner',\n id: mapId,\n });\n } else {\n // Reject: don't store. Non-owners should only set cursor in default mode.\n cursorBus.emit(MapCursorEvents.rejected, {\n rejectedCursor: cursor,\n rejectedOwner: requestOwner,\n currentOwner: state.currentOwner || currentModeOwner || 'unknown',\n reason: 'not-owner',\n id: mapId,\n });\n }\n },\n );\n\n // Handle mode changes\n const unsubMode = modeBus.on(MapModeEvents.changed, (event) => {\n if (event.payload.id !== mapId) {\n return;\n }\n\n const state = get();\n const previousCursor = getEffectiveCursor(mapId, state);\n\n // Clear current tracking on mode change with immutable update\n if (\n event.payload.currentMode === 'default' ||\n event.payload.previousMode !== event.payload.currentMode\n ) {\n set({\n currentCursor: null,\n currentOwner: null,\n });\n }\n\n // Defer check until new mode owner is registered\n queueMicrotask(() => {\n // Re-get state after microtask since it may have changed\n const currentState = get();\n const newCursor = getEffectiveCursor(mapId, currentState);\n if (previousCursor !== newCursor) {\n const newModeOwner = getModeOwner?.(mapId) || 'system';\n cursorBus.emit(MapCursorEvents.changed, {\n previousCursor,\n currentCursor: newCursor,\n owner: newModeOwner,\n id: mapId,\n });\n }\n });\n });\n\n return () => {\n unsubRequest();\n unsubMode();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get effective cursor (computed from state)\n */\nexport function getCursor(mapId: UniqueId): CSSCursorType {\n return getEffectiveCursor(mapId, cursorStore.get(mapId));\n}\n\n/**\n * Hook for effective cursor value.\n *\n * **Internal use only** - not exported from the public API.\n * Use `useMapCursor` instead, which provides:\n * - MapContext integration (auto-resolves mapId inside MapProvider)\n * - Actions (requestCursorChange, clearCursor)\n * - Better ergonomics for consumers\n *\n * This hook exists for internal composition (used by useMapCursor).\n */\nexport function useCursor(mapId: UniqueId): CSSCursorType {\n return cursorStore.useSelector(mapId, (state) =>\n getEffectiveCursor(mapId, state),\n );\n}\n\n/**\n * Clear cursor state\n */\nexport function clearCursorState(mapId: UniqueId): void {\n cursorStore.clear(mapId);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":["DEFAULT_CURSOR: CSSCursorType","getModeOwnerFn: ((mapId: UniqueId) => string | undefined) | null","isRegisteredModeOwnerFn:\n | ((mapId: UniqueId, owner: string) => boolean)\n | null","importPromise: Promise<void> | null","updates: Partial<CursorState>","newState: CursorState"],"sources":["../../src/map-cursor/store.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Map Cursor Store\n *\n * Manages cursor state with ownership-based priority.\n *\n * Priority order:\n * 1. Mode owner's cursor (if mode is owned)\n * 2. Most recent cursor request\n * 3. Default cursor\n *\n * @example\n * ```tsx\n * import { cursorStore } from '@accelint/map-toolkit/map-cursor';\n *\n * function MapContainer({ mapId }) {\n * const cursor = cursorStore.useSelector(mapId, (s) => getEffectiveCursor(mapId, s));\n * return <div style={{ cursor }}>...</div>;\n * }\n *\n * // Request cursor change from a layer:\n * cursorStore.actions(mapId).requestCursorChange('crosshair', 'draw-layer');\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { MapModeEvents } from '../map-mode/events';\nimport { createMapStore, mapDelete, mapSet } from '../shared/create-map-store';\nimport { MapCursorEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { ModeChangedEvent } from '../map-mode/types';\nimport type { CSSCursorType, MapCursorEventType } from './types';\n\nconst DEFAULT_CURSOR: CSSCursorType = 'default';\n\n/**\n * State shape for map cursor\n */\ntype CursorState = {\n /** Map of owner -> cursor */\n cursorOwners: Map<string, CSSCursorType>;\n /** Current active cursor (for priority tracking) */\n currentCursor: CSSCursorType | null;\n /** Current cursor owner (for priority tracking) */\n currentOwner: string | null;\n};\n\n/**\n * Actions for cursor management\n */\ntype CursorActions = {\n /** Request a cursor change */\n requestCursorChange: (cursor: CSSCursorType, owner: string) => void;\n /** Clear cursor for an owner */\n clearCursor: (owner: string) => void;\n};\n\nconst cursorBus = Broadcast.getInstance<MapCursorEventType>();\nconst modeBus = Broadcast.getInstance<ModeChangedEvent>();\n\n/**\n * Lazy import to avoid circular dependency between cursor and mode stores.\n * The mode store doesn't depend on cursor store, but importing it synchronously\n * at module load time can cause initialization order issues.\n *\n * This pattern ensures the import is resolved before first use (bus listeners\n * are set up on first subscriber, not at module load time).\n */\nlet getModeOwnerFn: ((mapId: UniqueId) => string | undefined) | null = null;\nlet isRegisteredModeOwnerFn:\n | ((mapId: UniqueId, owner: string) => boolean)\n | null = null;\nlet importPromise: Promise<void> | null = null;\nlet importFailed = false;\n\nfunction ensureModeStoreImported(): void {\n if (getModeOwnerFn !== null || importFailed) {\n return;\n }\n if (importPromise === null) {\n importPromise = import('../map-mode/store')\n .then((mod) => {\n getModeOwnerFn = mod.getCurrentModeOwner;\n isRegisteredModeOwnerFn = mod.isRegisteredModeOwner;\n })\n .catch((error) => {\n importFailed = true;\n // Log error in development only - in production this is a silent fallback\n if (process.env.NODE_ENV !== 'production') {\n console.error('[MapCursor] Failed to import mode store:', error);\n }\n });\n }\n}\n\n// Start the import immediately so it's likely resolved by first use\nensureModeStoreImported();\n\nfunction getModeOwner(mapId: UniqueId): string | undefined {\n // getModeOwnerFn will be available by the time bus listeners are set up\n // (which happens on first React subscriber, not at module load)\n return getModeOwnerFn?.(mapId);\n}\n\nfunction isRegisteredOwner(mapId: UniqueId, owner: string): boolean {\n return isRegisteredModeOwnerFn?.(mapId, owner) ?? false;\n}\n\n/**\n * Calculate effective cursor based on priority.\n *\n * Priority order:\n * 1. Mode owner's cursor (if mode is owned)\n * 2. Most recent cursor request (if owner still has entry)\n * 3. Default cursor\n *\n * @param mapId - Unique identifier for the map instance\n * @param state - Current cursor state\n * @returns The effective cursor to display\n */\nfunction getEffectiveCursor(\n mapId: UniqueId,\n state: CursorState,\n): CSSCursorType {\n // Priority 1: Mode owner's cursor\n const modeOwner = getModeOwner?.(mapId);\n if (modeOwner) {\n const modeOwnerCursor = state.cursorOwners.get(modeOwner);\n if (modeOwnerCursor) {\n return modeOwnerCursor;\n }\n }\n\n // Priority 2: Current cursor (if owner still has entry)\n if (state.currentCursor && state.currentOwner) {\n if (state.cursorOwners.has(state.currentOwner)) {\n return state.currentCursor;\n }\n }\n\n // Priority 3: Default\n return DEFAULT_CURSOR;\n}\n\n/**\n * Cursor store\n */\nexport const cursorStore = createMapStore<CursorState, CursorActions>({\n defaultState: {\n cursorOwners: new Map(),\n currentCursor: null,\n currentOwner: null,\n },\n\n actions: (mapId, { get, set }) => ({\n requestCursorChange: (cursor: CSSCursorType, owner: string) => {\n const trimmedCursor = cursor.trim() as CSSCursorType;\n const trimmedOwner = owner.trim();\n\n if (!trimmedCursor) {\n throw new Error('requestCursorChange requires non-empty cursor');\n }\n if (!trimmedOwner) {\n throw new Error('requestCursorChange requires non-empty owner');\n }\n\n cursorBus.emit(MapCursorEvents.changeRequest, {\n cursor: trimmedCursor,\n owner: trimmedOwner,\n id: mapId,\n });\n },\n\n clearCursor: (owner: string) => {\n const state = get();\n const hadCursor = state.cursorOwners.has(owner);\n\n if (hadCursor) {\n // Clear current tracking if this was the owner\n const updates: Partial<CursorState> = {\n cursorOwners: mapDelete(state.cursorOwners, owner),\n };\n if (state.currentOwner === owner) {\n updates.currentCursor = null;\n updates.currentOwner = null;\n }\n\n set(updates);\n }\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Handle cursor change requests\n const unsubRequest = cursorBus.on(\n MapCursorEvents.changeRequest,\n (event) => {\n const { cursor, owner: requestOwner, id } = event.payload;\n if (id !== mapId) {\n return;\n }\n\n const state = get();\n const previousCursor = getEffectiveCursor(mapId, state);\n\n // Skip if same cursor already stored for this owner\n if (state.cursorOwners.get(requestOwner) === cursor) {\n return;\n }\n\n // Create new Map for immutable update\n const newCursorOwners = mapSet(\n state.cursorOwners,\n requestOwner,\n cursor,\n );\n\n // Check ownership\n const currentModeOwner = getModeOwner?.(mapId);\n const isOwnerless = !currentModeOwner;\n const isCurrentModeOwner = requestOwner === currentModeOwner;\n const isAnyModeOwner = isRegisteredOwner(mapId, requestOwner);\n\n if (isOwnerless || isCurrentModeOwner) {\n // Accept: apply cursor with immutable update\n const newState: CursorState = {\n cursorOwners: newCursorOwners,\n currentCursor: cursor,\n currentOwner: requestOwner,\n };\n\n // Calculate new cursor with updated state\n const newCursor = getEffectiveCursor(mapId, newState);\n\n if (previousCursor !== newCursor) {\n set(newState);\n cursorBus.emit(MapCursorEvents.changed, {\n previousCursor,\n currentCursor: newCursor,\n owner: requestOwner,\n id: mapId,\n });\n } else {\n // Still need to update state even if cursor didn't change visually\n set(newState);\n }\n } else if (isAnyModeOwner) {\n // Store but don't apply: requester owns a different mode (pending or not current).\n // When their mode becomes active, getEffectiveCursor will find their cursor.\n set({ cursorOwners: newCursorOwners });\n\n cursorBus.emit(MapCursorEvents.rejected, {\n rejectedCursor: cursor,\n rejectedOwner: requestOwner,\n currentOwner: state.currentOwner || currentModeOwner || 'unknown',\n reason: 'not-current-owner',\n id: mapId,\n });\n } else {\n // Reject: don't store. Non-owners should only set cursor in default mode.\n cursorBus.emit(MapCursorEvents.rejected, {\n rejectedCursor: cursor,\n rejectedOwner: requestOwner,\n currentOwner: state.currentOwner || currentModeOwner || 'unknown',\n reason: 'not-owner',\n id: mapId,\n });\n }\n },\n );\n\n // Handle mode changes\n const unsubMode = modeBus.on(MapModeEvents.changed, (event) => {\n if (event.payload.id !== mapId) {\n return;\n }\n\n const state = get();\n const previousCursor = getEffectiveCursor(mapId, state);\n\n // Clear current tracking on mode change with immutable update\n if (\n event.payload.currentMode === 'default' ||\n event.payload.previousMode !== event.payload.currentMode\n ) {\n set({\n currentCursor: null,\n currentOwner: null,\n });\n }\n\n // Defer check until new mode owner is registered\n queueMicrotask(() => {\n // Re-get state after microtask since it may have changed\n const currentState = get();\n const newCursor = getEffectiveCursor(mapId, currentState);\n if (previousCursor !== newCursor) {\n const newModeOwner = getModeOwner?.(mapId) || 'system';\n cursorBus.emit(MapCursorEvents.changed, {\n previousCursor,\n currentCursor: newCursor,\n owner: newModeOwner,\n id: mapId,\n });\n }\n });\n });\n\n return () => {\n unsubRequest();\n unsubMode();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get effective cursor value for a map instance.\n *\n * Computes the effective cursor from the current state by applying\n * the priority system (mode owner > most recent > default).\n *\n * @param mapId - Unique identifier for the map instance\n * @returns The effective cursor to display\n *\n * @example\n * ```typescript\n * import { getCursor } from '@accelint/map-toolkit/map-cursor';\n *\n * const currentCursor = getCursor(mapId);\n * console.log('Current cursor:', currentCursor); // 'default', 'pointer', etc.\n * ```\n */\nexport function getCursor(mapId: UniqueId): CSSCursorType {\n return getEffectiveCursor(mapId, cursorStore.get(mapId));\n}\n\n/**\n * Hook for effective cursor value.\n *\n * **Internal use only** - not exported from the public API.\n * Use `useMapCursor` instead, which provides:\n * - MapContext integration (auto-resolves mapId inside MapProvider)\n * - Actions (requestCursorChange, clearCursor)\n * - Better ergonomics for consumers\n *\n * This hook exists for internal composition (used by useMapCursor).\n *\n * @param mapId - Unique identifier for the map instance\n * @returns The effective cursor to display\n */\nexport function useCursor(mapId: UniqueId): CSSCursorType {\n return cursorStore.useSelector(mapId, (state) =>\n getEffectiveCursor(mapId, state),\n );\n}\n\n/**\n * Clear cursor state for a specific map instance.\n *\n * Removes all cursor ownership data and resets to default state.\n * This is typically not needed as cleanup happens automatically.\n * Use only in advanced scenarios where manual cleanup is required.\n *\n * @param mapId - Unique identifier for the map instance to clear\n * @returns void\n *\n * @example\n * ```typescript\n * import { clearCursorState } from '@accelint/map-toolkit/map-cursor';\n *\n * // Manual cleanup when destroying a map\n * clearCursorState(mapId);\n * ```\n */\nexport function clearCursorState(mapId: UniqueId): void {\n cursorStore.clear(mapId);\n}\n"],"mappings":"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"}
@@ -43,26 +43,65 @@ type MapModeActions = {
43
43
  */
44
44
  declare const modeStore: MapStore<MapModeState, MapModeActions>;
45
45
  /**
46
- * Get the current mode for a map instance
46
+ * Get the current mode for a map instance.
47
+ *
48
+ * @param mapId - Unique identifier for the map instance
49
+ * @returns The current active mode string
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { getMode } from '@accelint/map-toolkit/map-mode';
54
+ *
55
+ * const currentMode = getMode(mapId);
56
+ * console.log('Current mode:', currentMode); // 'default', 'drawing', etc.
57
+ * ```
47
58
  */
48
59
  declare function getMode(mapId: UniqueId): string;
49
60
  /**
50
- * Hook for current mode value
61
+ * Hook for current mode value.
62
+ *
63
+ * **Internal use only** - not exported from the public API.
64
+ * Use `useMapMode` instead for better ergonomics and MapContext integration.
65
+ *
66
+ * @param mapId - Unique identifier for the map instance
67
+ * @returns The current active mode string
51
68
  */
52
69
  declare function useMode(mapId: UniqueId): string;
53
70
  /**
54
- * Get the owner of the current mode for a given map instance
71
+ * Get the owner of the current mode for a given map instance.
72
+ *
55
73
  * @internal - For internal map-toolkit use only
74
+ * @param instanceId - Unique identifier for the map instance
75
+ * @returns The owner ID of the current mode, or undefined if unowned
56
76
  */
57
77
  declare function getCurrentModeOwner(instanceId: UniqueId): string | undefined;
58
78
  /**
59
79
  * Check if a given owner is registered as the owner of any mode.
60
80
  * This includes both active mode owners and pending mode requests.
81
+ *
61
82
  * @internal - For internal map-toolkit use only
83
+ * @param instanceId - Unique identifier for the map instance
84
+ * @param owner - The owner ID to check
85
+ * @returns True if the owner is registered for any mode
62
86
  */
63
87
  declare function isRegisteredModeOwner(instanceId: UniqueId, owner: string): boolean;
64
88
  /**
65
- * Manually clear map mode state for a specific instanceId.
89
+ * Manually clear map mode state for a specific map instance.
90
+ *
91
+ * Removes all mode ownership data, pending requests, and resets to default state.
92
+ * This is typically not needed as cleanup happens automatically.
93
+ * Use only in advanced scenarios where manual cleanup is required.
94
+ *
95
+ * @param instanceId - Unique identifier for the map instance to clear
96
+ * @returns void
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * import { clearMapModeState } from '@accelint/map-toolkit/map-mode';
101
+ *
102
+ * // Manual cleanup when destroying a map
103
+ * clearMapModeState(mapId);
104
+ * ```
66
105
  */
67
106
  declare function clearMapModeState(instanceId: UniqueId): void;
68
107
  //#endregion
@@ -53,7 +53,18 @@ const DEFAULT_MODE = "default";
53
53
  */
54
54
  const mapModeBus = Broadcast.getInstance();
55
55
  /**
56
- * Determine if a mode change request should be auto-accepted without authorization
56
+ * Determine if a mode change request should be auto-accepted without authorization.
57
+ *
58
+ * Auto-accept conditions:
59
+ * - Owner returning to default mode
60
+ * - Owner switching between their own modes
61
+ * - No ownership conflicts exist
62
+ * - Entering an owned mode from default mode
63
+ *
64
+ * @param state - Current mode state
65
+ * @param desiredMode - The mode being requested
66
+ * @param requestOwner - The component requesting the mode change
67
+ * @returns True if the request should be auto-accepted
57
68
  */
58
69
  function shouldAutoAcceptRequest(state, desiredMode, requestOwner) {
59
70
  const currentModeOwner = state.modeOwners.get(state.mode);
@@ -222,20 +233,40 @@ const modeStore = createMapStore({
222
233
  }
223
234
  });
224
235
  /**
225
- * Get the current mode for a map instance
236
+ * Get the current mode for a map instance.
237
+ *
238
+ * @param mapId - Unique identifier for the map instance
239
+ * @returns The current active mode string
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * import { getMode } from '@accelint/map-toolkit/map-mode';
244
+ *
245
+ * const currentMode = getMode(mapId);
246
+ * console.log('Current mode:', currentMode); // 'default', 'drawing', etc.
247
+ * ```
226
248
  */
227
249
  function getMode(mapId) {
228
250
  return modeStore.get(mapId).mode;
229
251
  }
230
252
  /**
231
- * Hook for current mode value
253
+ * Hook for current mode value.
254
+ *
255
+ * **Internal use only** - not exported from the public API.
256
+ * Use `useMapMode` instead for better ergonomics and MapContext integration.
257
+ *
258
+ * @param mapId - Unique identifier for the map instance
259
+ * @returns The current active mode string
232
260
  */
233
261
  function useMode(mapId) {
234
262
  return modeStore.useSelector(mapId, (state) => state.mode);
235
263
  }
236
264
  /**
237
- * Get the owner of the current mode for a given map instance
265
+ * Get the owner of the current mode for a given map instance.
266
+ *
238
267
  * @internal - For internal map-toolkit use only
268
+ * @param instanceId - Unique identifier for the map instance
269
+ * @returns The owner ID of the current mode, or undefined if unowned
239
270
  */
240
271
  function getCurrentModeOwner(instanceId) {
241
272
  const state = modeStore.get(instanceId);
@@ -244,7 +275,11 @@ function getCurrentModeOwner(instanceId) {
244
275
  /**
245
276
  * Check if a given owner is registered as the owner of any mode.
246
277
  * This includes both active mode owners and pending mode requests.
278
+ *
247
279
  * @internal - For internal map-toolkit use only
280
+ * @param instanceId - Unique identifier for the map instance
281
+ * @param owner - The owner ID to check
282
+ * @returns True if the owner is registered for any mode
248
283
  */
249
284
  function isRegisteredModeOwner(instanceId, owner) {
250
285
  const state = modeStore.get(instanceId);
@@ -253,7 +288,22 @@ function isRegisteredModeOwner(instanceId, owner) {
253
288
  return false;
254
289
  }
255
290
  /**
256
- * Manually clear map mode state for a specific instanceId.
291
+ * Manually clear map mode state for a specific map instance.
292
+ *
293
+ * Removes all mode ownership data, pending requests, and resets to default state.
294
+ * This is typically not needed as cleanup happens automatically.
295
+ * Use only in advanced scenarios where manual cleanup is required.
296
+ *
297
+ * @param instanceId - Unique identifier for the map instance to clear
298
+ * @returns void
299
+ *
300
+ * @example
301
+ * ```typescript
302
+ * import { clearMapModeState } from '@accelint/map-toolkit/map-mode';
303
+ *
304
+ * // Manual cleanup when destroying a map
305
+ * clearMapModeState(mapId);
306
+ * ```
257
307
  */
258
308
  function clearMapModeState(instanceId) {
259
309
  modeStore.clear(instanceId);
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":["requestsToReject: PendingRequest[]","matchingRequestOwner: string | null","matchingRequest: PendingRequest | null"],"sources":["../../src/map-mode/store.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Map Mode Store\n *\n * Manages mode state with ownership-based authorization.\n *\n * @example\n * ```tsx\n * import { modeStore } from '@accelint/map-toolkit/map-mode';\n *\n * function MapControls({ mapId }) {\n * const { state, requestModeChange } = modeStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Current mode: {state.mode}</p>\n * <button onClick={() => requestModeChange('draw', 'draw-layer')}>\n * Draw Mode\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { uuid } from '@accelint/core';\nimport { getLogger } from '@accelint/logger';\nimport {\n createMapStore,\n mapClear,\n mapDelete,\n mapSet,\n} from '../shared/create-map-store';\nimport { MapModeEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { StoreHelpers } from '../shared/create-map-store';\nimport type { MapModeEventType, ModeChangeDecisionPayload } from './types';\n\nconst logger = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[MapMode]',\n pretty: true,\n});\n\nconst DEFAULT_MODE = 'default';\n\n/**\n * Typed event bus instance for map mode events.\n */\nconst mapModeBus = Broadcast.getInstance<MapModeEventType>();\n\n/**\n * Internal type for tracking pending authorization requests.\n */\ntype PendingRequest = {\n authId: string;\n desiredMode: string;\n currentMode: string;\n requestOwner: string;\n};\n\n/**\n * State shape for map mode\n */\ntype MapModeState = {\n mode: string;\n modeOwners: Map<string, string>;\n pendingRequests: Map<string, PendingRequest>;\n};\n\n/**\n * Actions for map mode\n */\ntype MapModeActions = {\n /** Request a mode change */\n requestModeChange: (desiredMode: string, requestOwner: string) => void;\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 * Approve a request and reject all others (immutable update)\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 set: StoreHelpers<MapModeState>['set'],\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 // Build immutable updates: clear pending requests, update owners\n const newModeOwners =\n approvedRequest.desiredMode !== DEFAULT_MODE\n ? mapSet(\n state.modeOwners,\n approvedRequest.desiredMode,\n approvedRequest.requestOwner,\n )\n : state.modeOwners;\n\n // Immutable update: clear pending requests, update owners, change mode\n set({\n mode: approvedRequest.desiredMode,\n pendingRequests: mapClear<string, PendingRequest>(),\n modeOwners: newModeOwners,\n });\n\n // Emit mode changed event\n mapModeBus.emit(MapModeEvents.changed, {\n previousMode: state.mode,\n currentMode: approvedRequest.desiredMode,\n id: instanceId,\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 (immutable update)\n */\nfunction handlePendingRequestsOnDefaultMode(\n instanceId: UniqueId,\n state: MapModeState,\n previousMode: string,\n set: StoreHelpers<MapModeState>['set'],\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\n // Immutable update: clear pending requests\n set({ pendingRequests: mapClear<string, PendingRequest>() });\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 set,\n );\n }\n}\n\n/**\n * Handle authorization decision (immutable update)\n */\nfunction handleAuthorizationDecision(\n instanceId: UniqueId,\n state: MapModeState,\n payload: {\n approved: boolean;\n authId: string;\n owner: string;\n },\n set: StoreHelpers<MapModeState>['set'],\n): void {\n const { approved, authId, owner: decisionOwner } = payload;\n\n // Verify decision is from current mode's owner\n const currentModeOwner = state.modeOwners.get(state.mode);\n if (decisionOwner !== currentModeOwner) {\n logger.warn(\n `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 set,\n );\n } else {\n // Immutable update: remove the rejected request\n set({\n pendingRequests: mapDelete(state.pendingRequests, matchingRequestOwner),\n });\n }\n}\n\n/**\n * Handle mode change request logic (immutable update)\n */\nfunction handleModeChangeRequest(\n instanceId: UniqueId,\n state: MapModeState,\n desiredMode: string,\n requestOwner: string,\n set: StoreHelpers<MapModeState>['set'],\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 // Build immutable updates\n const newModeOwners =\n desiredMode !== DEFAULT_MODE && !desiredModeOwner\n ? mapSet(state.modeOwners, desiredMode, requestOwner)\n : state.modeOwners;\n\n // Clear requester's pending request since mode changed successfully\n const newPendingRequests = mapDelete(state.pendingRequests, requestOwner);\n\n const previousMode = state.mode;\n\n // Immutable update\n set({\n mode: desiredMode,\n modeOwners: newModeOwners,\n pendingRequests: newPendingRequests,\n });\n\n mapModeBus.emit(MapModeEvents.changed, {\n previousMode,\n currentMode: desiredMode,\n id: instanceId,\n });\n\n return;\n }\n\n // Otherwise, send authorization request\n const authId = uuid();\n\n // Immutable update: add pending request\n set({\n pendingRequests: mapSet(state.pendingRequests, requestOwner, {\n authId,\n desiredMode,\n currentMode: state.mode,\n requestOwner,\n }),\n });\n\n mapModeBus.emit(MapModeEvents.changeAuthorization, {\n authId,\n desiredMode,\n currentMode: state.mode,\n id: instanceId,\n });\n}\n\n/**\n * Map mode store\n */\nexport const modeStore = createMapStore<MapModeState, MapModeActions>({\n defaultState: {\n mode: DEFAULT_MODE,\n modeOwners: new Map(),\n pendingRequests: new Map(),\n },\n\n actions: (instanceId) => ({\n requestModeChange: (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\n bus: (instanceId, { get, set }) => {\n // Listen for mode change requests\n const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {\n const { desiredMode, owner: requestOwner, id } = event.payload;\n\n const state = get();\n // Filter: only handle if targeted at this map\n if (id !== instanceId || desiredMode === state.mode) {\n return;\n }\n\n handleModeChangeRequest(\n instanceId,\n state,\n desiredMode,\n requestOwner,\n set,\n );\n });\n\n // Listen for authorization decisions\n const unsubDecision = mapModeBus.on(\n MapModeEvents.changeDecision,\n (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(\n instanceId,\n get(),\n { approved, authId, owner },\n set,\n );\n },\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 const state = get();\n // When mode owner changes to default mode, handle pending requests\n if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) {\n handlePendingRequestsOnDefaultMode(\n instanceId,\n state,\n previousMode,\n set,\n );\n }\n });\n\n return () => {\n unsubRequest();\n unsubDecision();\n unsubChanged();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get the current mode for a map instance\n */\nexport function getMode(mapId: UniqueId): string {\n return modeStore.get(mapId).mode;\n}\n\n/**\n * Hook for current mode value\n */\nexport function useMode(mapId: UniqueId): string {\n return modeStore.useSelector(mapId, (state) => state.mode);\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 return state.modeOwners.get(state.mode);\n}\n\n/**\n * Check if a given owner is registered as the owner of any mode.\n * This includes both active mode owners and pending mode requests.\n * @internal - For internal map-toolkit use only\n */\nexport function isRegisteredModeOwner(\n instanceId: UniqueId,\n owner: string,\n): boolean {\n const state = modeStore.get(instanceId);\n\n // Check active mode owners\n for (const modeOwner of state.modeOwners.values()) {\n if (modeOwner === owner) {\n return true;\n }\n }\n\n // Check pending mode requests (owner is the key in pendingRequests map)\n if (state.pendingRequests.has(owner)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Manually clear map mode state for a specific instanceId.\n */\nexport function clearMapModeState(instanceId: UniqueId): void {\n modeStore.clear(instanceId);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":["requestsToReject: PendingRequest[]","matchingRequestOwner: string | null","matchingRequest: PendingRequest | null"],"sources":["../../src/map-mode/store.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Map Mode Store\n *\n * Manages mode state with ownership-based authorization.\n *\n * @example\n * ```tsx\n * import { modeStore } from '@accelint/map-toolkit/map-mode';\n *\n * function MapControls({ mapId }) {\n * const { state, requestModeChange } = modeStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Current mode: {state.mode}</p>\n * <button onClick={() => requestModeChange('draw', 'draw-layer')}>\n * Draw Mode\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { uuid } from '@accelint/core';\nimport { getLogger } from '@accelint/logger';\nimport {\n createMapStore,\n mapClear,\n mapDelete,\n mapSet,\n} from '../shared/create-map-store';\nimport { MapModeEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { StoreHelpers } from '../shared/create-map-store';\nimport type { MapModeEventType, ModeChangeDecisionPayload } from './types';\n\nconst logger = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[MapMode]',\n pretty: true,\n});\n\nconst DEFAULT_MODE = 'default';\n\n/**\n * Typed event bus instance for map mode events.\n */\nconst mapModeBus = Broadcast.getInstance<MapModeEventType>();\n\n/**\n * Internal type for tracking pending authorization requests.\n */\ntype PendingRequest = {\n authId: string;\n desiredMode: string;\n currentMode: string;\n requestOwner: string;\n};\n\n/**\n * State shape for map mode\n */\ntype MapModeState = {\n mode: string;\n modeOwners: Map<string, string>;\n pendingRequests: Map<string, PendingRequest>;\n};\n\n/**\n * Actions for map mode\n */\ntype MapModeActions = {\n /** Request a mode change */\n requestModeChange: (desiredMode: string, requestOwner: string) => void;\n};\n\n/**\n * Determine if a mode change request should be auto-accepted without authorization.\n *\n * Auto-accept conditions:\n * - Owner returning to default mode\n * - Owner switching between their own modes\n * - No ownership conflicts exist\n * - Entering an owned mode from default mode\n *\n * @param state - Current mode state\n * @param desiredMode - The mode being requested\n * @param requestOwner - The component requesting the mode change\n * @returns True if the request should be auto-accepted\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 * Approve a request and reject all others (immutable update)\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 set: StoreHelpers<MapModeState>['set'],\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 // Build immutable updates: clear pending requests, update owners\n const newModeOwners =\n approvedRequest.desiredMode !== DEFAULT_MODE\n ? mapSet(\n state.modeOwners,\n approvedRequest.desiredMode,\n approvedRequest.requestOwner,\n )\n : state.modeOwners;\n\n // Immutable update: clear pending requests, update owners, change mode\n set({\n mode: approvedRequest.desiredMode,\n pendingRequests: mapClear<string, PendingRequest>(),\n modeOwners: newModeOwners,\n });\n\n // Emit mode changed event\n mapModeBus.emit(MapModeEvents.changed, {\n previousMode: state.mode,\n currentMode: approvedRequest.desiredMode,\n id: instanceId,\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 (immutable update)\n */\nfunction handlePendingRequestsOnDefaultMode(\n instanceId: UniqueId,\n state: MapModeState,\n previousMode: string,\n set: StoreHelpers<MapModeState>['set'],\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\n // Immutable update: clear pending requests\n set({ pendingRequests: mapClear<string, PendingRequest>() });\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 set,\n );\n }\n}\n\n/**\n * Handle authorization decision (immutable update)\n */\nfunction handleAuthorizationDecision(\n instanceId: UniqueId,\n state: MapModeState,\n payload: {\n approved: boolean;\n authId: string;\n owner: string;\n },\n set: StoreHelpers<MapModeState>['set'],\n): void {\n const { approved, authId, owner: decisionOwner } = payload;\n\n // Verify decision is from current mode's owner\n const currentModeOwner = state.modeOwners.get(state.mode);\n if (decisionOwner !== currentModeOwner) {\n logger.warn(\n `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 set,\n );\n } else {\n // Immutable update: remove the rejected request\n set({\n pendingRequests: mapDelete(state.pendingRequests, matchingRequestOwner),\n });\n }\n}\n\n/**\n * Handle mode change request logic (immutable update)\n */\nfunction handleModeChangeRequest(\n instanceId: UniqueId,\n state: MapModeState,\n desiredMode: string,\n requestOwner: string,\n set: StoreHelpers<MapModeState>['set'],\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 // Build immutable updates\n const newModeOwners =\n desiredMode !== DEFAULT_MODE && !desiredModeOwner\n ? mapSet(state.modeOwners, desiredMode, requestOwner)\n : state.modeOwners;\n\n // Clear requester's pending request since mode changed successfully\n const newPendingRequests = mapDelete(state.pendingRequests, requestOwner);\n\n const previousMode = state.mode;\n\n // Immutable update\n set({\n mode: desiredMode,\n modeOwners: newModeOwners,\n pendingRequests: newPendingRequests,\n });\n\n mapModeBus.emit(MapModeEvents.changed, {\n previousMode,\n currentMode: desiredMode,\n id: instanceId,\n });\n\n return;\n }\n\n // Otherwise, send authorization request\n const authId = uuid();\n\n // Immutable update: add pending request\n set({\n pendingRequests: mapSet(state.pendingRequests, requestOwner, {\n authId,\n desiredMode,\n currentMode: state.mode,\n requestOwner,\n }),\n });\n\n mapModeBus.emit(MapModeEvents.changeAuthorization, {\n authId,\n desiredMode,\n currentMode: state.mode,\n id: instanceId,\n });\n}\n\n/**\n * Map mode store\n */\nexport const modeStore = createMapStore<MapModeState, MapModeActions>({\n defaultState: {\n mode: DEFAULT_MODE,\n modeOwners: new Map(),\n pendingRequests: new Map(),\n },\n\n actions: (instanceId) => ({\n requestModeChange: (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\n bus: (instanceId, { get, set }) => {\n // Listen for mode change requests\n const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {\n const { desiredMode, owner: requestOwner, id } = event.payload;\n\n const state = get();\n // Filter: only handle if targeted at this map\n if (id !== instanceId || desiredMode === state.mode) {\n return;\n }\n\n handleModeChangeRequest(\n instanceId,\n state,\n desiredMode,\n requestOwner,\n set,\n );\n });\n\n // Listen for authorization decisions\n const unsubDecision = mapModeBus.on(\n MapModeEvents.changeDecision,\n (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(\n instanceId,\n get(),\n { approved, authId, owner },\n set,\n );\n },\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 const state = get();\n // When mode owner changes to default mode, handle pending requests\n if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) {\n handlePendingRequestsOnDefaultMode(\n instanceId,\n state,\n previousMode,\n set,\n );\n }\n });\n\n return () => {\n unsubRequest();\n unsubDecision();\n unsubChanged();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get the current mode for a map instance.\n *\n * @param mapId - Unique identifier for the map instance\n * @returns The current active mode string\n *\n * @example\n * ```typescript\n * import { getMode } from '@accelint/map-toolkit/map-mode';\n *\n * const currentMode = getMode(mapId);\n * console.log('Current mode:', currentMode); // 'default', 'drawing', etc.\n * ```\n */\nexport function getMode(mapId: UniqueId): string {\n return modeStore.get(mapId).mode;\n}\n\n/**\n * Hook for current mode value.\n *\n * **Internal use only** - not exported from the public API.\n * Use `useMapMode` instead for better ergonomics and MapContext integration.\n *\n * @param mapId - Unique identifier for the map instance\n * @returns The current active mode string\n */\nexport function useMode(mapId: UniqueId): string {\n return modeStore.useSelector(mapId, (state) => state.mode);\n}\n\n/**\n * Get the owner of the current mode for a given map instance.\n *\n * @internal - For internal map-toolkit use only\n * @param instanceId - Unique identifier for the map instance\n * @returns The owner ID of the current mode, or undefined if unowned\n */\nexport function getCurrentModeOwner(instanceId: UniqueId): string | undefined {\n const state = modeStore.get(instanceId);\n return state.modeOwners.get(state.mode);\n}\n\n/**\n * Check if a given owner is registered as the owner of any mode.\n * This includes both active mode owners and pending mode requests.\n *\n * @internal - For internal map-toolkit use only\n * @param instanceId - Unique identifier for the map instance\n * @param owner - The owner ID to check\n * @returns True if the owner is registered for any mode\n */\nexport function isRegisteredModeOwner(\n instanceId: UniqueId,\n owner: string,\n): boolean {\n const state = modeStore.get(instanceId);\n\n // Check active mode owners\n for (const modeOwner of state.modeOwners.values()) {\n if (modeOwner === owner) {\n return true;\n }\n }\n\n // Check pending mode requests (owner is the key in pendingRequests map)\n if (state.pendingRequests.has(owner)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Manually clear map mode state for a specific map instance.\n *\n * Removes all mode ownership data, pending requests, and resets to default state.\n * This is typically not needed as cleanup happens automatically.\n * Use only in advanced scenarios where manual cleanup is required.\n *\n * @param instanceId - Unique identifier for the map instance to clear\n * @returns void\n *\n * @example\n * ```typescript\n * import { clearMapModeState } from '@accelint/map-toolkit/map-mode';\n *\n * // Manual cleanup when destroying a map\n * clearMapModeState(mapId);\n * ```\n */\nexport function clearMapModeState(instanceId: UniqueId): void {\n modeStore.clear(instanceId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,MAAM,SAAS,UAAU;CACvB,SACE,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,aAAa;CACpE,OAAO;CACP,QAAQ;CACR,QAAQ;CACT,CAAC;AAEF,MAAM,eAAe;;;;AAKrB,MAAM,aAAa,UAAU,aAA+B;;;;;;;;;;;;;;;AA2C5D,SAAS,wBACP,OACA,aACA,cACS;CACT,MAAM,mBAAmB,MAAM,WAAW,IAAI,MAAM,KAAK;CACzD,MAAM,mBAAmB,MAAM,WAAW,IAAI,YAAY;AAG1D,KAAI,gBAAgB,gBAAgB,iBAAiB,iBACnD,QAAO;AAIT,KAAI,iBAAiB,iBACnB,QAAO;AAIT,KAAI,EAAE,oBAAoB,kBACxB,QAAO;AAIT,KAAI,MAAM,SAAS,gBAAgB,iBAAiB,iBAClD,QAAO;AAGT,QAAO;;;;;AAMT,SAAS,8BACP,YACA,OACA,iBACA,eACA,eACA,QACA,cACA,KACM;CAEN,MAAMA,mBAAqC,EAAE;AAC7C,MAAK,MAAM,WAAW,MAAM,gBAAgB,QAAQ,CAClD,KAAI,QAAQ,WAAW,cACrB,kBAAiB,KAAK,QAAQ;CAKlC,MAAM,gBACJ,gBAAgB,gBAAgB,eAC5B,OACE,MAAM,YACN,gBAAgB,aAChB,gBAAgB,aACjB,GACD,MAAM;AAGZ,KAAI;EACF,MAAM,gBAAgB;EACtB,iBAAiB,UAAkC;EACnD,YAAY;EACb,CAAC;AAGF,YAAW,KAAK,cAAc,SAAS;EACrC,cAAc,MAAM;EACpB,aAAa,gBAAgB;EAC7B,IAAI;EACL,CAAC;AAGF,KAAI,aACF,YAAW,KAAK,cAAc,gBAAgB;EAC5C,QAAQ,gBAAgB;EACxB,UAAU;EACV,OAAO;EACP;EACA,IAAI;EACL,CAAC;AAIJ,MAAK,MAAM,WAAW,iBACpB,YAAW,KAAK,cAAc,gBAAgB;EAC5C,QAAQ,QAAQ;EAChB,UAAU;EACV,OAAO;EACP,QAAQ;EACR,IAAI;EACL,CAAC;;;;;AAON,SAAS,mCACP,YACA,OACA,cACA,KACM;CACN,MAAM,aAAa,MAAM,KAAK,MAAM,gBAAgB,QAAQ,CAAC,CAAC;AAC9D,KAAI,CAAC,WACH;CAGF,MAAM,oBAAoB,MAAM,WAAW,IAAI,aAAa;AAE5D,KAAI,CAAC,kBACH;AAIF,KAAI,WAAW,gBAAgB,cAAc;EAC3C,MAAM,cAAc,MAAM,KAAK,MAAM,gBAAgB,QAAQ,CAAC;AAG9D,MAAI,EAAE,iBAAiB,UAAkC,EAAE,CAAC;AAE5D,OAAK,MAAM,WAAW,YACpB,YAAW,KAAK,cAAc,gBAAgB;GAC5C,QAAQ,QAAQ;GAChB,UAAU;GACV,OAAO;GACP,QAAQ;GACR,IAAI;GACL,CAAqC;OAIxC,+BACE,YACA,OACA,YACA,WAAW,QACX,mBACA,qDACA,MACA,IACD;;;;;AAOL,SAAS,4BACP,YACA,OACA,SAKA,KACM;CACN,MAAM,EAAE,UAAU,QAAQ,OAAO,kBAAkB;CAGnD,MAAM,mBAAmB,MAAM,WAAW,IAAI,MAAM,KAAK;AACzD,KAAI,kBAAkB,kBAAkB;AACtC,SAAO,KACL,gCAAgC,cAAc,qCAAqC,MAAM,KAAK,YAAY,oBAAoB,OAAO,GACtI;AACD;;CAIF,IAAIC,uBAAsC;CAC1C,IAAIC,kBAAyC;AAE7C,MAAK,MAAM,CAAC,cAAc,YAAY,MAAM,gBAAgB,SAAS,CACnE,KAAI,QAAQ,WAAW,QAAQ;AAC7B,yBAAuB;AACvB,oBAAkB;AAClB;;AAIJ,KAAI,EAAE,mBAAmB,sBACvB;AAGF,KAAI,SACF,+BACE,YACA,OACA,iBACA,QACA,eACA,IACA,OACA,IACD;KAGD,KAAI,EACF,iBAAiB,UAAU,MAAM,iBAAiB,qBAAqB,EACxE,CAAC;;;;;AAON,SAAS,wBACP,YACA,OACA,aACA,cACA,KACM;CACN,MAAM,mBAAmB,MAAM,WAAW,IAAI,YAAY;AAG1D,KAAI,wBAAwB,OAAO,aAAa,aAAa,EAAE;EAE7D,MAAM,gBACJ,gBAAgB,gBAAgB,CAAC,mBAC7B,OAAO,MAAM,YAAY,aAAa,aAAa,GACnD,MAAM;EAGZ,MAAM,qBAAqB,UAAU,MAAM,iBAAiB,aAAa;EAEzE,MAAM,eAAe,MAAM;AAG3B,MAAI;GACF,MAAM;GACN,YAAY;GACZ,iBAAiB;GAClB,CAAC;AAEF,aAAW,KAAK,cAAc,SAAS;GACrC;GACA,aAAa;GACb,IAAI;GACL,CAAC;AAEF;;CAIF,MAAM,SAAS,MAAM;AAGrB,KAAI,EACF,iBAAiB,OAAO,MAAM,iBAAiB,cAAc;EAC3D;EACA;EACA,aAAa,MAAM;EACnB;EACD,CAAC,EACH,CAAC;AAEF,YAAW,KAAK,cAAc,qBAAqB;EACjD;EACA;EACA,aAAa,MAAM;EACnB,IAAI;EACL,CAAC;;;;;AAMJ,MAAa,YAAY,eAA6C;CACpE,cAAc;EACZ,MAAM;EACN,4BAAY,IAAI,KAAK;EACrB,iCAAiB,IAAI,KAAK;EAC3B;CAED,UAAU,gBAAgB,EACxB,oBAAoB,aAAqB,iBAAyB;EAChE,MAAM,qBAAqB,YAAY,MAAM;EAC7C,MAAM,sBAAsB,aAAa,MAAM;AAE/C,MAAI,CAAC,mBACH,OAAM,IAAI,MAAM,mDAAmD;AAErE,MAAI,CAAC,oBACH,OAAM,IAAI,MAAM,oDAAoD;AAGtE,aAAW,KAAK,cAAc,eAAe;GAC3C,aAAa;GACb,OAAO;GACP,IAAI;GACL,CAAC;IAEL;CAED,MAAM,YAAY,EAAE,KAAK,UAAU;EAEjC,MAAM,eAAe,WAAW,GAAG,cAAc,gBAAgB,UAAU;GACzE,MAAM,EAAE,aAAa,OAAO,cAAc,OAAO,MAAM;GAEvD,MAAM,QAAQ,KAAK;AAEnB,OAAI,OAAO,cAAc,gBAAgB,MAAM,KAC7C;AAGF,2BACE,YACA,OACA,aACA,cACA,IACD;IACD;EAGF,MAAM,gBAAgB,WAAW,GAC/B,cAAc,iBACb,UAAU;GACT,MAAM,EAAE,IAAI,UAAU,QAAQ,UAAU,MAAM;AAG9C,OAAI,OAAO,WACT;AAGF,+BACE,YACA,KAAK,EACL;IAAE;IAAU;IAAQ;IAAO,EAC3B,IACD;IAEJ;EAGD,MAAM,eAAe,WAAW,GAAG,cAAc,UAAU,UAAU;GACnE,MAAM,EAAE,aAAa,cAAc,OAAO,MAAM;AAGhD,OAAI,OAAO,WACT;GAGF,MAAM,QAAQ,KAAK;AAEnB,OAAI,gBAAgB,gBAAgB,MAAM,gBAAgB,OAAO,EAC/D,oCACE,YACA,OACA,cACA,IACD;IAEH;AAEF,eAAa;AACX,iBAAc;AACd,kBAAe;AACf,iBAAc;;;CAGnB,CAAC;;;;;;;;;;;;;;;AAoBF,SAAgB,QAAQ,OAAyB;AAC/C,QAAO,UAAU,IAAI,MAAM,CAAC;;;;;;;;;;;AAY9B,SAAgB,QAAQ,OAAyB;AAC/C,QAAO,UAAU,YAAY,QAAQ,UAAU,MAAM,KAAK;;;;;;;;;AAU5D,SAAgB,oBAAoB,YAA0C;CAC5E,MAAM,QAAQ,UAAU,IAAI,WAAW;AACvC,QAAO,MAAM,WAAW,IAAI,MAAM,KAAK;;;;;;;;;;;AAYzC,SAAgB,sBACd,YACA,OACS;CACT,MAAM,QAAQ,UAAU,IAAI,WAAW;AAGvC,MAAK,MAAM,aAAa,MAAM,WAAW,QAAQ,CAC/C,KAAI,cAAc,MAChB,QAAO;AAKX,KAAI,MAAM,gBAAgB,IAAI,MAAM,CAClC,QAAO;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,kBAAkB,YAA4B;AAC5D,WAAU,MAAM,WAAW"}
@@ -16,6 +16,11 @@ import { UniqueId } from "@accelint/core";
16
16
  /**
17
17
  * Create a new Map with an entry added or updated (immutable).
18
18
  *
19
+ * @param map - The Map to copy
20
+ * @param key - The key to set
21
+ * @param value - The value to set
22
+ * @returns A new Map with the entry added/updated
23
+ *
19
24
  * @example
20
25
  * ```ts
21
26
  * const newMap = mapSet(state.cursorOwners, 'draw-layer', 'crosshair');
@@ -26,6 +31,10 @@ declare function mapSet<K, V>(map: Map<K, V>, key: K, value: V): Map<K, V>;
26
31
  /**
27
32
  * Create a new Map with an entry removed (immutable).
28
33
  *
34
+ * @param map - The Map to copy
35
+ * @param key - The key to remove
36
+ * @returns A new Map with the entry removed
37
+ *
29
38
  * @example
30
39
  * ```ts
31
40
  * const newMap = mapDelete(state.cursorOwners, 'draw-layer');
@@ -36,6 +45,8 @@ declare function mapDelete<K, V>(map: Map<K, V>, key: K): Map<K, V>;
36
45
  /**
37
46
  * Create a new empty Map (immutable replacement for Map.clear()).
38
47
  *
48
+ * @returns A new empty Map
49
+ *
39
50
  * @example
40
51
  * ```ts
41
52
  * set({ pendingRequests: mapClear<string, PendingRequest>() });
@@ -166,6 +177,9 @@ type MapStore<TState, TActions> = {
166
177
  /**
167
178
  * Creates a store for managing state across multiple map instances.
168
179
  *
180
+ * @param config - Store configuration including default state, actions, and optional bus setup
181
+ * @returns A MapStore instance with hooks and methods for accessing/updating state
182
+ *
169
183
  * @example
170
184
  * ```ts
171
185
  * const cursorStore = createMapStore({
@@ -17,6 +17,11 @@ import { useRef, useSyncExternalStore } from "react";
17
17
  /**
18
18
  * Create a new Map with an entry added or updated (immutable).
19
19
  *
20
+ * @param map - The Map to copy
21
+ * @param key - The key to set
22
+ * @param value - The value to set
23
+ * @returns A new Map with the entry added/updated
24
+ *
20
25
  * @example
21
26
  * ```ts
22
27
  * const newMap = mapSet(state.cursorOwners, 'draw-layer', 'crosshair');
@@ -31,6 +36,10 @@ function mapSet(map, key, value) {
31
36
  /**
32
37
  * Create a new Map with an entry removed (immutable).
33
38
  *
39
+ * @param map - The Map to copy
40
+ * @param key - The key to remove
41
+ * @returns A new Map with the entry removed
42
+ *
34
43
  * @example
35
44
  * ```ts
36
45
  * const newMap = mapDelete(state.cursorOwners, 'draw-layer');
@@ -45,6 +54,8 @@ function mapDelete(map, key) {
45
54
  /**
46
55
  * Create a new empty Map (immutable replacement for Map.clear()).
47
56
  *
57
+ * @returns A new empty Map
58
+ *
48
59
  * @example
49
60
  * ```ts
50
61
  * set({ pendingRequests: mapClear<string, PendingRequest>() });
@@ -56,6 +67,9 @@ function mapClear() {
56
67
  /**
57
68
  * Creates a store for managing state across multiple map instances.
58
69
  *
70
+ * @param config - Store configuration including default state, actions, and optional bus setup
71
+ * @returns A MapStore instance with hooks and methods for accessing/updating state
72
+ *
59
73
  * @example
60
74
  * ```ts
61
75
  * const cursorStore = createMapStore({
@@ -131,7 +145,10 @@ function createMapStore(config) {
131
145
  return instance.actions;
132
146
  }
133
147
  /**
134
- * Clean up instance when last subscriber unmounts
148
+ * Clean up instance when last subscriber unmounts.
149
+ *
150
+ * @param mapId - Unique identifier for the map instance
151
+ * @param instance - The instance to clean up
135
152
  */
136
153
  function cleanupInstance(mapId, instance) {
137
154
  if (onCleanup) onCleanup(mapId, instance.state);
@@ -170,7 +187,10 @@ function createMapStore(config) {
170
187
  return defaultState;
171
188
  }
172
189
  /**
173
- * Main hook - returns state and actions
190
+ * Main hook - returns state and actions.
191
+ *
192
+ * @param mapId - Unique identifier for the map instance
193
+ * @returns Object containing state and all actions
174
194
  */
175
195
  function use(mapId) {
176
196
  return {
@@ -184,6 +204,10 @@ function createMapStore(config) {
184
204
  * Note: The selector function is intentionally NOT tracked as a dependency.
185
205
  * This prevents infinite re-render loops when using inline arrow functions.
186
206
  * If you need dynamic selector behavior, use the `use()` hook with `useMemo`.
207
+ *
208
+ * @param mapId - Unique identifier for the map instance
209
+ * @param selector - Function to select derived state
210
+ * @returns The selected value
187
211
  */
188
212
  function useSelector(mapId, selector) {
189
213
  const cache = useRef(null);