@accelint/map-toolkit 0.6.0 → 1.1.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 (207) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/catalog-info.yaml +7 -6
  3. package/dist/camera/events.js.map +1 -1
  4. package/dist/camera/index.d.ts +2 -2
  5. package/dist/camera/index.js +2 -2
  6. package/dist/camera/store.d.ts +120 -0
  7. package/dist/camera/store.js +279 -0
  8. package/dist/camera/store.js.map +1 -0
  9. package/dist/cursor-coordinates/index.d.ts +4 -2
  10. package/dist/cursor-coordinates/index.js +3 -2
  11. package/dist/cursor-coordinates/store.d.ts +48 -0
  12. package/dist/cursor-coordinates/store.js +92 -0
  13. package/dist/cursor-coordinates/store.js.map +1 -0
  14. package/dist/cursor-coordinates/types.d.ts +87 -0
  15. package/dist/cursor-coordinates/types.js +12 -0
  16. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +41 -37
  17. package/dist/cursor-coordinates/use-cursor-coordinates.js +131 -202
  18. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  19. package/dist/deckgl/base-map/constants.d.ts +1 -6
  20. package/dist/deckgl/base-map/constants.js +1 -6
  21. package/dist/deckgl/base-map/constants.js.map +1 -1
  22. package/dist/deckgl/base-map/controls.js +2 -0
  23. package/dist/deckgl/base-map/controls.js.map +1 -1
  24. package/dist/deckgl/base-map/events.js.map +1 -1
  25. package/dist/deckgl/base-map/index.d.ts +2 -2
  26. package/dist/deckgl/base-map/index.js +10 -11
  27. package/dist/deckgl/base-map/index.js.map +1 -1
  28. package/dist/deckgl/base-map/provider.d.ts +2 -2
  29. package/dist/deckgl/base-map/provider.js +1 -1
  30. package/dist/deckgl/base-map/provider.js.map +1 -1
  31. package/dist/deckgl/index.d.ts +4 -4
  32. package/dist/deckgl/index.js +4 -4
  33. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  34. package/dist/deckgl/saved-viewports/storage.js +10 -2
  35. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  36. package/dist/deckgl/shapes/display-shape-layer/constants.js +5 -8
  37. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  38. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
  39. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +18 -14
  40. package/dist/deckgl/shapes/display-shape-layer/index.js +63 -30
  41. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  42. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +2 -16
  43. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js.map +1 -1
  44. package/dist/deckgl/shapes/display-shape-layer/store.js +58 -272
  45. package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
  46. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +22 -11
  47. package/dist/deckgl/shapes/display-shape-layer/{use-shape-selection.d.ts → use-select-shape.d.ts} +9 -9
  48. package/dist/deckgl/shapes/display-shape-layer/{use-shape-selection.js → use-select-shape.js} +12 -12
  49. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js.map +1 -0
  50. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +5 -66
  51. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  52. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +2 -65
  53. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +3 -121
  54. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  55. package/dist/deckgl/shapes/draw-shape-layer/constants.js +46 -0
  56. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -0
  57. package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +92 -0
  58. package/dist/deckgl/shapes/draw-shape-layer/events.js +56 -0
  59. package/dist/deckgl/shapes/draw-shape-layer/events.js.map +1 -0
  60. package/dist/deckgl/shapes/draw-shape-layer/fiber.d.ts +11 -0
  61. package/dist/{maplibre/constants.js → deckgl/shapes/draw-shape-layer/fiber.js} +6 -12
  62. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -0
  63. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +53 -0
  64. package/dist/deckgl/shapes/draw-shape-layer/index.js +95 -0
  65. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -0
  66. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +51 -0
  67. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -0
  68. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +73 -0
  69. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -0
  70. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +87 -0
  71. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -0
  72. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +88 -0
  73. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -0
  74. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +77 -0
  75. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -0
  76. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +64 -0
  77. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -0
  78. package/dist/deckgl/shapes/draw-shape-layer/store.js +175 -0
  79. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -0
  80. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +86 -0
  81. package/dist/{viewport/constants.js → deckgl/shapes/draw-shape-layer/types.js} +1 -12
  82. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +82 -0
  83. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +112 -0
  84. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js.map +1 -0
  85. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +147 -0
  86. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -0
  87. package/dist/deckgl/shapes/edit-shape-layer/constants.js +41 -0
  88. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -0
  89. package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +92 -0
  90. package/dist/deckgl/shapes/edit-shape-layer/events.js +56 -0
  91. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -0
  92. package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +13 -0
  93. package/dist/deckgl/shapes/edit-shape-layer/fiber.js +14 -0
  94. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +63 -0
  95. package/dist/deckgl/shapes/edit-shape-layer/index.js +162 -0
  96. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -0
  97. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +154 -0
  98. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -0
  99. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +147 -0
  100. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -0
  101. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +87 -0
  102. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -0
  103. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +61 -0
  104. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -0
  105. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +109 -0
  106. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -0
  107. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +289 -0
  108. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -0
  109. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +121 -0
  110. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -0
  111. package/dist/deckgl/shapes/edit-shape-layer/store.js +194 -0
  112. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -0
  113. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +93 -0
  114. package/dist/deckgl/shapes/edit-shape-layer/types.js +14 -0
  115. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +82 -0
  116. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +114 -0
  117. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -0
  118. package/dist/deckgl/shapes/index.d.ts +15 -6
  119. package/dist/deckgl/shapes/index.js +12 -5
  120. package/dist/deckgl/shapes/shared/constants.d.ts +27 -32
  121. package/dist/deckgl/shapes/shared/constants.js +189 -25
  122. package/dist/deckgl/shapes/shared/constants.js.map +1 -1
  123. package/dist/deckgl/shapes/shared/events.d.ts +1 -20
  124. package/dist/deckgl/shapes/shared/events.js +1 -31
  125. package/dist/deckgl/shapes/shared/events.js.map +1 -1
  126. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +84 -0
  127. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -0
  128. package/dist/deckgl/shapes/shared/types.d.ts +187 -28
  129. package/dist/deckgl/shapes/shared/types.js +55 -1
  130. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  131. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +128 -0
  132. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -0
  133. package/dist/deckgl/shapes/shared/utils/layer-config.js +50 -0
  134. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -0
  135. package/dist/deckgl/shapes/shared/utils/mode-utils.js +113 -0
  136. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -0
  137. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +57 -0
  138. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -0
  139. package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +64 -0
  140. package/dist/deckgl/shapes/shared/utils/style-utils.js +101 -0
  141. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -0
  142. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  143. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  144. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  145. package/dist/deckgl/text-layer/default-settings.js +4 -24
  146. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  147. package/dist/deckgl/text-layer/fiber.js.map +1 -1
  148. package/dist/deckgl/text-layer/index.js.map +1 -1
  149. package/dist/deckgl/text-settings.d.ts +77 -0
  150. package/dist/deckgl/text-settings.js +83 -0
  151. package/dist/deckgl/text-settings.js.map +1 -0
  152. package/dist/map-cursor/events.js.map +1 -1
  153. package/dist/map-cursor/index.d.ts +2 -2
  154. package/dist/map-cursor/index.js +2 -2
  155. package/dist/map-cursor/store.d.ts +32 -61
  156. package/dist/map-cursor/store.js +165 -294
  157. package/dist/map-cursor/store.js.map +1 -1
  158. package/dist/map-cursor/use-map-cursor.d.ts +5 -2
  159. package/dist/map-cursor/use-map-cursor.js +33 -15
  160. package/dist/map-cursor/use-map-cursor.js.map +1 -1
  161. package/dist/map-mode/events.js.map +1 -1
  162. package/dist/map-mode/index.d.ts +2 -2
  163. package/dist/map-mode/index.js +2 -2
  164. package/dist/map-mode/store.d.ts +36 -37
  165. package/dist/map-mode/store.js +131 -237
  166. package/dist/map-mode/store.js.map +1 -1
  167. package/dist/map-mode/use-map-mode.js +6 -5
  168. package/dist/map-mode/use-map-mode.js.map +1 -1
  169. package/dist/maplibre/hooks/use-maplibre.js.map +1 -1
  170. package/dist/maplibre/index.d.ts +2 -2
  171. package/dist/maplibre/index.js +2 -2
  172. package/dist/shared/constants.d.ts +19 -0
  173. package/dist/shared/constants.js +33 -0
  174. package/dist/shared/constants.js.map +1 -0
  175. package/dist/shared/create-map-store.d.ts +202 -0
  176. package/dist/shared/create-map-store.js +223 -0
  177. package/dist/shared/create-map-store.js.map +1 -0
  178. package/dist/shared/units.d.ts +39 -0
  179. package/dist/shared/units.js +49 -0
  180. package/dist/shared/units.js.map +1 -0
  181. package/dist/viewport/index.d.ts +3 -3
  182. package/dist/viewport/index.js +3 -3
  183. package/dist/viewport/store.d.ts +69 -0
  184. package/dist/viewport/store.js +125 -0
  185. package/dist/viewport/store.js.map +1 -0
  186. package/dist/viewport/types.d.ts +2 -2
  187. package/dist/viewport/utils.js +2 -2
  188. package/dist/viewport/utils.js.map +1 -1
  189. package/dist/viewport/viewport-size.d.ts +2 -2
  190. package/dist/viewport/viewport-size.js +2 -2
  191. package/dist/viewport/viewport-size.js.map +1 -1
  192. package/package.json +39 -19
  193. package/dist/camera/use-camera-state.d.ts +0 -153
  194. package/dist/camera/use-camera-state.js +0 -418
  195. package/dist/camera/use-camera-state.js.map +0 -1
  196. package/dist/deckgl/shapes/display-shape-layer/constants.d.ts +0 -44
  197. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.d.ts +0 -66
  198. package/dist/deckgl/shapes/display-shape-layer/store.d.ts +0 -87
  199. package/dist/deckgl/shapes/display-shape-layer/use-shape-selection.js.map +0 -1
  200. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.d.ts +0 -61
  201. package/dist/maplibre/constants.d.ts +0 -13
  202. package/dist/maplibre/constants.js.map +0 -1
  203. package/dist/viewport/constants.d.ts +0 -11
  204. package/dist/viewport/constants.js.map +0 -1
  205. package/dist/viewport/use-viewport-state.d.ts +0 -100
  206. package/dist/viewport/use-viewport-state.js +0 -222
  207. package/dist/viewport/use-viewport-state.js.map +0 -1
@@ -13,9 +13,9 @@
13
13
 
14
14
  'use client';
15
15
 
16
- import { getOrCreateClearCursor, getOrCreateRequestCursorChange, getOrCreateServerSnapshot, getOrCreateSnapshot, getOrCreateSubscription } from "./store.js";
16
+ import { cursorStore, useCursor } from "./store.js";
17
17
  import { MapContext } from "../deckgl/base-map/provider.js";
18
- import { useContext, useEffect, useMemo, useSyncExternalStore } from "react";
18
+ import { useContext, useEffect, useMemo, useRef } from "react";
19
19
 
20
20
  //#region src/map-cursor/use-map-cursor.ts
21
21
  /**
@@ -70,12 +70,13 @@ function useMapCursor(id) {
70
70
  const contextId = useContext(MapContext);
71
71
  const actualId = id ?? contextId;
72
72
  if (!actualId) throw new Error("useMapCursor requires either an id parameter or to be used within a MapProvider");
73
- const cursor = useSyncExternalStore(getOrCreateSubscription(actualId), getOrCreateSnapshot(actualId), getOrCreateServerSnapshot(actualId));
73
+ const cursor = useCursor(actualId);
74
+ const actions = useMemo(() => cursorStore.actions(actualId), [actualId]);
74
75
  return useMemo(() => ({
75
76
  cursor,
76
- requestCursorChange: getOrCreateRequestCursorChange(actualId),
77
- clearCursor: getOrCreateClearCursor(actualId)
78
- }), [cursor, actualId]);
77
+ requestCursorChange: actions.requestCursorChange,
78
+ clearCursor: actions.clearCursor
79
+ }), [cursor, actions]);
79
80
  }
80
81
  /**
81
82
  * Hook to automatically manage cursor for a component's lifecycle.
@@ -83,7 +84,10 @@ function useMapCursor(id) {
83
84
  * This hook automatically requests a cursor when mounted and clears it when unmounted.
84
85
  * Useful for components that need to consistently show a specific cursor.
85
86
  *
86
- * @param cursor - The cursor to request
87
+ * NOTE: This hook does NOT re-render when cursor state changes. If you need to read
88
+ * the current cursor value, use `useMapCursor` instead.
89
+ *
90
+ * @param cursorType - The cursor to request
87
91
  * @param owner - The owner identifier
88
92
  * @param id - Optional map instance ID
89
93
  *
@@ -96,18 +100,32 @@ function useMapCursor(id) {
96
100
  * }
97
101
  * ```
98
102
  */
99
- function useMapCursorEffect(cursor, owner, id) {
100
- const { requestCursorChange, clearCursor } = useMapCursor(id);
103
+ function useMapCursorEffect(cursorType, owner, id) {
104
+ const contextId = useContext(MapContext);
105
+ const actualId = id ?? contextId;
106
+ if (!actualId) throw new Error("useMapCursorEffect requires either an id parameter or to be used within a MapProvider");
107
+ const cursorRef = useRef(cursorType);
108
+ const ownerRef = useRef(owner);
109
+ const idRef = useRef(actualId);
110
+ cursorRef.current = cursorType;
111
+ ownerRef.current = owner;
112
+ idRef.current = actualId;
101
113
  useEffect(() => {
102
- requestCursorChange(cursor, owner);
114
+ const mapId = idRef.current;
115
+ const unsubscribe = cursorStore.subscribe(mapId)(() => {});
103
116
  return () => {
104
- clearCursor(owner);
117
+ unsubscribe();
118
+ queueMicrotask(() => {
119
+ cursorStore.actions(idRef.current).clearCursor(ownerRef.current);
120
+ });
105
121
  };
122
+ }, []);
123
+ useEffect(() => {
124
+ cursorStore.actions(actualId).requestCursorChange(cursorType, owner);
106
125
  }, [
107
- cursor,
108
- owner,
109
- requestCursorChange,
110
- clearCursor
126
+ actualId,
127
+ cursorType,
128
+ owner
111
129
  ]);
112
130
  }
113
131
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-map-cursor.js","names":[],"sources":["../../src/map-cursor/use-map-cursor.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, useEffect, useMemo, useSyncExternalStore } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport {\n getOrCreateClearCursor,\n getOrCreateRequestCursorChange,\n getOrCreateServerSnapshot,\n getOrCreateSnapshot,\n getOrCreateSubscription,\n} from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type { CSSCursorType } from './types';\n\n/**\n * Return value for the useMapCursor hook\n */\nexport type UseMapCursorReturn = {\n /** The current active cursor */\n cursor: CSSCursorType;\n /** Function to request a cursor change with ownership */\n requestCursorChange: (cursor: CSSCursorType, requestOwner: string) => void;\n /** Function to clear cursor for an owner */\n clearCursor: (owner: string) => void;\n};\n\n/**\n * Hook to access the map cursor state and actions.\n *\n * This hook uses `useSyncExternalStore` to subscribe to map cursor state changes,\n * providing concurrent-safe cursor state updates. Uses a fan-out pattern where\n * a single bus listener per map instance notifies N React component subscribers.\n *\n * **Owner-based Priority System:**\n * - Mode owners (from MapModeStore) have highest priority\n * - Non-owners can set cursors only in default/ownerless modes\n * - Rejected requests emit events with rejection reasons\n *\n * @param id - Optional map instance ID. If not provided, will use the ID from `MapContext`.\n * @returns The current cursor, requestCursorChange function, and clearCursor 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)\n * function CustomDeckLayer() {\n * const { cursor, requestCursorChange, clearCursor } = useMapCursor();\n *\n * const handleHover = (info: PickingInfo) => {\n * if (info.object) {\n * requestCursorChange('pointer', 'custom-layer-id');\n * } else {\n * clearCursor('custom-layer-id');\n * }\n * };\n *\n * return <ScatterplotLayer onHover={handleHover} />;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Outside MapProvider - pass id directly\n * function ExternalControl({ mapId }: { mapId: UniqueId }) {\n * const { cursor, requestCursorChange } = useMapCursor(mapId);\n *\n * return (\n * <button onClick={() => requestCursorChange('crosshair', 'external-control')}>\n * Set Crosshair (current: {cursor})\n * </button>\n * );\n * }\n * ```\n */\nexport function useMapCursor(id?: UniqueId): UseMapCursorReturn {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useMapCursor 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 cursor = 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 cursor,\n requestCursorChange: getOrCreateRequestCursorChange(actualId),\n clearCursor: getOrCreateClearCursor(actualId),\n }),\n [cursor, actualId],\n );\n}\n\n/**\n * Hook to automatically manage cursor for a component's lifecycle.\n *\n * This hook automatically requests a cursor when mounted and clears it when unmounted.\n * Useful for components that need to consistently show a specific cursor.\n *\n * @param cursor - The cursor to request\n * @param owner - The owner identifier\n * @param id - Optional map instance ID\n *\n * @example\n * ```tsx\n * function DrawingMode() {\n * useMapCursorEffect('crosshair', 'drawing-mode');\n *\n * return <div>Drawing mode active with crosshair cursor</div>;\n * }\n * ```\n */\nexport function useMapCursorEffect(\n cursor: CSSCursorType,\n owner: string,\n id?: UniqueId,\n): void {\n const { requestCursorChange, clearCursor } = useMapCursor(id);\n\n useEffect(() => {\n requestCursorChange(cursor, owner);\n\n return () => {\n clearCursor(owner);\n };\n }, [cursor, owner, requestCursorChange, clearCursor]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,SAAgB,aAAa,IAAmC;CAC9D,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,kFACD;CAKH,MAAM,SAAS,qBACb,wBAAwB,SAAS,EACjC,oBAAoB,SAAS,EAC7B,0BAA0B,SAAS,CACpC;AAGD,QAAO,eACE;EACL;EACA,qBAAqB,+BAA+B,SAAS;EAC7D,aAAa,uBAAuB,SAAS;EAC9C,GACD,CAAC,QAAQ,SAAS,CACnB;;;;;;;;;;;;;;;;;;;;;AAsBH,SAAgB,mBACd,QACA,OACA,IACM;CACN,MAAM,EAAE,qBAAqB,gBAAgB,aAAa,GAAG;AAE7D,iBAAgB;AACd,sBAAoB,QAAQ,MAAM;AAElC,eAAa;AACX,eAAY,MAAM;;IAEnB;EAAC;EAAQ;EAAO;EAAqB;EAAY,CAAC"}
1
+ {"version":3,"file":"use-map-cursor.js","names":[],"sources":["../../src/map-cursor/use-map-cursor.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'use client';\n\nimport 'client-only';\nimport { useContext, useEffect, useMemo, useRef } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport { cursorStore, useCursor } from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type { CSSCursorType } from './types';\n\n/**\n * Return value for the useMapCursor hook\n */\nexport type UseMapCursorReturn = {\n /** The current active cursor */\n cursor: CSSCursorType;\n /** Function to request a cursor change with ownership */\n requestCursorChange: (cursor: CSSCursorType, requestOwner: string) => void;\n /** Function to clear cursor for an owner */\n clearCursor: (owner: string) => void;\n};\n\n/**\n * Hook to access the map cursor state and actions.\n *\n * This hook uses `useSyncExternalStore` to subscribe to map cursor state changes,\n * providing concurrent-safe cursor state updates. Uses a fan-out pattern where\n * a single bus listener per map instance notifies N React component subscribers.\n *\n * **Owner-based Priority System:**\n * - Mode owners (from MapModeStore) have highest priority\n * - Non-owners can set cursors only in default/ownerless modes\n * - Rejected requests emit events with rejection reasons\n *\n * @param id - Optional map instance ID. If not provided, will use the ID from `MapContext`.\n * @returns The current cursor, requestCursorChange function, and clearCursor 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)\n * function CustomDeckLayer() {\n * const { cursor, requestCursorChange, clearCursor } = useMapCursor();\n *\n * const handleHover = (info: PickingInfo) => {\n * if (info.object) {\n * requestCursorChange('pointer', 'custom-layer-id');\n * } else {\n * clearCursor('custom-layer-id');\n * }\n * };\n *\n * return <ScatterplotLayer onHover={handleHover} />;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Outside MapProvider - pass id directly\n * function ExternalControl({ mapId }: { mapId: UniqueId }) {\n * const { cursor, requestCursorChange } = useMapCursor(mapId);\n *\n * return (\n * <button onClick={() => requestCursorChange('crosshair', 'external-control')}>\n * Set Crosshair (current: {cursor})\n * </button>\n * );\n * }\n * ```\n */\nexport function useMapCursor(id?: UniqueId): UseMapCursorReturn {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useMapCursor requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n // Use the useCursor hook which computes effective cursor with priority\n const cursor = useCursor(actualId);\n\n // Get actions from the store - cursorStore.actions returns stable references\n // (cached per mapId), so we can memoize on actualId only\n const actions = useMemo(() => cursorStore.actions(actualId), [actualId]);\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n cursor,\n requestCursorChange: actions.requestCursorChange,\n clearCursor: actions.clearCursor,\n }),\n [cursor, actions],\n );\n}\n\n/**\n * Hook to automatically manage cursor for a component's lifecycle.\n *\n * This hook automatically requests a cursor when mounted and clears it when unmounted.\n * Useful for components that need to consistently show a specific cursor.\n *\n * NOTE: This hook does NOT re-render when cursor state changes. If you need to read\n * the current cursor value, use `useMapCursor` instead.\n *\n * @param cursorType - The cursor to request\n * @param owner - The owner identifier\n * @param id - Optional map instance ID\n *\n * @example\n * ```tsx\n * function DrawingMode() {\n * useMapCursorEffect('crosshair', 'drawing-mode');\n *\n * return <div>Drawing mode active with crosshair cursor</div>;\n * }\n * ```\n */\nexport function useMapCursorEffect(\n cursorType: CSSCursorType,\n owner: string,\n id?: UniqueId,\n): void {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useMapCursorEffect requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n // Store values in refs for stable cleanup function access\n const cursorRef = useRef(cursorType);\n const ownerRef = useRef(owner);\n const idRef = useRef(actualId);\n\n // Update refs on each render\n cursorRef.current = cursorType;\n ownerRef.current = owner;\n idRef.current = actualId;\n\n // Subscribe to store (to ensure bus listeners are set up) on mount only\n useEffect(() => {\n const mapId = idRef.current;\n\n // Subscribe to store to ensure bus listeners are set up\n // The subscription callback is a no-op since we don't want to re-render\n const unsubscribe = cursorStore.subscribe(mapId)(() => {\n // No-op: we don't want cursor state changes to trigger re-renders\n });\n\n return () => {\n // Unsubscribe first to prevent re-render loops\n unsubscribe();\n\n // IMPORTANT: Defer cursor clear using queueMicrotask to avoid the React error:\n // \"Cannot update a component while rendering a different component\"\n //\n // Effect cleanup runs during React's commit phase. If clearCursor() triggers\n // a state update synchronously (via store.set() -> notify subscribers), it can\n // cause React to attempt re-rendering while still committing the current tree.\n //\n // queueMicrotask schedules the cleanup to run after the current commit phase\n // completes, allowing React to finish its work before the state update occurs.\n queueMicrotask(() => {\n const cleanupActions = cursorStore.actions(idRef.current);\n cleanupActions.clearCursor(ownerRef.current);\n });\n };\n }, []);\n\n // Request cursor when cursorType or owner changes\n useEffect(() => {\n const actions = cursorStore.actions(actualId);\n actions.requestCursorChange(cursorType, owner);\n }, [actualId, cursorType, owner]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,SAAgB,aAAa,IAAmC;CAC9D,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,kFACD;CAIH,MAAM,SAAS,UAAU,SAAS;CAIlC,MAAM,UAAU,cAAc,YAAY,QAAQ,SAAS,EAAE,CAAC,SAAS,CAAC;AAGxE,QAAO,eACE;EACL;EACA,qBAAqB,QAAQ;EAC7B,aAAa,QAAQ;EACtB,GACD,CAAC,QAAQ,QAAQ,CAClB;;;;;;;;;;;;;;;;;;;;;;;;AAyBH,SAAgB,mBACd,YACA,OACA,IACM;CACN,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,wFACD;CAIH,MAAM,YAAY,OAAO,WAAW;CACpC,MAAM,WAAW,OAAO,MAAM;CAC9B,MAAM,QAAQ,OAAO,SAAS;AAG9B,WAAU,UAAU;AACpB,UAAS,UAAU;AACnB,OAAM,UAAU;AAGhB,iBAAgB;EACd,MAAM,QAAQ,MAAM;EAIpB,MAAM,cAAc,YAAY,UAAU,MAAM,OAAO,GAErD;AAEF,eAAa;AAEX,gBAAa;AAWb,wBAAqB;AAEnB,IADuB,YAAY,QAAQ,MAAM,QAAQ,CAC1C,YAAY,SAAS,QAAQ;KAC5C;;IAEH,EAAE,CAAC;AAGN,iBAAgB;AAEd,EADgB,YAAY,QAAQ,SAAS,CACrC,oBAAoB,YAAY,MAAM;IAC7C;EAAC;EAAU;EAAY;EAAM,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","names":[],"sources":["../../src/map-mode/events.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 * Namespace prefix for all map mode events.\n */\nexport const MapModeEventsNamespace = 'map-mode';\n\n/**\n * Event type constants for map mode state management.\n *\n * These events are emitted through the `@accelint/bus` event bus to coordinate\n * map mode changes across components in a decoupled manner.\n *\n * @example\n * ```ts\n * import { useOn, useEmit } from '@accelint/bus/react';\n * import { MapModeEvents } from '@accelint/map-toolkit/map-mode';\n *\n * // Listen for mode changes\n * useOn(MapModeEvents.changed, (event) => {\n * console.log('Mode changed to:', event.payload.currentMode);\n * });\n *\n * // Emit a decision\n * const emitDecision = useEmit(MapModeEvents.changeDecision);\n * emitDecision({ authId, approved: true, owner: 'my-id', id });\n * ```\n */\nexport const MapModeEvents = {\n /** Emitted when the map mode has successfully changed */\n changed: `${MapModeEventsNamespace}:changed`,\n /** Emitted when a component requests a mode change */\n changeRequest: `${MapModeEventsNamespace}:change:request`,\n /** Emitted when authorization is required for a mode change */\n changeAuthorization: `${MapModeEventsNamespace}:change:authorization`,\n /** Emitted when an authorization decision is made (approve/reject) */\n changeDecision: `${MapModeEventsNamespace}:change:decision`,\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAa,yBAAyB;;;;;;;;;;;;;;;;;;;;;;AAuBtC,MAAa,gBAAgB;CAE3B,SAAS,GAAG,uBAAuB;CAEnC,eAAe,GAAG,uBAAuB;CAEzC,qBAAqB,GAAG,uBAAuB;CAE/C,gBAAgB,GAAG,uBAAuB;CAC3C"}
1
+ {"version":3,"file":"events.js","names":[],"sources":["../../src/map-mode/events.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 * Namespace prefix for all map mode events.\n */\nexport const MapModeEventsNamespace = 'map-mode';\n\n/**\n * Event type constants for map mode state management.\n *\n * These events are emitted through the `@accelint/bus` event bus to coordinate\n * map mode changes across components in a decoupled manner.\n *\n * @example\n * ```ts\n * import { useOn, useEmit } from '@accelint/bus/react';\n * import { MapModeEvents } from '@accelint/map-toolkit/map-mode';\n *\n * // Listen for mode changes\n * useOn(MapModeEvents.changed, (event) => {\n * console.log('Mode changed to:', event.payload.currentMode);\n * });\n *\n * // Emit a decision\n * const emitDecision = useEmit(MapModeEvents.changeDecision);\n * emitDecision({ authId, approved: true, owner: 'my-id', id });\n * ```\n */\nexport const MapModeEvents = {\n /** Emitted when the map mode has successfully changed */\n changed: `${MapModeEventsNamespace}:changed`,\n /** Emitted when a component requests a mode change */\n changeRequest: `${MapModeEventsNamespace}:change:request`,\n /** Emitted when authorization is required for a mode change */\n changeAuthorization: `${MapModeEventsNamespace}:change:authorization`,\n /** Emitted when an authorization decision is made (approve/reject) */\n changeDecision: `${MapModeEventsNamespace}:change:decision`,\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAa,yBAAyB;;;;;;;;;;;;;;;;;;;;;;AAuBtC,MAAa,gBAAgB;CAE3B,SAAS,GAAG,uBAAuB;CAEnC,eAAe,GAAG,uBAAuB;CAEzC,qBAAqB,GAAG,uBAAuB;CAE/C,gBAAgB,GAAG,uBAAuB;CAC3C"}
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { MapModeEvents, MapModeEventsNamespace } from "./events.js";
14
- import { clearMapModeState, getCurrentModeOwner } from "./store.js";
14
+ import { clearMapModeState, getCurrentModeOwner, getMode, modeStore } from "./store.js";
15
15
  import { UseMapModeReturn, useMapMode } from "./use-map-mode.js";
16
16
  import { MapModeEventType, ModeChangeAuthorizationEvent, ModeChangeAuthorizationPayload, ModeChangeDecisionEvent, ModeChangeDecisionPayload, ModeChangeRequestEvent, ModeChangeRequestPayload, ModeChangedEvent, ModeChangedPayload } from "./types.js";
17
- export { type MapModeEventType, MapModeEvents, MapModeEventsNamespace, type ModeChangeAuthorizationEvent, type ModeChangeAuthorizationPayload, type ModeChangeDecisionEvent, type ModeChangeDecisionPayload, type ModeChangeRequestEvent, type ModeChangeRequestPayload, type ModeChangedEvent, type ModeChangedPayload, type UseMapModeReturn, clearMapModeState, getCurrentModeOwner, useMapMode };
17
+ export { type MapModeEventType, MapModeEvents, MapModeEventsNamespace, type ModeChangeAuthorizationEvent, type ModeChangeAuthorizationPayload, type ModeChangeDecisionEvent, type ModeChangeDecisionPayload, type ModeChangeRequestEvent, type ModeChangeRequestPayload, type ModeChangedEvent, type ModeChangedPayload, type UseMapModeReturn, clearMapModeState, getCurrentModeOwner, getMode, modeStore, useMapMode };
@@ -12,7 +12,7 @@
12
12
 
13
13
 
14
14
  import { MapModeEvents, MapModeEventsNamespace } from "./events.js";
15
- import { clearMapModeState, getCurrentModeOwner } from "./store.js";
15
+ import { clearMapModeState, getCurrentModeOwner, getMode, modeStore } from "./store.js";
16
16
  import { useMapMode } from "./use-map-mode.js";
17
17
 
18
- export { MapModeEvents, MapModeEventsNamespace, clearMapModeState, getCurrentModeOwner, useMapMode };
18
+ export { MapModeEvents, MapModeEventsNamespace, clearMapModeState, getCurrentModeOwner, getMode, modeStore, useMapMode };
@@ -10,62 +10,61 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import { MapStore } from "../shared/create-map-store.js";
13
14
  import { UniqueId } from "@accelint/core";
14
15
 
15
16
  //#region src/map-mode/store.d.ts
16
17
  /**
17
- * Creates or retrieves a cached subscription function for a given instanceId.
18
- * Uses a fan-out pattern: 1 bus listener -> N React subscribers.
19
- * Automatically cleans up map mode state when the last subscriber unsubscribes.
20
- *
21
- * @param instanceId - The unique identifier for the map mode instance
22
- * @returns A subscription function for useSyncExternalStore
18
+ * Internal type for tracking pending authorization requests.
23
19
  */
24
- declare function getOrCreateSubscription(instanceId: UniqueId): (onStoreChange: () => void) => () => void;
20
+ type PendingRequest = {
21
+ authId: string;
22
+ desiredMode: string;
23
+ currentMode: string;
24
+ requestOwner: string;
25
+ };
25
26
  /**
26
- * Creates or retrieves a cached snapshot function for a given instanceId.
27
- * The string returned gets equality checked, so it needs to be stable or React re-renders unnecessarily.
28
- *
29
- * @param instanceId - The unique identifier for the map mode instance
30
- * @returns A snapshot function for useSyncExternalStore
27
+ * State shape for map mode
31
28
  */
32
- declare function getOrCreateSnapshot(instanceId: UniqueId): () => string;
29
+ type MapModeState = {
30
+ mode: string;
31
+ modeOwners: Map<string, string>;
32
+ pendingRequests: Map<string, PendingRequest>;
33
+ };
33
34
  /**
34
- * Creates or retrieves a cached server snapshot function for a given instanceId.
35
- * Server snapshots always return the default mode since mode state is client-only.
36
- * Required for SSR/RSC compatibility with useSyncExternalStore.
37
- *
38
- * @param instanceId - The unique identifier for the map mode instance
39
- * @returns A server snapshot function for useSyncExternalStore
35
+ * Actions for map mode
40
36
  */
41
- declare function getOrCreateServerSnapshot(instanceId: UniqueId): () => string;
37
+ type MapModeActions = {
38
+ /** Request a mode change */
39
+ requestModeChange: (desiredMode: string, requestOwner: string) => void;
40
+ };
42
41
  /**
43
- * Creates or retrieves a cached requestModeChange function for a given instanceId.
44
- * This maintains referential stability for the function reference.
45
- *
46
- * @param instanceId - The unique identifier for the map mode instance
47
- * @returns A requestModeChange function for this instance
42
+ * Map mode store
48
43
  */
49
- declare function getOrCreateRequestModeChange(instanceId: UniqueId): (desiredMode: string, requestOwner: string) => void;
44
+ declare const modeStore: MapStore<MapModeState, MapModeActions>;
45
+ /**
46
+ * Get the current mode for a map instance
47
+ */
48
+ declare function getMode(mapId: UniqueId): string;
49
+ /**
50
+ * Hook for current mode value
51
+ */
52
+ declare function useMode(mapId: UniqueId): string;
50
53
  /**
51
54
  * Get the owner of the current mode for a given map instance
52
55
  * @internal - For internal map-toolkit use only
53
56
  */
54
57
  declare function getCurrentModeOwner(instanceId: UniqueId): string | undefined;
58
+ /**
59
+ * Check if a given owner is registered as the owner of any mode.
60
+ * This includes both active mode owners and pending mode requests.
61
+ * @internal - For internal map-toolkit use only
62
+ */
63
+ declare function isRegisteredModeOwner(instanceId: UniqueId, owner: string): boolean;
55
64
  /**
56
65
  * Manually clear map mode state for a specific instanceId.
57
- * This is typically not needed as cleanup happens automatically when all subscribers unmount.
58
- * Use this only in advanced scenarios where manual cleanup is required.
59
- *
60
- * @param instanceId - The unique identifier for the map mode instance to clear
61
- *
62
- * @example
63
- * ```tsx
64
- * // Manual cleanup (rarely needed)
65
- * clearMapModeState('my-map-instance');
66
- * ```
67
66
  */
68
67
  declare function clearMapModeState(instanceId: UniqueId): void;
69
68
  //#endregion
70
- export { clearMapModeState, getCurrentModeOwner, getOrCreateRequestModeChange, getOrCreateServerSnapshot, getOrCreateSnapshot, getOrCreateSubscription };
69
+ export { clearMapModeState, getCurrentModeOwner, getMode, isRegisteredModeOwner, modeStore, useMode };
71
70
  //# sourceMappingURL=store.d.ts.map