@accelint/map-toolkit 0.5.0 → 1.0.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 (273) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/catalog-info.yaml +6 -3
  3. package/dist/camera/index.d.ts +2 -2
  4. package/dist/camera/index.js +2 -2
  5. package/dist/camera/store.d.ts +120 -0
  6. package/dist/camera/store.js +279 -0
  7. package/dist/camera/store.js.map +1 -0
  8. package/dist/deckgl/base-map/constants.d.ts +9 -7
  9. package/dist/deckgl/base-map/constants.js +9 -7
  10. package/dist/deckgl/base-map/constants.js.map +1 -1
  11. package/dist/deckgl/base-map/controls.js +2 -0
  12. package/dist/deckgl/base-map/controls.js.map +1 -1
  13. package/dist/deckgl/base-map/index.d.ts +2 -0
  14. package/dist/deckgl/base-map/index.js +14 -16
  15. package/dist/deckgl/base-map/index.js.map +1 -1
  16. package/dist/deckgl/base-map/provider.js +1 -1
  17. package/dist/deckgl/base-map/types.d.ts +6 -1
  18. package/dist/deckgl/index.d.ts +8 -2
  19. package/dist/deckgl/index.js +6 -2
  20. package/dist/deckgl/saved-viewports/index.d.ts +1 -1
  21. package/dist/deckgl/saved-viewports/index.js +1 -2
  22. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  23. package/dist/deckgl/saved-viewports/storage.js +10 -2
  24. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  25. package/dist/deckgl/shapes/display-shape-layer/constants.js +58 -0
  26. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -0
  27. package/dist/{node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/assert.js → deckgl/shapes/display-shape-layer/fiber.d.ts} +11 -7
  28. package/dist/{packages/hotkey-manager/dist/lib/is-client/index.js → deckgl/shapes/display-shape-layer/fiber.js} +6 -7
  29. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -0
  30. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +210 -0
  31. package/dist/deckgl/shapes/display-shape-layer/index.js +449 -0
  32. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -0
  33. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +102 -0
  34. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js.map +1 -0
  35. package/dist/deckgl/shapes/display-shape-layer/store.js +102 -0
  36. package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -0
  37. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +126 -0
  38. package/dist/{node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/layer.js → deckgl/shapes/display-shape-layer/types.js} +0 -3
  39. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +89 -0
  40. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +88 -0
  41. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js.map +1 -0
  42. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +50 -0
  43. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -0
  44. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +133 -0
  45. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +250 -0
  46. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -0
  47. package/dist/deckgl/shapes/draw-shape-layer/constants.js +46 -0
  48. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -0
  49. package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +92 -0
  50. package/dist/deckgl/shapes/draw-shape-layer/events.js +56 -0
  51. package/dist/deckgl/shapes/draw-shape-layer/events.js.map +1 -0
  52. package/dist/deckgl/shapes/draw-shape-layer/fiber.d.ts +11 -0
  53. package/dist/{node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/get-bounds.js → deckgl/shapes/draw-shape-layer/fiber.js} +5 -4
  54. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -0
  55. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +53 -0
  56. package/dist/deckgl/shapes/draw-shape-layer/index.js +95 -0
  57. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -0
  58. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +51 -0
  59. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -0
  60. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +73 -0
  61. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -0
  62. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +87 -0
  63. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -0
  64. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +88 -0
  65. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -0
  66. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +77 -0
  67. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -0
  68. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +64 -0
  69. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -0
  70. package/dist/deckgl/shapes/draw-shape-layer/store.js +175 -0
  71. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -0
  72. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +86 -0
  73. package/dist/{node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/fly-to-viewport.js → deckgl/shapes/draw-shape-layer/types.js} +1 -1
  74. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +82 -0
  75. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +112 -0
  76. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js.map +1 -0
  77. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +147 -0
  78. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -0
  79. package/dist/deckgl/shapes/edit-shape-layer/constants.js +41 -0
  80. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -0
  81. package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +92 -0
  82. package/dist/deckgl/shapes/edit-shape-layer/events.js +56 -0
  83. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -0
  84. package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +13 -0
  85. package/dist/{node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/web-mercator-viewport.js → deckgl/shapes/edit-shape-layer/fiber.js} +1 -3
  86. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +63 -0
  87. package/dist/deckgl/shapes/edit-shape-layer/index.js +162 -0
  88. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -0
  89. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +154 -0
  90. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -0
  91. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +147 -0
  92. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -0
  93. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +87 -0
  94. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -0
  95. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +61 -0
  96. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -0
  97. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +109 -0
  98. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -0
  99. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +289 -0
  100. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -0
  101. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +121 -0
  102. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -0
  103. package/dist/deckgl/shapes/edit-shape-layer/store.js +194 -0
  104. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -0
  105. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +93 -0
  106. package/dist/{node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/normalize-viewport-props.js → deckgl/shapes/edit-shape-layer/types.js} +1 -1
  107. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +82 -0
  108. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +114 -0
  109. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -0
  110. package/dist/deckgl/shapes/index.d.ts +29 -0
  111. package/dist/deckgl/shapes/index.js +27 -0
  112. package/dist/deckgl/shapes/shared/constants.d.ts +73 -0
  113. package/dist/deckgl/shapes/shared/constants.js +273 -0
  114. package/dist/deckgl/shapes/shared/constants.js.map +1 -0
  115. package/dist/deckgl/shapes/shared/events.d.ts +54 -0
  116. package/dist/{packages/hotkey-manager/dist/lib/is-mac/index.js → deckgl/shapes/shared/events.js} +10 -6
  117. package/dist/deckgl/shapes/shared/events.js.map +1 -0
  118. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +84 -0
  119. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -0
  120. package/dist/deckgl/shapes/shared/types.d.ts +317 -0
  121. package/dist/deckgl/shapes/shared/types.js +83 -0
  122. package/dist/deckgl/shapes/shared/types.js.map +1 -0
  123. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +128 -0
  124. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -0
  125. package/dist/deckgl/shapes/shared/utils/layer-config.js +50 -0
  126. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -0
  127. package/dist/deckgl/shapes/shared/utils/mode-utils.js +113 -0
  128. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -0
  129. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +57 -0
  130. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -0
  131. package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +64 -0
  132. package/dist/deckgl/shapes/shared/utils/style-utils.js +101 -0
  133. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -0
  134. package/dist/deckgl/symbol-layer/index.d.ts +1 -1
  135. package/dist/deckgl/text-layer/default-settings.js +4 -24
  136. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  137. package/dist/deckgl/text-settings.d.ts +77 -0
  138. package/dist/deckgl/text-settings.js +83 -0
  139. package/dist/deckgl/text-settings.js.map +1 -0
  140. package/dist/map-cursor/index.d.ts +2 -2
  141. package/dist/map-cursor/index.js +2 -2
  142. package/dist/map-cursor/store.d.ts +32 -61
  143. package/dist/map-cursor/store.js +165 -294
  144. package/dist/map-cursor/store.js.map +1 -1
  145. package/dist/map-cursor/use-map-cursor.d.ts +5 -2
  146. package/dist/map-cursor/use-map-cursor.js +33 -15
  147. package/dist/map-cursor/use-map-cursor.js.map +1 -1
  148. package/dist/map-mode/index.d.ts +2 -2
  149. package/dist/map-mode/index.js +2 -2
  150. package/dist/map-mode/store.d.ts +36 -37
  151. package/dist/map-mode/store.js +131 -237
  152. package/dist/map-mode/store.js.map +1 -1
  153. package/dist/map-mode/use-map-mode.js +6 -5
  154. package/dist/map-mode/use-map-mode.js.map +1 -1
  155. package/dist/maplibre/index.d.ts +2 -2
  156. package/dist/maplibre/index.js +2 -2
  157. package/dist/shared/constants.d.ts +19 -0
  158. package/dist/{maplibre → shared}/constants.js +9 -3
  159. package/dist/shared/constants.js.map +1 -0
  160. package/dist/shared/create-map-store.d.ts +202 -0
  161. package/dist/shared/create-map-store.js +223 -0
  162. package/dist/shared/create-map-store.js.map +1 -0
  163. package/dist/shared/units.d.ts +39 -0
  164. package/dist/shared/units.js +49 -0
  165. package/dist/shared/units.js.map +1 -0
  166. package/dist/viewport/index.d.ts +3 -3
  167. package/dist/viewport/index.js +3 -3
  168. package/dist/viewport/store.d.ts +69 -0
  169. package/dist/viewport/store.js +125 -0
  170. package/dist/viewport/store.js.map +1 -0
  171. package/dist/viewport/types.d.ts +2 -2
  172. package/dist/viewport/utils.js +2 -2
  173. package/dist/viewport/utils.js.map +1 -1
  174. package/dist/viewport/viewport-size.js +2 -2
  175. package/dist/viewport/viewport-size.js.map +1 -1
  176. package/package.json +60 -26
  177. package/dist/_virtual/rolldown_runtime.js +0 -22
  178. package/dist/camera/use-camera-state.d.ts +0 -153
  179. package/dist/camera/use-camera-state.js +0 -419
  180. package/dist/camera/use-camera-state.js.map +0 -1
  181. package/dist/decorators/deckgl.d.ts +0 -19
  182. package/dist/decorators/deckgl.js +0 -32
  183. package/dist/decorators/deckgl.js.map +0 -1
  184. package/dist/maplibre/constants.d.ts +0 -13
  185. package/dist/maplibre/constants.js.map +0 -1
  186. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/assert.js.map +0 -1
  187. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/fit-bounds.js +0 -63
  188. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/fit-bounds.js.map +0 -1
  189. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/get-bounds.js.map +0 -1
  190. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/index.js +0 -19
  191. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/math-utils.js +0 -25
  192. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/math-utils.js.map +0 -1
  193. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/web-mercator-utils.js +0 -59
  194. package/dist/node_modules/.pnpm/@math.gl_web-mercator@4.1.0/node_modules/@math.gl/web-mercator/dist/web-mercator-utils.js.map +0 -1
  195. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/attribution-control.js +0 -29
  196. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/attribution-control.js.map +0 -1
  197. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/fullscreen-control.js +0 -29
  198. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/fullscreen-control.js.map +0 -1
  199. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/geolocate-control.js +0 -54
  200. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/geolocate-control.js.map +0 -1
  201. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/logo-control.js +0 -29
  202. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/logo-control.js.map +0 -1
  203. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/map.js +0 -91
  204. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/map.js.map +0 -1
  205. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/marker.js +0 -88
  206. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/marker.js.map +0 -1
  207. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/navigation-control.js +0 -29
  208. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/navigation-control.js.map +0 -1
  209. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/popup.js +0 -69
  210. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/popup.js.map +0 -1
  211. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/scale-control.js +0 -35
  212. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/scale-control.js.map +0 -1
  213. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/source.js +0 -15
  214. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/terrain-control.js +0 -29
  215. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/terrain-control.js.map +0 -1
  216. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/use-control.js +0 -40
  217. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/use-control.js.map +0 -1
  218. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/use-map.js +0 -23
  219. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/components/use-map.js.map +0 -1
  220. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/index.js +0 -27
  221. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/maplibre/create-ref.js +0 -57
  222. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/maplibre/create-ref.js.map +0 -1
  223. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/maplibre/maplibre.js +0 -343
  224. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/maplibre/maplibre.js.map +0 -1
  225. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/apply-react-style.js +0 -28
  226. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/apply-react-style.js.map +0 -1
  227. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/compare-class-names.js +0 -31
  228. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/compare-class-names.js.map +0 -1
  229. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/deep-equal.js +0 -57
  230. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/deep-equal.js.map +0 -1
  231. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/set-globals.js +0 -30
  232. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/set-globals.js.map +0 -1
  233. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/style-utils.js +0 -53
  234. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/style-utils.js.map +0 -1
  235. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/transform.js +0 -52
  236. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/transform.js.map +0 -1
  237. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/use-isomorphic-layout-effect.js +0 -22
  238. package/dist/node_modules/.pnpm/@vis.gl_react-maplibre@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/@vis.gl/react-maplibre/dist/utils/use-isomorphic-layout-effect.js.map +0 -1
  239. package/dist/node_modules/.pnpm/immer@10.2.0/node_modules/immer/dist/immer.js +0 -812
  240. package/dist/node_modules/.pnpm/immer@10.2.0/node_modules/immer/dist/immer.js.map +0 -1
  241. package/dist/node_modules/.pnpm/radashi@12.7.1/node_modules/radashi/dist/radashi.js +0 -35
  242. package/dist/node_modules/.pnpm/radashi@12.7.1/node_modules/radashi/dist/radashi.js.map +0 -1
  243. package/dist/node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/cjs/react-dom.development.js +0 -195
  244. package/dist/node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/cjs/react-dom.development.js.map +0 -1
  245. package/dist/node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/cjs/react-dom.production.js +0 -76
  246. package/dist/node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/cjs/react-dom.production.js.map +0 -1
  247. package/dist/node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/index.js +0 -39
  248. package/dist/node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/index.js.map +0 -1
  249. package/dist/node_modules/.pnpm/react-map-gl@8.1.0_maplibre-gl@5.15.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/react-map-gl/dist/maplibre.js +0 -16
  250. package/dist/node_modules/.pnpm/zustand@5.0.9_@types_react@19.2.7_immer@10.2.0_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/middleware/immer.js +0 -27
  251. package/dist/node_modules/.pnpm/zustand@5.0.9_@types_react@19.2.7_immer@10.2.0_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/middleware/immer.js.map +0 -1
  252. package/dist/node_modules/.pnpm/zustand@5.0.9_@types_react@19.2.7_immer@10.2.0_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/vanilla.js +0 -45
  253. package/dist/node_modules/.pnpm/zustand@5.0.9_@types_react@19.2.7_immer@10.2.0_react@19.2.3_use-sync-external-store@1.6.0_react@19.2.3_/node_modules/zustand/esm/vanilla.js.map +0 -1
  254. package/dist/packages/hotkey-manager/dist/actions/register-hotkey/index.js +0 -78
  255. package/dist/packages/hotkey-manager/dist/actions/register-hotkey/index.js.map +0 -1
  256. package/dist/packages/hotkey-manager/dist/constants.js +0 -47
  257. package/dist/packages/hotkey-manager/dist/constants.js.map +0 -1
  258. package/dist/packages/hotkey-manager/dist/enums/keycode.js +0 -130
  259. package/dist/packages/hotkey-manager/dist/enums/keycode.js.map +0 -1
  260. package/dist/packages/hotkey-manager/dist/lib/is-client/index.js.map +0 -1
  261. package/dist/packages/hotkey-manager/dist/lib/is-mac/index.js.map +0 -1
  262. package/dist/packages/hotkey-manager/dist/lib/key-to-id/index.js +0 -39
  263. package/dist/packages/hotkey-manager/dist/lib/key-to-id/index.js.map +0 -1
  264. package/dist/packages/hotkey-manager/dist/lib/key-to-string/index.js +0 -27
  265. package/dist/packages/hotkey-manager/dist/lib/key-to-string/index.js.map +0 -1
  266. package/dist/packages/hotkey-manager/dist/stores/hotkey-store/index.js +0 -95
  267. package/dist/packages/hotkey-manager/dist/stores/hotkey-store/index.js.map +0 -1
  268. package/dist/viewport/constants.d.ts +0 -11
  269. package/dist/viewport/constants.js +0 -25
  270. package/dist/viewport/constants.js.map +0 -1
  271. package/dist/viewport/use-viewport-state.d.ts +0 -100
  272. package/dist/viewport/use-viewport-state.js +0 -222
  273. package/dist/viewport/use-viewport-state.js.map +0 -1
@@ -11,68 +11,48 @@
11
11
  */
12
12
 
13
13
 
14
+ import { createMapStore, mapClear, mapDelete, mapSet } from "../shared/create-map-store.js";
14
15
  import { MapModeEvents } from "./events.js";
15
16
  import { Broadcast } from "@accelint/bus";
16
17
  import { uuid } from "@accelint/core";
18
+ import { getLogger } from "@accelint/logger";
17
19
 
18
20
  //#region src/map-mode/store.ts
21
+ /**
22
+ * Map Mode Store
23
+ *
24
+ * Manages mode state with ownership-based authorization.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * import { modeStore } from '@accelint/map-toolkit/map-mode';
29
+ *
30
+ * function MapControls({ mapId }) {
31
+ * const { state, requestModeChange } = modeStore.use(mapId);
32
+ *
33
+ * return (
34
+ * <div>
35
+ * <p>Current mode: {state.mode}</p>
36
+ * <button onClick={() => requestModeChange('draw', 'draw-layer')}>
37
+ * Draw Mode
38
+ * </button>
39
+ * </div>
40
+ * );
41
+ * }
42
+ * ```
43
+ */
44
+ const logger = getLogger({
45
+ enabled: process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test",
46
+ level: "warn",
47
+ prefix: "[MapMode]",
48
+ pretty: true
49
+ });
19
50
  const DEFAULT_MODE = "default";
20
51
  /**
21
52
  * Typed event bus instance for map mode events.
22
- * Provides type-safe event emission and listening for all map mode state changes.
23
53
  */
24
54
  const mapModeBus = Broadcast.getInstance();
25
55
  /**
26
- * Store for map mode state keyed by instanceId
27
- */
28
- const modeStore = /* @__PURE__ */ new Map();
29
- /**
30
- * Track React component subscribers per instanceId (for fan-out notifications).
31
- * Each Set contains onStoreChange callbacks from useSyncExternalStore.
32
- */
33
- const componentSubscribers = /* @__PURE__ */ new Map();
34
- /**
35
- * Cache of bus unsubscribe functions (1 per instanceId).
36
- * This ensures we only have one bus listener per map mode instance, regardless of
37
- * how many React components subscribe to it.
38
- */
39
- const busUnsubscribers = /* @__PURE__ */ new Map();
40
- /**
41
- * Cache of subscription functions per instanceId to avoid recreating on every render
42
- */
43
- const subscriptionCache = /* @__PURE__ */ new Map();
44
- /**
45
- * Cache of snapshot functions per instanceId to maintain referential stability
46
- */
47
- const snapshotCache = /* @__PURE__ */ new Map();
48
- /**
49
- * Cache of server snapshot functions per instanceId to maintain referential stability.
50
- * Server snapshots always return default mode since mode state is client-only.
51
- */
52
- const serverSnapshotCache = /* @__PURE__ */ new Map();
53
- /**
54
- * Cache of requestModeChange functions per instanceId to maintain referential stability
55
- */
56
- const requestModeChangeCache = /* @__PURE__ */ new Map();
57
- /**
58
- * Get or create mode state for a given instanceId
59
- */
60
- function getOrCreateState(instanceId) {
61
- if (!modeStore.has(instanceId)) modeStore.set(instanceId, {
62
- mode: DEFAULT_MODE,
63
- modeOwners: /* @__PURE__ */ new Map(),
64
- pendingRequests: /* @__PURE__ */ new Map()
65
- });
66
- return modeStore.get(instanceId);
67
- }
68
- /**
69
- * Notify all React subscribers for a given instanceId
70
- */
71
- function notifySubscribers(instanceId) {
72
- const subscribers = componentSubscribers.get(instanceId);
73
- if (subscribers) for (const onStoreChange of subscribers) onStoreChange();
74
- }
75
- /**
76
56
  * Determine if a mode change request should be auto-accepted without authorization
77
57
  */
78
58
  function shouldAutoAcceptRequest(state, desiredMode, requestOwner) {
@@ -85,27 +65,22 @@ function shouldAutoAcceptRequest(state, desiredMode, requestOwner) {
85
65
  return false;
86
66
  }
87
67
  /**
88
- * Set mode and emit change event
68
+ * Approve a request and reject all others (immutable update)
89
69
  */
90
- function setMode(instanceId, state, newMode) {
91
- const previousMode = state.mode;
92
- state.mode = newMode;
70
+ function approveRequestAndRejectOthers(instanceId, state, approvedRequest, excludeAuthId, decisionOwner, reason, emitApproval, set) {
71
+ const requestsToReject = [];
72
+ for (const request of state.pendingRequests.values()) if (request.authId !== excludeAuthId) requestsToReject.push(request);
73
+ const newModeOwners = approvedRequest.desiredMode !== DEFAULT_MODE ? mapSet(state.modeOwners, approvedRequest.desiredMode, approvedRequest.requestOwner) : state.modeOwners;
74
+ set({
75
+ mode: approvedRequest.desiredMode,
76
+ pendingRequests: mapClear(),
77
+ modeOwners: newModeOwners
78
+ });
93
79
  mapModeBus.emit(MapModeEvents.changed, {
94
- previousMode,
95
- currentMode: newMode,
80
+ previousMode: state.mode,
81
+ currentMode: approvedRequest.desiredMode,
96
82
  id: instanceId
97
83
  });
98
- notifySubscribers(instanceId);
99
- }
100
- /**
101
- * Approve a request and reject all others
102
- */
103
- function approveRequestAndRejectOthers(instanceId, state, approvedRequest, excludeAuthId, decisionOwner, reason, emitApproval) {
104
- const requestsToReject = [];
105
- for (const request of state.pendingRequests.values()) if (request.authId !== excludeAuthId) requestsToReject.push(request);
106
- state.pendingRequests.clear();
107
- setMode(instanceId, state, approvedRequest.desiredMode);
108
- if (approvedRequest.desiredMode !== DEFAULT_MODE) state.modeOwners.set(approvedRequest.desiredMode, approvedRequest.requestOwner);
109
84
  if (emitApproval) mapModeBus.emit(MapModeEvents.changeDecision, {
110
85
  authId: approvedRequest.authId,
111
86
  approved: true,
@@ -122,16 +97,16 @@ function approveRequestAndRejectOthers(instanceId, state, approvedRequest, exclu
122
97
  });
123
98
  }
124
99
  /**
125
- * Handle pending requests when returning to default mode
100
+ * Handle pending requests when returning to default mode (immutable update)
126
101
  */
127
- function handlePendingRequestsOnDefaultMode(instanceId, state, previousMode) {
102
+ function handlePendingRequestsOnDefaultMode(instanceId, state, previousMode, set) {
128
103
  const firstEntry = Array.from(state.pendingRequests.values())[0];
129
104
  if (!firstEntry) return;
130
105
  const previousModeOwner = state.modeOwners.get(previousMode);
131
106
  if (!previousModeOwner) return;
132
107
  if (firstEntry.desiredMode === DEFAULT_MODE) {
133
108
  const allRequests = Array.from(state.pendingRequests.values());
134
- state.pendingRequests.clear();
109
+ set({ pendingRequests: mapClear() });
135
110
  for (const request of allRequests) mapModeBus.emit(MapModeEvents.changeDecision, {
136
111
  authId: request.authId,
137
112
  approved: false,
@@ -139,24 +114,16 @@ function handlePendingRequestsOnDefaultMode(instanceId, state, previousMode) {
139
114
  reason: "Request rejected - already in requested mode",
140
115
  id: instanceId
141
116
  });
142
- } else approveRequestAndRejectOthers(instanceId, state, firstEntry, firstEntry.authId, previousModeOwner, "Auto-accepted when mode owner returned to default", true);
117
+ } else approveRequestAndRejectOthers(instanceId, state, firstEntry, firstEntry.authId, previousModeOwner, "Auto-accepted when mode owner returned to default", true, set);
143
118
  }
144
119
  /**
145
- * Handle authorization decision
146
- *
147
- * Processes approval/rejection decisions from mode owners. Only the current mode's owner
148
- * can make authorization decisions. If a decision comes from a non-owner, a warning is
149
- * logged and the decision is ignored to prevent unauthorized mode changes.
150
- *
151
- * @param instanceId - The unique identifier for this map instance
152
- * @param state - The mode state for this instance
153
- * @param payload - The authorization decision containing authId, approved status, and owner
120
+ * Handle authorization decision (immutable update)
154
121
  */
155
- function handleAuthorizationDecision(instanceId, state, payload) {
122
+ function handleAuthorizationDecision(instanceId, state, payload, set) {
156
123
  const { approved, authId, owner: decisionOwner } = payload;
157
124
  const currentModeOwner = state.modeOwners.get(state.mode);
158
125
  if (decisionOwner !== currentModeOwner) {
159
- console.warn(`[MapMode] Authorization decision from "${decisionOwner}" ignored - not the owner of mode "${state.mode}" (owner: ${currentModeOwner || "none"})`);
126
+ logger.warn(`Authorization decision from "${decisionOwner}" ignored - not the owner of mode "${state.mode}" (owner: ${currentModeOwner || "none"})`);
160
127
  return;
161
128
  }
162
129
  let matchingRequestOwner = null;
@@ -167,27 +134,37 @@ function handleAuthorizationDecision(instanceId, state, payload) {
167
134
  break;
168
135
  }
169
136
  if (!(matchingRequest && matchingRequestOwner)) return;
170
- if (approved) approveRequestAndRejectOthers(instanceId, state, matchingRequest, authId, decisionOwner, "", false);
171
- else state.pendingRequests.delete(matchingRequestOwner);
137
+ if (approved) approveRequestAndRejectOthers(instanceId, state, matchingRequest, authId, decisionOwner, "", false, set);
138
+ else set({ pendingRequests: mapDelete(state.pendingRequests, matchingRequestOwner) });
172
139
  }
173
140
  /**
174
- * Handle mode change request logic
141
+ * Handle mode change request logic (immutable update)
175
142
  */
176
- function handleModeChangeRequest(instanceId, state, desiredMode, requestOwner) {
143
+ function handleModeChangeRequest(instanceId, state, desiredMode, requestOwner, set) {
177
144
  const desiredModeOwner = state.modeOwners.get(desiredMode);
178
145
  if (shouldAutoAcceptRequest(state, desiredMode, requestOwner)) {
179
- setMode(instanceId, state, desiredMode);
180
- if (desiredMode !== DEFAULT_MODE && !desiredModeOwner) state.modeOwners.set(desiredMode, requestOwner);
181
- state.pendingRequests.delete(requestOwner);
146
+ const newModeOwners = desiredMode !== DEFAULT_MODE && !desiredModeOwner ? mapSet(state.modeOwners, desiredMode, requestOwner) : state.modeOwners;
147
+ const newPendingRequests = mapDelete(state.pendingRequests, requestOwner);
148
+ const previousMode = state.mode;
149
+ set({
150
+ mode: desiredMode,
151
+ modeOwners: newModeOwners,
152
+ pendingRequests: newPendingRequests
153
+ });
154
+ mapModeBus.emit(MapModeEvents.changed, {
155
+ previousMode,
156
+ currentMode: desiredMode,
157
+ id: instanceId
158
+ });
182
159
  return;
183
160
  }
184
161
  const authId = uuid();
185
- state.pendingRequests.set(requestOwner, {
162
+ set({ pendingRequests: mapSet(state.pendingRequests, requestOwner, {
186
163
  authId,
187
164
  desiredMode,
188
165
  currentMode: state.mode,
189
166
  requestOwner
190
- });
167
+ }) });
191
168
  mapModeBus.emit(MapModeEvents.changeAuthorization, {
192
169
  authId,
193
170
  desiredMode,
@@ -196,126 +173,15 @@ function handleModeChangeRequest(instanceId, state, desiredMode, requestOwner) {
196
173
  });
197
174
  }
198
175
  /**
199
- * Ensures a single bus listener exists for the given instanceId.
200
- * All React subscribers will be notified via fan-out when the bus events fire.
201
- * This prevents creating N bus listeners for N React components.
202
- *
203
- * @param instanceId - The unique identifier for the map mode instance
176
+ * Map mode store
204
177
  */
205
- function ensureBusListener(instanceId) {
206
- if (busUnsubscribers.has(instanceId)) return;
207
- const state = getOrCreateState(instanceId);
208
- const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {
209
- const { desiredMode, owner: requestOwner, id } = event.payload;
210
- if (id !== instanceId || desiredMode === state.mode) return;
211
- handleModeChangeRequest(instanceId, state, desiredMode, requestOwner);
212
- });
213
- const unsubDecision = mapModeBus.on(MapModeEvents.changeDecision, (event) => {
214
- const { id, approved, authId, owner } = event.payload;
215
- if (id !== instanceId) return;
216
- handleAuthorizationDecision(instanceId, state, {
217
- approved,
218
- authId,
219
- owner
220
- });
221
- });
222
- const unsubChanged = mapModeBus.on(MapModeEvents.changed, (event) => {
223
- const { currentMode, previousMode, id } = event.payload;
224
- if (id !== instanceId) return;
225
- if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) handlePendingRequestsOnDefaultMode(instanceId, state, previousMode);
226
- });
227
- busUnsubscribers.set(instanceId, () => {
228
- unsubRequest();
229
- unsubDecision();
230
- unsubChanged();
231
- });
232
- }
233
- /**
234
- * Cleans up the bus listener if no React subscribers remain.
235
- *
236
- * @param instanceId - The unique identifier for the map mode instance
237
- */
238
- function cleanupBusListenerIfNeeded(instanceId) {
239
- const subscribers = componentSubscribers.get(instanceId);
240
- if (!subscribers || subscribers.size === 0) {
241
- const unsub = busUnsubscribers.get(instanceId);
242
- if (unsub) {
243
- unsub();
244
- busUnsubscribers.delete(instanceId);
245
- }
246
- modeStore.delete(instanceId);
247
- componentSubscribers.delete(instanceId);
248
- subscriptionCache.delete(instanceId);
249
- snapshotCache.delete(instanceId);
250
- serverSnapshotCache.delete(instanceId);
251
- requestModeChangeCache.delete(instanceId);
252
- }
253
- }
254
- /**
255
- * Creates or retrieves a cached subscription function for a given instanceId.
256
- * Uses a fan-out pattern: 1 bus listener -> N React subscribers.
257
- * Automatically cleans up map mode state when the last subscriber unsubscribes.
258
- *
259
- * @param instanceId - The unique identifier for the map mode instance
260
- * @returns A subscription function for useSyncExternalStore
261
- */
262
- function getOrCreateSubscription(instanceId) {
263
- const subscription = subscriptionCache.get(instanceId) ?? ((onStoreChange) => {
264
- getOrCreateState(instanceId);
265
- ensureBusListener(instanceId);
266
- let subscriberSet = componentSubscribers.get(instanceId);
267
- if (!subscriberSet) {
268
- subscriberSet = /* @__PURE__ */ new Set();
269
- componentSubscribers.set(instanceId, subscriberSet);
270
- }
271
- subscriberSet.add(onStoreChange);
272
- return () => {
273
- const currentSubscriberSet = componentSubscribers.get(instanceId);
274
- if (currentSubscriberSet) currentSubscriberSet.delete(onStoreChange);
275
- cleanupBusListenerIfNeeded(instanceId);
276
- };
277
- });
278
- subscriptionCache.set(instanceId, subscription);
279
- return subscription;
280
- }
281
- /**
282
- * Creates or retrieves a cached snapshot function for a given instanceId.
283
- * The string returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.
284
- *
285
- * @param instanceId - The unique identifier for the map mode instance
286
- * @returns A snapshot function for useSyncExternalStore
287
- */
288
- function getOrCreateSnapshot(instanceId) {
289
- const snapshot = snapshotCache.get(instanceId) ?? (() => {
290
- const state = modeStore.get(instanceId);
291
- if (!state) return DEFAULT_MODE;
292
- return state.mode;
293
- });
294
- snapshotCache.set(instanceId, snapshot);
295
- return snapshot;
296
- }
297
- /**
298
- * Creates or retrieves a cached server snapshot function for a given instanceId.
299
- * Server snapshots always return the default mode since mode state is client-only.
300
- * Required for SSR/RSC compatibility with useSyncExternalStore.
301
- *
302
- * @param instanceId - The unique identifier for the map mode instance
303
- * @returns A server snapshot function for useSyncExternalStore
304
- */
305
- function getOrCreateServerSnapshot(instanceId) {
306
- const serverSnapshot = serverSnapshotCache.get(instanceId) ?? (() => DEFAULT_MODE);
307
- serverSnapshotCache.set(instanceId, serverSnapshot);
308
- return serverSnapshot;
309
- }
310
- /**
311
- * Creates or retrieves a cached requestModeChange function for a given instanceId.
312
- * This maintains referential stability for the function reference.
313
- *
314
- * @param instanceId - The unique identifier for the map mode instance
315
- * @returns A requestModeChange function for this instance
316
- */
317
- function getOrCreateRequestModeChange(instanceId) {
318
- const requestModeChange = requestModeChangeCache.get(instanceId) ?? ((desiredMode, requestOwner) => {
178
+ const modeStore = createMapStore({
179
+ defaultState: {
180
+ mode: DEFAULT_MODE,
181
+ modeOwners: /* @__PURE__ */ new Map(),
182
+ pendingRequests: /* @__PURE__ */ new Map()
183
+ },
184
+ actions: (instanceId) => ({ requestModeChange: (desiredMode, requestOwner) => {
319
185
  const trimmedDesiredMode = desiredMode.trim();
320
186
  const trimmedRequestOwner = requestOwner.trim();
321
187
  if (!trimmedDesiredMode) throw new Error("requestModeChange requires non-empty desiredMode");
@@ -325,9 +191,47 @@ function getOrCreateRequestModeChange(instanceId) {
325
191
  owner: trimmedRequestOwner,
326
192
  id: instanceId
327
193
  });
328
- });
329
- requestModeChangeCache.set(instanceId, requestModeChange);
330
- return requestModeChange;
194
+ } }),
195
+ bus: (instanceId, { get, set }) => {
196
+ const unsubRequest = mapModeBus.on(MapModeEvents.changeRequest, (event) => {
197
+ const { desiredMode, owner: requestOwner, id } = event.payload;
198
+ const state = get();
199
+ if (id !== instanceId || desiredMode === state.mode) return;
200
+ handleModeChangeRequest(instanceId, state, desiredMode, requestOwner, set);
201
+ });
202
+ const unsubDecision = mapModeBus.on(MapModeEvents.changeDecision, (event) => {
203
+ const { id, approved, authId, owner } = event.payload;
204
+ if (id !== instanceId) return;
205
+ handleAuthorizationDecision(instanceId, get(), {
206
+ approved,
207
+ authId,
208
+ owner
209
+ }, set);
210
+ });
211
+ const unsubChanged = mapModeBus.on(MapModeEvents.changed, (event) => {
212
+ const { currentMode, previousMode, id } = event.payload;
213
+ if (id !== instanceId) return;
214
+ const state = get();
215
+ if (currentMode === DEFAULT_MODE && state.pendingRequests.size > 0) handlePendingRequestsOnDefaultMode(instanceId, state, previousMode, set);
216
+ });
217
+ return () => {
218
+ unsubRequest();
219
+ unsubDecision();
220
+ unsubChanged();
221
+ };
222
+ }
223
+ });
224
+ /**
225
+ * Get the current mode for a map instance
226
+ */
227
+ function getMode(mapId) {
228
+ return modeStore.get(mapId).mode;
229
+ }
230
+ /**
231
+ * Hook for current mode value
232
+ */
233
+ function useMode(mapId) {
234
+ return modeStore.useSelector(mapId, (state) => state.mode);
331
235
  }
332
236
  /**
333
237
  * Get the owner of the current mode for a given map instance
@@ -335,36 +239,26 @@ function getOrCreateRequestModeChange(instanceId) {
335
239
  */
336
240
  function getCurrentModeOwner(instanceId) {
337
241
  const state = modeStore.get(instanceId);
338
- if (!state) return;
339
242
  return state.modeOwners.get(state.mode);
340
243
  }
341
244
  /**
245
+ * Check if a given owner is registered as the owner of any mode.
246
+ * This includes both active mode owners and pending mode requests.
247
+ * @internal - For internal map-toolkit use only
248
+ */
249
+ function isRegisteredModeOwner(instanceId, owner) {
250
+ const state = modeStore.get(instanceId);
251
+ for (const modeOwner of state.modeOwners.values()) if (modeOwner === owner) return true;
252
+ if (state.pendingRequests.has(owner)) return true;
253
+ return false;
254
+ }
255
+ /**
342
256
  * Manually clear map mode state for a specific instanceId.
343
- * This is typically not needed as cleanup happens automatically when all subscribers unmount.
344
- * Use this only in advanced scenarios where manual cleanup is required.
345
- *
346
- * @param instanceId - The unique identifier for the map mode instance to clear
347
- *
348
- * @example
349
- * ```tsx
350
- * // Manual cleanup (rarely needed)
351
- * clearMapModeState('my-map-instance');
352
- * ```
353
257
  */
354
258
  function clearMapModeState(instanceId) {
355
- const unsub = busUnsubscribers.get(instanceId);
356
- if (unsub) {
357
- unsub();
358
- busUnsubscribers.delete(instanceId);
359
- }
360
- modeStore.delete(instanceId);
361
- componentSubscribers.delete(instanceId);
362
- subscriptionCache.delete(instanceId);
363
- snapshotCache.delete(instanceId);
364
- serverSnapshotCache.delete(instanceId);
365
- requestModeChangeCache.delete(instanceId);
259
+ modeStore.clear(instanceId);
366
260
  }
367
261
 
368
262
  //#endregion
369
- export { clearMapModeState, getCurrentModeOwner, getOrCreateRequestModeChange, getOrCreateServerSnapshot, getOrCreateSnapshot, getOrCreateSubscription };
263
+ export { clearMapModeState, getCurrentModeOwner, getMode, isRegisteredModeOwner, modeStore, useMode };
370
264
  //# sourceMappingURL=store.js.map
@@ -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 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 server snapshot functions per instanceId to maintain referential stability.\n * Server snapshots always return default mode since mode state is client-only.\n */\nconst serverSnapshotCache = 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 serverSnapshotCache.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 server snapshot function for a given instanceId.\n * Server snapshots always return the default mode since mode state is client-only.\n * Required for SSR/RSC compatibility with useSyncExternalStore.\n *\n * @param instanceId - The unique identifier for the map mode instance\n * @returns A server snapshot function for useSyncExternalStore\n */\nexport function getOrCreateServerSnapshot(instanceId: UniqueId): () => string {\n const serverSnapshot =\n serverSnapshotCache.get(instanceId) ?? (() => DEFAULT_MODE);\n\n serverSnapshotCache.set(instanceId, serverSnapshot);\n\n return serverSnapshot;\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 serverSnapshotCache.delete(instanceId);\n requestModeChangeCache.delete(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 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * 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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"}
@@ -13,9 +13,9 @@
13
13
 
14
14
  'use client';
15
15
 
16
- import { getOrCreateRequestModeChange, getOrCreateServerSnapshot, getOrCreateSnapshot, getOrCreateSubscription } from "./store.js";
16
+ import { modeStore } from "./store.js";
17
17
  import { MapContext } from "../deckgl/base-map/provider.js";
18
- import { useContext, useMemo, useSyncExternalStore } from "react";
18
+ import { useContext, useMemo } from "react";
19
19
 
20
20
  //#region src/map-mode/use-map-mode.ts
21
21
  /**
@@ -60,11 +60,12 @@ function useMapMode(id) {
60
60
  const contextId = useContext(MapContext);
61
61
  const actualId = id ?? contextId;
62
62
  if (!actualId) throw new Error("useMapMode requires either an id parameter or to be used within a MapProvider");
63
- const mode = useSyncExternalStore(getOrCreateSubscription(actualId), getOrCreateSnapshot(actualId), getOrCreateServerSnapshot(actualId));
63
+ const mode = modeStore.useSelector(actualId, (state) => state.mode);
64
+ const { requestModeChange } = modeStore.actions(actualId);
64
65
  return useMemo(() => ({
65
66
  mode,
66
- requestModeChange: getOrCreateRequestModeChange(actualId)
67
- }), [mode, actualId]);
67
+ requestModeChange
68
+ }), [mode, requestModeChange]);
68
69
  }
69
70
 
70
71
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-map-mode.js","names":[],"sources":["../../src/map-mode/use-map-mode.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n'use client';\n\nimport 'client-only';\nimport { useContext, useMemo, useSyncExternalStore } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport {\n getOrCreateRequestModeChange,\n getOrCreateServerSnapshot,\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 // Third parameter provides server snapshot for SSR/RSC compatibility\n const mode = useSyncExternalStore(\n getOrCreateSubscription(actualId),\n getOrCreateSnapshot(actualId),\n getOrCreateServerSnapshot(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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,WAAW,IAAiC;CAC1D,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,gFACD;CAKH,MAAM,OAAO,qBACX,wBAAwB,SAAS,EACjC,oBAAoB,SAAS,EAC7B,0BAA0B,SAAS,CACpC;AAGD,QAAO,eACE;EACL;EACA,mBAAmB,6BAA6B,SAAS;EAC1D,GACD,CAAC,MAAM,SAAS,CACjB"}
1
+ {"version":3,"file":"use-map-mode.js","names":[],"sources":["../../src/map-mode/use-map-mode.ts"],"sourcesContent":["/*\n * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n'use client';\n\nimport 'client-only';\nimport { useContext, useMemo } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport { modeStore } 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 // Use selector to get just the mode string (primitive comparison works with useSyncExternalStore)\n const mode = modeStore.useSelector(actualId, (state) => state.mode);\n\n // Get actions separately (stable reference)\n const { requestModeChange } = modeStore.actions(actualId);\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n mode,\n requestModeChange,\n }),\n [mode, requestModeChange],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,WAAW,IAAiC;CAC1D,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,gFACD;CAIH,MAAM,OAAO,UAAU,YAAY,WAAW,UAAU,MAAM,KAAK;CAGnE,MAAM,EAAE,sBAAsB,UAAU,QAAQ,SAAS;AAGzD,QAAO,eACE;EACL;EACA;EACD,GACD,CAAC,MAAM,kBAAkB,CAC1B"}
@@ -10,6 +10,6 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { INITIAL_VIEW_STATE } from "./constants.js";
14
13
  import { useMapLibre } from "./hooks/use-maplibre.js";
15
- export { INITIAL_VIEW_STATE, useMapLibre };
14
+ import { DEFAULT_VIEW_STATE } from "../shared/constants.js";
15
+ export { DEFAULT_VIEW_STATE, useMapLibre };
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
 
14
- import { INITIAL_VIEW_STATE } from "./constants.js";
14
+ import { DEFAULT_VIEW_STATE } from "../shared/constants.js";
15
15
  import { useMapLibre } from "./hooks/use-maplibre.js";
16
16
 
17
- export { INITIAL_VIEW_STATE, useMapLibre };
17
+ export { DEFAULT_VIEW_STATE, useMapLibre };