@honua/react 0.0.20-alpha.0 → 0.1.1-beta.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 (363) hide show
  1. package/contract/discovery.d.ts +231 -0
  2. package/contract/discovery.d.ts.map +1 -0
  3. package/contract/discovery.js +554 -0
  4. package/contract/edit-sketch.d.ts +8 -0
  5. package/contract/edit-sketch.d.ts.map +1 -1
  6. package/contract/edit-sketch.js +15 -0
  7. package/contract/edit-snapping.d.ts +160 -0
  8. package/contract/edit-snapping.d.ts.map +1 -0
  9. package/contract/edit-snapping.js +675 -0
  10. package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
  11. package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
  12. package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
  13. package/contract/index.d.ts +12 -1
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -0
  16. package/contract/pmtiles.d.ts +193 -0
  17. package/contract/pmtiles.d.ts.map +1 -0
  18. package/contract/pmtiles.js +195 -0
  19. package/contract/protocol-module.d.ts +70 -0
  20. package/contract/protocol-module.d.ts.map +1 -0
  21. package/contract/protocol-module.js +1 -0
  22. package/contract/schema-envelope.d.ts +26 -0
  23. package/contract/schema-envelope.d.ts.map +1 -0
  24. package/contract/schema-envelope.js +25 -0
  25. package/contract/schema.d.ts +582 -0
  26. package/contract/schema.d.ts.map +1 -0
  27. package/contract/schema.js +2483 -0
  28. package/contract/source-capability-support.d.ts +6 -0
  29. package/contract/source-capability-support.d.ts.map +1 -0
  30. package/contract/source-capability-support.js +154 -0
  31. package/contract/source.d.ts +57 -15
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +368 -65
  34. package/contract/types.d.ts +171 -6
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +14 -14
  37. package/core/auth/client-credentials-provider.d.ts +51 -0
  38. package/core/auth/client-credentials-provider.d.ts.map +1 -0
  39. package/core/auth/client-credentials-provider.js +115 -0
  40. package/core/auth/credential-store.d.ts +52 -0
  41. package/core/auth/credential-store.d.ts.map +1 -0
  42. package/core/auth/credential-store.js +82 -0
  43. package/core/auth/index.d.ts +23 -0
  44. package/core/auth/index.d.ts.map +1 -0
  45. package/core/auth/index.js +20 -0
  46. package/core/auth/oauth2-provider.d.ts +114 -0
  47. package/core/auth/oauth2-provider.d.ts.map +1 -0
  48. package/core/auth/oauth2-provider.js +408 -0
  49. package/core/auth/pkce.d.ts +25 -0
  50. package/core/auth/pkce.d.ts.map +1 -0
  51. package/core/auth/pkce.js +46 -0
  52. package/core/auth/static-providers.d.ts +20 -0
  53. package/core/auth/static-providers.d.ts.map +1 -0
  54. package/core/auth/static-providers.js +26 -0
  55. package/core/auth/token-endpoint.d.ts +40 -0
  56. package/core/auth/token-endpoint.d.ts.map +1 -0
  57. package/core/auth/token-endpoint.js +133 -0
  58. package/core/auth/types.d.ts +144 -0
  59. package/core/auth/types.d.ts.map +1 -0
  60. package/core/auth/types.js +8 -0
  61. package/core/bounded-json.d.ts +4 -0
  62. package/core/bounded-json.d.ts.map +1 -0
  63. package/core/bounded-json.js +38 -0
  64. package/core/cache-state.d.ts +9 -0
  65. package/core/cache-state.d.ts.map +1 -1
  66. package/core/cache-state.js +10 -0
  67. package/core/capabilities-xml.d.ts +33 -0
  68. package/core/capabilities-xml.d.ts.map +1 -0
  69. package/core/capabilities-xml.js +331 -0
  70. package/core/client.d.ts +35 -2
  71. package/core/client.d.ts.map +1 -1
  72. package/core/client.js +167 -29
  73. package/core/error-classifications.d.ts +153 -0
  74. package/core/error-classifications.d.ts.map +1 -0
  75. package/core/error-classifications.js +140 -0
  76. package/core/error-code-registry.d.ts +158 -0
  77. package/core/error-code-registry.d.ts.map +1 -0
  78. package/core/error-code-registry.js +143 -0
  79. package/core/error-envelope.d.ts +68 -0
  80. package/core/error-envelope.d.ts.map +1 -0
  81. package/core/error-envelope.js +357 -0
  82. package/core/errors.d.ts +73 -25
  83. package/core/errors.d.ts.map +1 -1
  84. package/core/errors.js +113 -46
  85. package/core/geoparquet-sql.d.ts +190 -0
  86. package/core/geoparquet-sql.d.ts.map +1 -0
  87. package/core/geoparquet-sql.js +368 -0
  88. package/core/odata-key-path.d.ts +12 -0
  89. package/core/odata-key-path.d.ts.map +1 -0
  90. package/core/odata-key-path.js +28 -0
  91. package/core/odata-write-codec.d.ts +66 -0
  92. package/core/odata-write-codec.d.ts.map +1 -0
  93. package/core/odata-write-codec.js +1083 -0
  94. package/core/odata.d.ts +59 -7
  95. package/core/odata.d.ts.map +1 -1
  96. package/core/odata.js +431 -99
  97. package/core/ogc-endpoint-layout.d.ts +70 -0
  98. package/core/ogc-endpoint-layout.d.ts.map +1 -0
  99. package/core/ogc-endpoint-layout.js +173 -0
  100. package/core/ogc-features.d.ts.map +1 -1
  101. package/core/ogc-features.js +35 -17
  102. package/core/ogc-maps.d.ts +7 -0
  103. package/core/ogc-maps.d.ts.map +1 -1
  104. package/core/ogc-maps.js +46 -7
  105. package/core/ogc-processes.d.ts +2 -1
  106. package/core/ogc-processes.d.ts.map +1 -1
  107. package/core/ogc-processes.js +33 -5
  108. package/core/ogc-records.d.ts +9 -0
  109. package/core/ogc-records.d.ts.map +1 -1
  110. package/core/ogc-records.js +78 -29
  111. package/core/ogc-tiles.d.ts +10 -1
  112. package/core/ogc-tiles.d.ts.map +1 -1
  113. package/core/ogc-tiles.js +56 -17
  114. package/core/path-utils.d.ts +9 -0
  115. package/core/path-utils.d.ts.map +1 -1
  116. package/core/path-utils.js +19 -0
  117. package/core/protocol-transport.d.ts +1 -0
  118. package/core/protocol-transport.d.ts.map +1 -1
  119. package/core/request-pipeline.d.ts.map +1 -1
  120. package/core/request-pipeline.js +1 -1
  121. package/core/spatial-filter.d.ts +8 -4
  122. package/core/spatial-filter.d.ts.map +1 -1
  123. package/core/spatial-filter.js +206 -16
  124. package/core/stac-static.d.ts +55 -0
  125. package/core/stac-static.d.ts.map +1 -0
  126. package/core/stac-static.js +386 -0
  127. package/core/stac.d.ts +12 -0
  128. package/core/stac.d.ts.map +1 -1
  129. package/core/stac.js +45 -15
  130. package/core/surfaces.d.ts +13 -0
  131. package/core/surfaces.d.ts.map +1 -1
  132. package/core/surfaces.js +127 -16
  133. package/core/types.d.ts +174 -7
  134. package/core/types.d.ts.map +1 -1
  135. package/core/wfs-capabilities.d.ts +12 -0
  136. package/core/wfs-capabilities.d.ts.map +1 -1
  137. package/core/wfs-capabilities.js +22 -3
  138. package/core/wfs.d.ts +13 -0
  139. package/core/wfs.d.ts.map +1 -1
  140. package/core/wfs.js +35 -5
  141. package/core/wms-capabilities.d.ts +56 -84
  142. package/core/wms-capabilities.d.ts.map +1 -1
  143. package/core/wms-capabilities.js +212 -509
  144. package/core/wmts-capabilities.d.ts +62 -62
  145. package/core/wmts-capabilities.d.ts.map +1 -1
  146. package/core/wmts-capabilities.js +224 -371
  147. package/esri-compat/editor.d.ts +8 -0
  148. package/esri-compat/editor.d.ts.map +1 -1
  149. package/esri-compat/editor.js +13 -0
  150. package/esri-compat/feature-layer.d.ts +2 -0
  151. package/esri-compat/feature-layer.d.ts.map +1 -1
  152. package/esri-compat/feature-layer.js +1 -0
  153. package/esri-compat/identity-manager.d.ts +43 -0
  154. package/esri-compat/identity-manager.d.ts.map +1 -1
  155. package/esri-compat/identity-manager.js +131 -1
  156. package/esri-compat/layer-list.d.ts +11 -0
  157. package/esri-compat/layer-list.d.ts.map +1 -1
  158. package/esri-compat/layer-list.js +49 -0
  159. package/esri-compat/legend.d.ts +10 -0
  160. package/esri-compat/legend.d.ts.map +1 -1
  161. package/esri-compat/legend.js +27 -0
  162. package/esri-compat/oauth-info.d.ts +15 -0
  163. package/esri-compat/oauth-info.d.ts.map +1 -1
  164. package/esri-compat/oauth-info.js +27 -0
  165. package/esri-compat/portal.d.ts +197 -0
  166. package/esri-compat/portal.d.ts.map +1 -0
  167. package/esri-compat/portal.js +251 -0
  168. package/esri-compat/renderer-objects.d.ts +40 -0
  169. package/esri-compat/renderer-objects.d.ts.map +1 -0
  170. package/esri-compat/renderer-objects.js +62 -0
  171. package/esri-compat/sketch.d.ts +43 -0
  172. package/esri-compat/sketch.d.ts.map +1 -1
  173. package/esri-compat/sketch.js +46 -0
  174. package/esri-compat/snapping.d.ts +43 -0
  175. package/esri-compat/snapping.d.ts.map +1 -0
  176. package/esri-compat/snapping.js +45 -0
  177. package/esri-compat/url.d.ts.map +1 -1
  178. package/esri-compat/url.js +1 -1
  179. package/esri-compat/widget-host.d.ts +84 -0
  180. package/esri-compat/widget-host.d.ts.map +1 -0
  181. package/esri-compat/widget-host.js +159 -0
  182. package/filter-registry/index.d.ts +145 -0
  183. package/filter-registry/index.d.ts.map +1 -0
  184. package/filter-registry/index.js +531 -0
  185. package/generated-app/errors.d.ts +18 -2
  186. package/generated-app/errors.d.ts.map +1 -1
  187. package/generated-app/errors.js +44 -11
  188. package/map/automatic-mount-integration.d.ts +134 -0
  189. package/map/automatic-mount-integration.d.ts.map +1 -0
  190. package/map/automatic-mount-integration.js +305 -0
  191. package/map/automatic-source-strategy.d.ts +125 -0
  192. package/map/automatic-source-strategy.d.ts.map +1 -0
  193. package/map/automatic-source-strategy.js +494 -0
  194. package/map/data-to-map-bridge.d.ts +293 -0
  195. package/map/data-to-map-bridge.d.ts.map +1 -0
  196. package/map/data-to-map-bridge.js +817 -0
  197. package/map/geojson-projection.d.ts +33 -0
  198. package/map/geojson-projection.d.ts.map +1 -0
  199. package/map/geojson-projection.js +118 -0
  200. package/map/index.d.ts +14 -0
  201. package/map/index.d.ts.map +1 -1
  202. package/map/index.js +8 -0
  203. package/map/raster-source-spec.d.ts +30 -0
  204. package/map/raster-source-spec.d.ts.map +1 -0
  205. package/map/raster-source-spec.js +190 -0
  206. package/map/raster-source-strategy.d.ts +65 -0
  207. package/map/raster-source-strategy.d.ts.map +1 -0
  208. package/map/raster-source-strategy.js +232 -0
  209. package/map/source-to-maplibre.d.ts +91 -0
  210. package/map/source-to-maplibre.d.ts.map +1 -0
  211. package/map/source-to-maplibre.js +454 -0
  212. package/map/temporal-playback.d.ts +139 -0
  213. package/map/temporal-playback.d.ts.map +1 -0
  214. package/map/temporal-playback.js +247 -0
  215. package/package.json +19 -1
  216. package/query-planner/canonical.d.ts +7 -0
  217. package/query-planner/canonical.d.ts.map +1 -0
  218. package/query-planner/canonical.js +145 -0
  219. package/query-planner/cql2-json.d.ts +17 -0
  220. package/query-planner/cql2-json.d.ts.map +1 -0
  221. package/query-planner/cql2-json.js +646 -0
  222. package/query-planner/diagnostics.d.ts +35 -0
  223. package/query-planner/diagnostics.d.ts.map +1 -0
  224. package/query-planner/diagnostics.js +373 -0
  225. package/query-planner/duckdb.d.ts +79 -0
  226. package/query-planner/duckdb.d.ts.map +1 -0
  227. package/query-planner/duckdb.js +758 -0
  228. package/query-planner/execution-context.d.ts +5 -0
  229. package/query-planner/execution-context.d.ts.map +1 -0
  230. package/query-planner/execution-context.js +50 -0
  231. package/query-planner/executor.d.ts +5 -0
  232. package/query-planner/executor.d.ts.map +1 -0
  233. package/query-planner/executor.js +223 -0
  234. package/query-planner/geoservices.d.ts +91 -0
  235. package/query-planner/geoservices.d.ts.map +1 -0
  236. package/query-planner/geoservices.js +667 -0
  237. package/query-planner/grpc.d.ts +100 -0
  238. package/query-planner/grpc.d.ts.map +1 -0
  239. package/query-planner/grpc.js +736 -0
  240. package/query-planner/index.d.ts +44 -0
  241. package/query-planner/index.d.ts.map +1 -0
  242. package/query-planner/index.js +30 -0
  243. package/query-planner/ir.d.ts +20 -0
  244. package/query-planner/ir.d.ts.map +1 -0
  245. package/query-planner/ir.js +417 -0
  246. package/query-planner/local-aggregate.d.ts +5 -0
  247. package/query-planner/local-aggregate.d.ts.map +1 -0
  248. package/query-planner/local-aggregate.js +126 -0
  249. package/query-planner/odata.d.ts +48 -0
  250. package/query-planner/odata.d.ts.map +1 -0
  251. package/query-planner/odata.js +662 -0
  252. package/query-planner/ogc-compiler.d.ts +55 -0
  253. package/query-planner/ogc-compiler.d.ts.map +1 -0
  254. package/query-planner/ogc-compiler.js +480 -0
  255. package/query-planner/ogc-features.d.ts +53 -0
  256. package/query-planner/ogc-features.d.ts.map +1 -0
  257. package/query-planner/ogc-features.js +617 -0
  258. package/query-planner/planner.d.ts +17 -0
  259. package/query-planner/planner.d.ts.map +1 -0
  260. package/query-planner/planner.js +818 -0
  261. package/query-planner/resource.d.ts +94 -0
  262. package/query-planner/resource.d.ts.map +1 -0
  263. package/query-planner/resource.js +526 -0
  264. package/query-planner/semantic-canonical.d.ts +15 -0
  265. package/query-planner/semantic-canonical.d.ts.map +1 -0
  266. package/query-planner/semantic-canonical.js +62 -0
  267. package/query-planner/semantic-compat.d.ts +10 -0
  268. package/query-planner/semantic-compat.d.ts.map +1 -0
  269. package/query-planner/semantic-compat.js +35 -0
  270. package/query-planner/semantic-compiler.d.ts +64 -0
  271. package/query-planner/semantic-compiler.d.ts.map +1 -0
  272. package/query-planner/semantic-compiler.js +81 -0
  273. package/query-planner/semantic-literals.d.ts +30 -0
  274. package/query-planner/semantic-literals.d.ts.map +1 -0
  275. package/query-planner/semantic-literals.js +133 -0
  276. package/query-planner/semantic-types.d.ts +265 -0
  277. package/query-planner/semantic-types.d.ts.map +1 -0
  278. package/query-planner/semantic-types.js +1 -0
  279. package/query-planner/semantic.d.ts +54 -0
  280. package/query-planner/semantic.d.ts.map +1 -0
  281. package/query-planner/semantic.js +1421 -0
  282. package/query-planner/types.d.ts +560 -0
  283. package/query-planner/types.d.ts.map +1 -0
  284. package/query-planner/types.js +49 -0
  285. package/query-planner/wfs-semantic.d.ts +58 -0
  286. package/query-planner/wfs-semantic.d.ts.map +1 -0
  287. package/query-planner/wfs-semantic.js +547 -0
  288. package/query-planner/wfs.d.ts +6 -0
  289. package/query-planner/wfs.d.ts.map +1 -0
  290. package/query-planner/wfs.js +71 -0
  291. package/react/external-map.d.ts +67 -0
  292. package/react/external-map.d.ts.map +1 -0
  293. package/react/external-map.js +63 -0
  294. package/react/honua-map.d.ts.map +1 -1
  295. package/react/honua-map.js +3 -2
  296. package/react/honua-source-layer.d.ts +110 -0
  297. package/react/honua-source-layer.d.ts.map +1 -0
  298. package/react/honua-source-layer.js +101 -0
  299. package/react/hooks.d.ts +28 -0
  300. package/react/hooks.d.ts.map +1 -1
  301. package/react/hooks.js +26 -4
  302. package/react/index.d.ts +8 -0
  303. package/react/index.d.ts.map +1 -1
  304. package/react/index.js +5 -0
  305. package/react/query-cache.d.ts +26 -4
  306. package/react/query-cache.d.ts.map +1 -1
  307. package/react/query-cache.js +51 -8
  308. package/react/selection.d.ts +177 -0
  309. package/react/selection.d.ts.map +1 -0
  310. package/react/selection.js +0 -0
  311. package/react/use-mounted-source.d.ts +69 -0
  312. package/react/use-mounted-source.d.ts.map +1 -0
  313. package/react/use-mounted-source.js +346 -0
  314. package/runtime/edit-snapping.d.ts +107 -0
  315. package/runtime/edit-snapping.d.ts.map +1 -0
  316. package/runtime/edit-snapping.js +190 -0
  317. package/runtime/errors.d.ts +3 -2
  318. package/runtime/errors.d.ts.map +1 -1
  319. package/runtime/errors.js +19 -2
  320. package/runtime/index.d.ts +8 -2
  321. package/runtime/index.d.ts.map +1 -1
  322. package/runtime/index.js +4 -4
  323. package/runtime/load-package.d.ts.map +1 -1
  324. package/runtime/load-package.js +8 -0
  325. package/runtime/map-package-validation.d.ts +0 -13
  326. package/runtime/map-package-validation.d.ts.map +1 -1
  327. package/runtime/map-package-validation.js +0 -12
  328. package/runtime/map-package.d.ts +1 -1
  329. package/runtime/map-package.d.ts.map +1 -1
  330. package/runtime/maplibre-renderer.d.ts +41 -0
  331. package/runtime/maplibre-renderer.d.ts.map +1 -0
  332. package/runtime/maplibre-renderer.js +322 -0
  333. package/runtime/pmtiles-protocol.d.ts +77 -0
  334. package/runtime/pmtiles-protocol.d.ts.map +1 -0
  335. package/runtime/pmtiles-protocol.js +120 -0
  336. package/runtime/query-tiles.d.ts +2 -1
  337. package/runtime/query-tiles.d.ts.map +1 -1
  338. package/runtime/query-tiles.js +6 -2
  339. package/runtime/source-bridge.d.ts +4 -4
  340. package/runtime/source-bridge.d.ts.map +1 -1
  341. package/runtime/source-bridge.js +36 -56
  342. package/runtime/style-interactions.d.ts +3 -2
  343. package/runtime/style-interactions.d.ts.map +1 -1
  344. package/runtime/style-interactions.js +11 -3
  345. package/runtime/terra-draw-sketch.d.ts +275 -0
  346. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  347. package/runtime/terra-draw-sketch.js +404 -0
  348. package/source-capability-registry.d.ts +1 -0
  349. package/source-capability-registry.js +1 -0
  350. package/source-capability-types.d.ts +186 -0
  351. package/source-capability-types.js +7 -0
  352. package/style/index.d.ts +2 -0
  353. package/style/index.d.ts.map +1 -1
  354. package/style/index.js +2 -0
  355. package/style/renderers.d.ts +326 -0
  356. package/style/renderers.d.ts.map +1 -0
  357. package/style/renderers.js +517 -0
  358. package/webmap/convert-renderer.d.ts +24 -3
  359. package/webmap/convert-renderer.d.ts.map +1 -1
  360. package/webmap/convert-renderer.js +93 -112
  361. package/webmap/index.d.ts +1 -1
  362. package/webmap/index.d.ts.map +1 -1
  363. package/webmap/index.js +1 -1
@@ -0,0 +1,346 @@
1
+ /**
2
+ * {@link useMountedSource} — the React lifecycle wrapper around the
3
+ * data-to-map bridge's `mountSource` (REQ-002 of the react depth pass).
4
+ *
5
+ * One bridge mount per effect run: the mount effect creates a fresh
6
+ * `MountedSource` and its cleanup aborts any in-flight mount and disposes the
7
+ * handle (idempotent), so React 18/19 StrictMode's mount → unmount → mount
8
+ * never leaks MapLibre sources, layers, or listeners and never double-adds.
9
+ *
10
+ * Prop changes are diffed instead of torn down:
11
+ * - `query` → `handle.setFilter(query)` (GeoJSON `setData` / tile URL rewrite);
12
+ * - `renderer` (a first-class renderer object from `@honua/sdk-js/style`) →
13
+ * `handle.setRenderer(renderer)`, which diffs paint/layout in place and
14
+ * recreates only the owned layers on a structural renderer change;
15
+ * - `paint` / `layout` → per-property `map.setPaintProperty` /
16
+ * `map.setLayoutProperty` on the bridge-owned layers (falling back to a full
17
+ * remount when the host map does not expose those methods);
18
+ * - structural options (strategy, ids, custom `layers`, geometry, tiles…) →
19
+ * remount.
20
+ *
21
+ * SSR-safe: no `window` / `document` access at module scope.
22
+ *
23
+ * @module
24
+ */
25
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
26
+ import { mountSource } from "../map/data-to-map-bridge.js";
27
+ import { defaultPaint, defaultPolygonOutlinePaint } from "../map/geojson-projection.js";
28
+ import { useHonuaMap } from "./external-map.js";
29
+ import { stableQueryHash } from "./query-cache.js";
30
+ const IDLE_STATE = Object.freeze({
31
+ handle: null,
32
+ diagnostics: null,
33
+ error: undefined,
34
+ isMounting: false,
35
+ });
36
+ function isAbortError(error) {
37
+ return ((error instanceof DOMException && error.name === "AbortError") ||
38
+ (error instanceof Error && error.name === "AbortError"));
39
+ }
40
+ /**
41
+ * Everything that requires a structural remount when it changes. `query`,
42
+ * `renderer`, `paint`, and `layout` are intentionally absent (diff-updated in
43
+ * place through `setFilter` / `setRenderer` / property setters);
44
+ * function-valued options (`popup.factory` / `popup.render`, callbacks) are
45
+ * read through a ref so their identity never forces a remount.
46
+ */
47
+ function structuralSignature(options) {
48
+ return stableQueryHash({
49
+ strategy: options.strategy,
50
+ sourceId: options.sourceId,
51
+ layerId: options.layerId,
52
+ beforeId: options.beforeId,
53
+ geometry: options.geometry,
54
+ layers: options.layers,
55
+ maxGeoJsonFeatures: options.maxGeoJsonFeatures,
56
+ queryTiles: options.queryTiles,
57
+ sourceLayer: options.sourceLayer,
58
+ attribution: options.attribution,
59
+ fitBounds: options.fitBounds,
60
+ hover: options.hover,
61
+ popup: options.popup
62
+ ? {
63
+ fields: options.popup.fields,
64
+ title: options.popup.title,
65
+ // Adding/removing a custom renderer rewires the bridge's popup
66
+ // binding (structural); identity changes of an existing renderer or
67
+ // factory are delivered through the stable wrappers below instead.
68
+ hasRender: options.popup.render !== undefined,
69
+ }
70
+ : undefined,
71
+ });
72
+ }
73
+ /**
74
+ * Stable identity for a renderer-object option: its serializable descriptor.
75
+ * Renderer instances are commonly rebuilt inline every render, so identity
76
+ * must come from `toJSON()`, not object reference.
77
+ */
78
+ function rendererSignature(renderer) {
79
+ if (renderer === undefined)
80
+ return "none";
81
+ try {
82
+ return stableQueryHash(renderer.toJSON());
83
+ }
84
+ catch {
85
+ // Malformed renderer objects fail mountSource validation with a typed
86
+ // error; the signature only needs to be deterministic.
87
+ return stableQueryHash({ kind: renderer.kind });
88
+ }
89
+ }
90
+ /**
91
+ * Wrap a popup option so its function-valued members (`factory`, `render`)
92
+ * are resolved from the latest props at call time. The bridge captures the
93
+ * popup object once at mount; without this, a renderer or factory that closes
94
+ * over React state would keep using its mount-time closure.
95
+ */
96
+ function livePopupOptions(snapshot, optionsRef) {
97
+ return {
98
+ ...snapshot,
99
+ factory: () => (optionsRef.current.popup ?? snapshot).factory(),
100
+ ...(snapshot.render !== undefined
101
+ ? {
102
+ render: (context) => (optionsRef.current.popup?.render ?? snapshot.render)?.(context),
103
+ }
104
+ : {}),
105
+ };
106
+ }
107
+ const KIND_BY_SUFFIX = [
108
+ ["-polygon-outline", "polygonOutline"],
109
+ ["-polygon", "polygon"],
110
+ ["-line", "line"],
111
+ ["-point", "point"],
112
+ ];
113
+ function kindForLayerId(layerId) {
114
+ for (const [suffix, kind] of KIND_BY_SUFFIX) {
115
+ if (layerId.endsWith(suffix))
116
+ return kind;
117
+ }
118
+ return undefined;
119
+ }
120
+ function defaultPaintFor(kind) {
121
+ return kind === "polygonOutline" ? defaultPolygonOutlinePaint() : defaultPaint(kind);
122
+ }
123
+ /**
124
+ * Apply a `paint` / `layout` change to the bridge's default layer matrix in
125
+ * place. Returns `false` when the host map lacks the property setters (the
126
+ * caller then falls back to a structural remount). Keys removed from an
127
+ * override are restored to the bridge default (paint) or reset (`undefined`,
128
+ * layout), keyed off the previously applied override.
129
+ */
130
+ function applyRendererDiff(map, handle, previous, next) {
131
+ const host = map;
132
+ if (typeof host.setPaintProperty !== "function" || typeof host.setLayoutProperty !== "function") {
133
+ return false;
134
+ }
135
+ for (const layerId of handle.layerIds) {
136
+ const kind = kindForLayerId(layerId);
137
+ if (kind === undefined)
138
+ return false; // custom layer set: structural
139
+ const defaults = defaultPaintFor(kind);
140
+ const prevPaint = previous?.paint?.[kind] ?? {};
141
+ const nextPaint = next.paint?.[kind] ?? {};
142
+ for (const key of new Set([...Object.keys(prevPaint), ...Object.keys(nextPaint)])) {
143
+ host.setPaintProperty(layerId, key, key in nextPaint ? nextPaint[key] : defaults[key]);
144
+ }
145
+ const prevLayout = previous?.layout?.[kind] ?? {};
146
+ const nextLayout = next.layout?.[kind] ?? {};
147
+ for (const key of new Set([...Object.keys(prevLayout), ...Object.keys(nextLayout)])) {
148
+ host.setLayoutProperty(layerId, key, key in nextLayout ? nextLayout[key] : undefined);
149
+ }
150
+ }
151
+ return true;
152
+ }
153
+ /**
154
+ * Mount a contract `Source` on a MapLibre map through the data-to-map bridge
155
+ * for the lifetime of the component.
156
+ *
157
+ * Pass the map explicitly, or pass `undefined` to resolve it from the nearest
158
+ * {@link HonuaMapProvider} / `HonuaMap` (external `@vis.gl/react-maplibre`
159
+ * interop works the same way — hand over `mapRef.getMap()`).
160
+ *
161
+ * @example
162
+ * ```tsx
163
+ * const { diagnostics, error } = useMountedSource(map, source, {
164
+ * query: { where: "STATUS = 'OPEN'" },
165
+ * hover: true,
166
+ * onDiagnostics: (d) => console.log(d.strategy, d.featureCount),
167
+ * });
168
+ * ```
169
+ *
170
+ * @experimental
171
+ */
172
+ export function useMountedSource(map, source, options = {}) {
173
+ const resolvedMap = useHonuaMap(map);
174
+ const enabled = options.enabled ?? true;
175
+ // Latest options snapshot for effects: mount reads it once per run; the
176
+ // diff effects read it when their hash keys change. Callback / factory
177
+ // identity changes therefore never remount or re-run anything.
178
+ const optionsRef = useRef(options);
179
+ optionsRef.current = options;
180
+ const structuralKey = structuralSignature(options);
181
+ const queryKey = stableQueryHash(options.query ?? null);
182
+ const rendererKey = stableQueryHash({ paint: options.paint, layout: options.layout });
183
+ const rendererObjectKey = rendererSignature(options.renderer);
184
+ const [remountToken, setRemountToken] = useState(0);
185
+ const [state, setState] = useState(IDLE_STATE);
186
+ // Hash keys applied by the *current* mount; diff effects compare against
187
+ // these so a fresh mount (which reads the latest options) is never followed
188
+ // by a redundant setFilter / repaint.
189
+ const appliedRef = useRef(null);
190
+ // ── Mount / structural remount ─────────────────────────────────
191
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `structuralKey` hashes every structural option; the rest is read via optionsRef.
192
+ useEffect(() => {
193
+ if (!resolvedMap || !source || !enabled) {
194
+ appliedRef.current = null;
195
+ setState(IDLE_STATE);
196
+ return;
197
+ }
198
+ let cancelled = false;
199
+ let mounted = null;
200
+ const controller = new AbortController();
201
+ const snapshot = optionsRef.current;
202
+ const { enabled: _enabled, onDiagnostics: _onDiagnostics, onError: _onError, ...mountOptions } = snapshot;
203
+ appliedRef.current = null;
204
+ setState({ handle: null, diagnostics: null, error: undefined, isMounting: true });
205
+ // Popup callbacks are wrapped so the bridge (which captures the popup
206
+ // object once) always calls the latest `factory` / `render` props.
207
+ const popup = mountOptions.popup ? livePopupOptions(mountOptions.popup, optionsRef) : undefined;
208
+ mountSource(resolvedMap, source, {
209
+ ...mountOptions,
210
+ ...(popup ? { popup } : {}),
211
+ signal: controller.signal,
212
+ }).then((handle) => {
213
+ if (cancelled) {
214
+ // StrictMode / fast unmount: the mount resolved after cleanup ran.
215
+ // Dispose immediately — dispose() is idempotent, so a second call
216
+ // from a racing cleanup is safe.
217
+ handle.dispose();
218
+ return;
219
+ }
220
+ mounted = handle;
221
+ appliedRef.current = {
222
+ query: stableQueryHash(snapshot.query ?? null),
223
+ renderer: stableQueryHash({ paint: snapshot.paint, layout: snapshot.layout }),
224
+ rendererObject: rendererSignature(snapshot.renderer),
225
+ };
226
+ const diagnostics = handle.diagnostics;
227
+ setState({ handle, diagnostics, error: undefined, isMounting: false });
228
+ optionsRef.current.onDiagnostics?.(diagnostics);
229
+ }, (error) => {
230
+ if (cancelled || controller.signal.aborted || isAbortError(error))
231
+ return;
232
+ setState({ handle: null, diagnostics: null, error, isMounting: false });
233
+ optionsRef.current.onError?.(error);
234
+ });
235
+ return () => {
236
+ cancelled = true;
237
+ // Abort first so an in-flight mount never touches the map, then dispose
238
+ // whatever this run mounted. Both are idempotent, so the StrictMode
239
+ // mount → cleanup → mount sequence releases every MapLibre resource.
240
+ controller.abort(new DOMException("useMountedSource unmounted", "AbortError"));
241
+ try {
242
+ mounted?.dispose();
243
+ }
244
+ catch (error) {
245
+ optionsRef.current.onError?.(error);
246
+ }
247
+ };
248
+ }, [resolvedMap, source, enabled, structuralKey, remountToken]);
249
+ // ── Query prop → setFilter diff update ─────────────────────────
250
+ const handle = state.handle;
251
+ useEffect(() => {
252
+ if (!handle || handle.state === "disposed")
253
+ return;
254
+ const applied = appliedRef.current;
255
+ if (!applied || applied.query === queryKey)
256
+ return;
257
+ applied.query = queryKey;
258
+ handle.setFilter(optionsRef.current.query).then((diagnostics) => {
259
+ if (handle.state === "disposed")
260
+ return;
261
+ setState((current) => (current.handle === handle ? { ...current, diagnostics, error: undefined } : current));
262
+ optionsRef.current.onDiagnostics?.(diagnostics);
263
+ }, (error) => {
264
+ if (handle.state === "disposed" || isAbortError(error))
265
+ return;
266
+ setState((current) => (current.handle === handle ? { ...current, error } : current));
267
+ optionsRef.current.onError?.(error);
268
+ });
269
+ }, [handle, queryKey]);
270
+ // ── renderer object prop → setRenderer diff update ─────────────
271
+ useEffect(() => {
272
+ if (!handle || handle.state === "disposed")
273
+ return;
274
+ const applied = appliedRef.current;
275
+ if (!applied || applied.rendererObject === rendererObjectKey)
276
+ return;
277
+ applied.rendererObject = rendererObjectKey;
278
+ handle.setRenderer(optionsRef.current.renderer).then((diagnostics) => {
279
+ if (handle.state === "disposed")
280
+ return;
281
+ setState((current) => (current.handle === handle ? { ...current, diagnostics, error: undefined } : current));
282
+ optionsRef.current.onDiagnostics?.(diagnostics);
283
+ }, (error) => {
284
+ if (handle.state === "disposed" || isAbortError(error))
285
+ return;
286
+ setState((current) => (current.handle === handle ? { ...current, error } : current));
287
+ optionsRef.current.onError?.(error);
288
+ });
289
+ }, [handle, rendererObjectKey]);
290
+ // ── paint/layout props → in-place repaint (or structural remount) ──
291
+ const previousRendererRef = useRef(undefined);
292
+ useEffect(() => {
293
+ if (!handle || handle.state === "disposed" || !resolvedMap)
294
+ return;
295
+ const applied = appliedRef.current;
296
+ if (!applied)
297
+ return;
298
+ if (applied.renderer === rendererKey) {
299
+ previousRendererRef.current = {
300
+ paint: optionsRef.current.paint,
301
+ layout: optionsRef.current.layout,
302
+ };
303
+ return;
304
+ }
305
+ if (optionsRef.current.renderer !== undefined) {
306
+ // A first-class renderer object owns the layer styling; the
307
+ // paint/layout override matrix applies only to geometry-default layers.
308
+ applied.renderer = rendererKey;
309
+ previousRendererRef.current = { paint: optionsRef.current.paint, layout: optionsRef.current.layout };
310
+ return;
311
+ }
312
+ const next = { paint: optionsRef.current.paint, layout: optionsRef.current.layout };
313
+ if (optionsRef.current.layers === undefined &&
314
+ applyRendererDiff(resolvedMap, handle, previousRendererRef.current, next)) {
315
+ applied.renderer = rendererKey;
316
+ previousRendererRef.current = next;
317
+ return;
318
+ }
319
+ // Custom layer sets and hosts without property setters cannot be diffed
320
+ // in place: fall back to a full, transactional remount.
321
+ setRemountToken((token) => token + 1);
322
+ }, [handle, resolvedMap, rendererKey]);
323
+ const refresh = useCallback(() => {
324
+ const current = handle;
325
+ if (!current || current.state === "disposed")
326
+ return;
327
+ current.refresh().then((diagnostics) => {
328
+ if (current.state === "disposed")
329
+ return;
330
+ setState((existing) => existing.handle === current ? { ...existing, diagnostics, error: undefined } : existing);
331
+ optionsRef.current.onDiagnostics?.(diagnostics);
332
+ }, (error) => {
333
+ if (current.state === "disposed" || isAbortError(error))
334
+ return;
335
+ setState((existing) => (existing.handle === current ? { ...existing, error } : existing));
336
+ optionsRef.current.onError?.(error);
337
+ });
338
+ }, [handle]);
339
+ return useMemo(() => ({
340
+ handle: state.handle,
341
+ diagnostics: state.diagnostics,
342
+ error: state.error,
343
+ isMounting: state.isMounting,
344
+ refresh,
345
+ }), [state, refresh]);
346
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * MapLibre runtime binding for edit-sketch snapping.
3
+ *
4
+ * Wires pointer-move on a MapLibre-shaped map to the renderer-neutral
5
+ * snapping engine in `contract/edit-snapping.ts`, applies snapped
6
+ * coordinates to an `EditSketchWorkflowModel`, surfaces snapped / unsnapped
7
+ * events, and maintains a default visual indicator via a lightweight GeoJSON
8
+ * circle layer.
9
+ *
10
+ * The map interfaces are duck-typed (same pattern as
11
+ * `interactions/feature-state.ts`) so the SDK carries no hard runtime
12
+ * dependency on `maplibre-gl`.
13
+ *
14
+ * @module
15
+ */
16
+ import type { EditSketchTool, EditSketchWorkflowModel } from "../contract/edit-sketch.js";
17
+ import { type SnapCandidate, type SnapIndex, type SnapPosition, type SnapResolution, type SnapScreenPoint, type SnappingConfig } from "../contract/edit-snapping.js";
18
+ /** Minimal subset of a MapLibre `Map` needed for snapping. */
19
+ export interface SnappingMap {
20
+ on(event: string, handler: (...args: unknown[]) => void): void;
21
+ off(event: string, handler: (...args: unknown[]) => void): void;
22
+ project(position: readonly [number, number]): SnapScreenPoint | {
23
+ x: number;
24
+ y: number;
25
+ };
26
+ addSource?(id: string, source: Record<string, unknown>): void;
27
+ removeSource?(id: string): void;
28
+ addLayer?(layer: Record<string, unknown>, beforeId?: string): void;
29
+ removeLayer?(id: string): void;
30
+ getSource?(id: string): unknown;
31
+ getLayer?(id: string): unknown;
32
+ }
33
+ export interface EditSketchSnapIndicatorOptions {
34
+ /** Render the default indicator layer. @default true */
35
+ enabled?: boolean;
36
+ /** Indicator source and layer id. @default "honua-snap-indicator" */
37
+ id?: string;
38
+ /** Paint overrides merged over the default circle paint. */
39
+ paint?: Record<string, unknown>;
40
+ /** Insert the indicator layer before this layer id. */
41
+ beforeId?: string;
42
+ }
43
+ export interface EditSketchSnappingEvents {
44
+ /** Fired when the pointer acquires or changes a snap target. */
45
+ onSnap?(resolution: SnapResolution): void;
46
+ /** Fired when the pointer leaves a snap target; receives the lost candidate. */
47
+ onUnsnap?(previous: SnapCandidate): void;
48
+ }
49
+ export interface BindEditSketchSnappingOptions<T = Record<string, unknown>> extends EditSketchSnappingEvents {
50
+ /** Snap index over the loaded snap-source features. */
51
+ index: SnapIndex;
52
+ /**
53
+ * Sketch workflow whose snapping config drives resolution and that
54
+ * receives snapped geometry via {@link EditSketchSnappingHandle.applySketchGeometry}.
55
+ * When omitted, pass `config` and consume snaps through events / `current`.
56
+ */
57
+ model?: EditSketchWorkflowModel<T>;
58
+ /** Snapping configuration when no model is bound (or overrides for one-off use). */
59
+ config?: Partial<SnappingConfig>;
60
+ /** Pointer event name to listen for. @default "mousemove" */
61
+ pointerEvent?: string;
62
+ /** Default snap indicator; `false` disables it. */
63
+ indicator?: boolean | EditSketchSnapIndicatorOptions;
64
+ }
65
+ export interface EditSketchSnappingHandle {
66
+ /** The currently snapped candidate, if any. */
67
+ readonly current: SnapCandidate | undefined;
68
+ /** Resolve a pointer position without going through a map event. */
69
+ resolve(input: {
70
+ point: SnapScreenPoint;
71
+ position: SnapPosition;
72
+ }): SnapResolution;
73
+ /** Return the snapped position for `position`, or `position` unchanged. */
74
+ snapPosition(position: SnapPosition): SnapPosition;
75
+ /** Effective snapping configuration. */
76
+ config(): SnappingConfig;
77
+ /** Merge configuration changes (writes through to the bound model). */
78
+ setConfig(config: Partial<SnappingConfig>): void;
79
+ /**
80
+ * Apply `geometry` to the bound sketch model with its active vertex
81
+ * replaced by the current snap position (no-op replacement when the
82
+ * pointer is not snapped). Returns `true` when the model accepted the
83
+ * geometry.
84
+ */
85
+ applySketchGeometry(tool: EditSketchTool, geometry: Record<string, unknown> | null): boolean;
86
+ /** Detach event handlers and remove the indicator layer. */
87
+ remove(): void;
88
+ }
89
+ /**
90
+ * Bind edit-sketch snapping to a MapLibre-shaped map.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const index = createSnapIndex();
95
+ * index.setSourceFeatures("parcels", parcels);
96
+ * const snapping = bindEditSketchSnapping(map, {
97
+ * index,
98
+ * model: workflow,
99
+ * onSnap: ({ candidate }) => showGuide(candidate),
100
+ * });
101
+ * map.on("click", (e) => {
102
+ * snapping.applySketchGeometry("point", { type: "Point", coordinates: [e.lngLat.lng, e.lngLat.lat] });
103
+ * });
104
+ * ```
105
+ */
106
+ export declare function bindEditSketchSnapping<T = Record<string, unknown>>(map: SnappingMap, options: BindEditSketchSnappingOptions<T>): EditSketchSnappingHandle;
107
+ //# sourceMappingURL=edit-snapping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-snapping.d.ts","sourceRoot":"","sources":["../../../src/runtime/edit-snapping.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * MapLibre runtime binding for edit-sketch snapping.
3
+ *
4
+ * Wires pointer-move on a MapLibre-shaped map to the renderer-neutral
5
+ * snapping engine in `contract/edit-snapping.ts`, applies snapped
6
+ * coordinates to an `EditSketchWorkflowModel`, surfaces snapped / unsnapped
7
+ * events, and maintains a default visual indicator via a lightweight GeoJSON
8
+ * circle layer.
9
+ *
10
+ * The map interfaces are duck-typed (same pattern as
11
+ * `interactions/feature-state.ts`) so the SDK carries no hard runtime
12
+ * dependency on `maplibre-gl`.
13
+ *
14
+ * @module
15
+ */
16
+ import { resolveSnapCandidate, resolveSnappingConfig, withSnappedActiveVertex, } from "../contract/edit-snapping.js";
17
+ const DEFAULT_INDICATOR_ID = "honua-snap-indicator";
18
+ const DEFAULT_INDICATOR_PAINT = Object.freeze({
19
+ "circle-radius": 6,
20
+ "circle-color": ["match", ["get", "kind"], "vertex", "#2563eb", "edge", "#0891b2", "#64748b"],
21
+ "circle-opacity": 0.9,
22
+ "circle-stroke-color": "#ffffff",
23
+ "circle-stroke-width": 2,
24
+ });
25
+ /**
26
+ * Bind edit-sketch snapping to a MapLibre-shaped map.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const index = createSnapIndex();
31
+ * index.setSourceFeatures("parcels", parcels);
32
+ * const snapping = bindEditSketchSnapping(map, {
33
+ * index,
34
+ * model: workflow,
35
+ * onSnap: ({ candidate }) => showGuide(candidate),
36
+ * });
37
+ * map.on("click", (e) => {
38
+ * snapping.applySketchGeometry("point", { type: "Point", coordinates: [e.lngLat.lng, e.lngLat.lat] });
39
+ * });
40
+ * ```
41
+ */
42
+ export function bindEditSketchSnapping(map, options) {
43
+ const { index, model } = options;
44
+ let localConfig = resolveSnappingConfig(options.config);
45
+ const effectiveConfig = () => (model && !options.config ? model.snappingConfig() : localConfig);
46
+ const pointerEvent = options.pointerEvent ?? "mousemove";
47
+ const indicator = normalizeIndicatorOptions(options.indicator);
48
+ const indicatorInstalled = indicator.enabled ? installIndicator(map, indicator) : false;
49
+ let current;
50
+ const project = (position) => {
51
+ const projected = map.project([position[0], position[1]]);
52
+ return { x: projected.x, y: projected.y };
53
+ };
54
+ const resolve = (input) => resolveSnapCandidate(index, { point: input.point, position: input.position, project }, effectiveConfig());
55
+ const applyResolution = (resolution) => {
56
+ const previous = current;
57
+ current = resolution.candidate;
58
+ if (indicatorInstalled)
59
+ updateIndicator(map, indicator.id, current);
60
+ if (current) {
61
+ if (!previous || !sameCandidate(previous, current))
62
+ options.onSnap?.(resolution);
63
+ }
64
+ else if (previous) {
65
+ options.onUnsnap?.(previous);
66
+ }
67
+ };
68
+ const onPointerMove = (...args) => {
69
+ const input = pointerInput(args[0]);
70
+ if (!input)
71
+ return;
72
+ applyResolution(resolve(input));
73
+ };
74
+ map.on(pointerEvent, onPointerMove);
75
+ return {
76
+ get current() {
77
+ return current;
78
+ },
79
+ resolve(input) {
80
+ const resolution = resolve(input);
81
+ applyResolution(resolution);
82
+ return resolution;
83
+ },
84
+ snapPosition(position) {
85
+ const resolution = resolve({ point: project(position), position });
86
+ return resolution.candidate ? resolution.candidate.position : position;
87
+ },
88
+ config() {
89
+ return effectiveConfig();
90
+ },
91
+ setConfig(config) {
92
+ if (model)
93
+ model.setSnapping(config);
94
+ localConfig = resolveSnappingConfig({ ...localConfig, ...config });
95
+ // Disabling snapping must not leave a stale snap target behind: clear
96
+ // the current candidate, indicator, and fire onUnsnap.
97
+ if (!effectiveConfig().enabled && current) {
98
+ applyResolution({ snapped: false, candidates: [] });
99
+ }
100
+ },
101
+ applySketchGeometry(tool, geometry) {
102
+ if (!model)
103
+ return false;
104
+ // Gate on the effective config so a snap acquired before snapping was
105
+ // disabled (e.g. directly via model.setSnapping) is never applied.
106
+ const active = effectiveConfig().enabled ? current : undefined;
107
+ const snapped = active ? withSnappedActiveVertex(geometry, active.position) : geometry;
108
+ const capability = model.setSketchGeometry(tool, snapped).toolCapability(tool);
109
+ return capability.state === "supported";
110
+ },
111
+ remove() {
112
+ map.off(pointerEvent, onPointerMove);
113
+ if (indicatorInstalled)
114
+ removeIndicator(map, indicator.id);
115
+ current = undefined;
116
+ },
117
+ };
118
+ }
119
+ function normalizeIndicatorOptions(input) {
120
+ const options = typeof input === "object" ? input : {};
121
+ return {
122
+ enabled: input === false ? false : (options.enabled ?? true),
123
+ id: options.id ?? DEFAULT_INDICATOR_ID,
124
+ paint: { ...DEFAULT_INDICATOR_PAINT, ...(options.paint ?? {}) },
125
+ ...(options.beforeId ? { beforeId: options.beforeId } : {}),
126
+ };
127
+ }
128
+ function installIndicator(map, options) {
129
+ if (!map.addSource || !map.addLayer)
130
+ return false;
131
+ if (!map.getSource?.(options.id)) {
132
+ map.addSource(options.id, { type: "geojson", data: emptyIndicatorData() });
133
+ }
134
+ if (!map.getLayer?.(options.id)) {
135
+ map.addLayer({ id: options.id, type: "circle", source: options.id, paint: options.paint }, options.beforeId);
136
+ }
137
+ return true;
138
+ }
139
+ function updateIndicator(map, id, candidate) {
140
+ const source = map.getSource?.(id);
141
+ source?.setData?.(candidate ? indicatorData(candidate) : emptyIndicatorData());
142
+ }
143
+ function removeIndicator(map, id) {
144
+ if (map.getLayer?.(id))
145
+ map.removeLayer?.(id);
146
+ if (map.getSource?.(id))
147
+ map.removeSource?.(id);
148
+ }
149
+ function emptyIndicatorData() {
150
+ return { type: "FeatureCollection", features: [] };
151
+ }
152
+ function indicatorData(candidate) {
153
+ return {
154
+ type: "FeatureCollection",
155
+ features: [
156
+ {
157
+ type: "Feature",
158
+ geometry: { type: "Point", coordinates: [candidate.position[0], candidate.position[1]] },
159
+ properties: {
160
+ kind: candidate.kind,
161
+ sourceId: candidate.sourceId,
162
+ featureId: candidate.featureId,
163
+ ...(candidate.vertexIndex !== undefined ? { vertexIndex: candidate.vertexIndex } : {}),
164
+ ...(candidate.segmentIndex !== undefined ? { segmentIndex: candidate.segmentIndex } : {}),
165
+ },
166
+ },
167
+ ],
168
+ };
169
+ }
170
+ function sameCandidate(a, b) {
171
+ return (a.kind === b.kind &&
172
+ a.sourceId === b.sourceId &&
173
+ a.featureId === b.featureId &&
174
+ a.vertexIndex === b.vertexIndex &&
175
+ a.segmentIndex === b.segmentIndex &&
176
+ a.position[0] === b.position[0] &&
177
+ a.position[1] === b.position[1]);
178
+ }
179
+ function pointerInput(event) {
180
+ if (!event || typeof event !== "object")
181
+ return undefined;
182
+ const candidate = event;
183
+ const point = candidate.point;
184
+ const lngLat = candidate.lngLat;
185
+ if (typeof point?.x !== "number" || typeof point.y !== "number")
186
+ return undefined;
187
+ if (typeof lngLat?.lng !== "number" || typeof lngLat.lat !== "number")
188
+ return undefined;
189
+ return { point: { x: point.x, y: point.y }, position: [lngLat.lng, lngLat.lat] };
190
+ }
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * @module
8
8
  */
9
+ import { type HonuaErrorMetadata, HonuaSdkError } from "../core/error-envelope.js";
9
10
  /**
10
11
  * Stage of the runtime pipeline a failure occurred in. Keeping this as a
11
12
  * string union lets callers switch on it without a dependency on the
@@ -25,7 +26,7 @@ export type HonuaMapPackageErrorStage = "fetch" | "load" | "validate" | "update"
25
26
  * query-time rejections forwarded by mixed-source consumers — see
26
27
  * `docs/composition.md`.
27
28
  */
28
- export declare class HonuaMapPackageError extends Error {
29
+ export declare class HonuaMapPackageError extends HonuaSdkError {
29
30
  readonly packageId: string | undefined;
30
31
  readonly stage: HonuaMapPackageErrorStage;
31
32
  readonly detail: unknown;
@@ -35,6 +36,6 @@ export declare class HonuaMapPackageError extends Error {
35
36
  stage: HonuaMapPackageErrorStage;
36
37
  detail?: unknown;
37
38
  cause?: unknown;
38
- });
39
+ } & HonuaErrorMetadata);
39
40
  }
40
41
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/runtime/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GACjC,OAAO,GACP,MAAM,GACN,UAAU,GACV,QAAQ,GACR,eAAe,GACf,aAAa,GACb,MAAM,GACN,OAAO,GACP,SAAS,CAAC;AAEd;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAgB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,SAAgB,KAAK,EAAE,yBAAyB,CAAC;IACjD,SAAgB,MAAM,EAAE,OAAO,CAAC;IAChC,SAAyB,KAAK,EAAE,OAAO,CAAC;gBAGtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,yBAAyB,CAAC;QACjC,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CASJ"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/runtime/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAA0B,MAAM,2BAA2B,CAAC;AAE3G;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GACjC,OAAO,GACP,MAAM,GACN,UAAU,GACV,QAAQ,GACR,eAAe,GACf,aAAa,GACb,MAAM,GACN,OAAO,GACP,SAAS,CAAC;AAEd;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAqB,SAAQ,aAAa;IACrD,SAAgB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,SAAgB,KAAK,EAAE,yBAAyB,CAAC;IACjD,SAAgB,MAAM,EAAE,OAAO,CAAC;IAChC,SAAyB,KAAK,EAAE,OAAO,CAAC;gBAGtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,yBAAyB,CAAC;QACjC,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,GAAG,kBAAkB;CAczB"}