@honua/app-platform 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.
- package/contract/discovery.d.ts +231 -0
- package/contract/discovery.d.ts.map +1 -0
- package/contract/discovery.js +554 -0
- package/contract/edit-sketch.d.ts +8 -0
- package/contract/edit-sketch.d.ts.map +1 -1
- package/contract/edit-sketch.js +15 -0
- package/contract/edit-snapping.d.ts +160 -0
- package/contract/edit-snapping.d.ts.map +1 -0
- package/contract/edit-snapping.js +675 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
- package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
- package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
- package/contract/index.d.ts +11 -2
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +4 -1
- package/contract/pmtiles.d.ts +31 -0
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +60 -20
- package/contract/protocol-module.d.ts +70 -0
- package/contract/protocol-module.d.ts.map +1 -0
- package/contract/protocol-module.js +1 -0
- package/contract/schema-envelope.d.ts +26 -0
- package/contract/schema-envelope.d.ts.map +1 -0
- package/contract/schema-envelope.js +25 -0
- package/contract/schema.d.ts +582 -0
- package/contract/schema.d.ts.map +1 -0
- package/contract/schema.js +2483 -0
- package/contract/source-capability-support.d.ts +6 -0
- package/contract/source-capability-support.d.ts.map +1 -0
- package/contract/source-capability-support.js +154 -0
- package/contract/source.d.ts +35 -17
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +267 -71
- package/contract/types.d.ts +140 -4
- package/contract/types.d.ts.map +1 -1
- package/contract/types.js +6 -14
- package/core/bounded-json.d.ts +4 -0
- package/core/bounded-json.d.ts.map +1 -0
- package/core/bounded-json.js +38 -0
- package/core/cache-state.d.ts +9 -0
- package/core/cache-state.d.ts.map +1 -1
- package/core/cache-state.js +10 -0
- package/core/capabilities-xml.d.ts +33 -0
- package/core/capabilities-xml.d.ts.map +1 -0
- package/core/capabilities-xml.js +331 -0
- package/core/client.d.ts +4 -1
- package/core/client.d.ts.map +1 -1
- package/core/client.js +115 -29
- package/core/error-classifications.d.ts +153 -0
- package/core/error-classifications.d.ts.map +1 -0
- package/core/error-classifications.js +140 -0
- package/core/error-code-registry.d.ts +158 -0
- package/core/error-code-registry.d.ts.map +1 -0
- package/core/error-code-registry.js +143 -0
- package/core/error-envelope.d.ts +68 -0
- package/core/error-envelope.d.ts.map +1 -0
- package/core/error-envelope.js +357 -0
- package/core/errors.d.ts +45 -29
- package/core/errors.d.ts.map +1 -1
- package/core/errors.js +97 -50
- package/core/geoparquet-sql.d.ts +30 -2
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +38 -3
- package/core/odata-key-path.d.ts +12 -0
- package/core/odata-key-path.d.ts.map +1 -0
- package/core/odata-key-path.js +28 -0
- package/core/odata-write-codec.d.ts +66 -0
- package/core/odata-write-codec.d.ts.map +1 -0
- package/core/odata-write-codec.js +1083 -0
- package/core/odata.d.ts +59 -7
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +431 -99
- package/core/ogc-features.d.ts.map +1 -1
- package/core/ogc-features.js +3 -0
- package/core/ogc-maps.d.ts +7 -0
- package/core/ogc-maps.d.ts.map +1 -1
- package/core/ogc-maps.js +46 -7
- package/core/ogc-processes.d.ts +2 -1
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +33 -5
- package/core/ogc-records.d.ts +9 -0
- package/core/ogc-records.d.ts.map +1 -1
- package/core/ogc-records.js +78 -29
- package/core/ogc-tiles.d.ts +10 -1
- package/core/ogc-tiles.d.ts.map +1 -1
- package/core/ogc-tiles.js +56 -17
- package/core/path-utils.d.ts +9 -0
- package/core/path-utils.d.ts.map +1 -1
- package/core/path-utils.js +19 -0
- package/core/protocol-transport.d.ts +1 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/request-pipeline.d.ts.map +1 -1
- package/core/request-pipeline.js +1 -1
- package/core/spatial-filter.d.ts +8 -4
- package/core/spatial-filter.d.ts.map +1 -1
- package/core/spatial-filter.js +206 -16
- package/core/stac-static.d.ts +6 -2
- package/core/stac-static.d.ts.map +1 -1
- package/core/stac-static.js +260 -69
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +69 -5
- package/core/types.d.ts +125 -7
- package/core/types.d.ts.map +1 -1
- package/core/wfs-capabilities.d.ts +2 -0
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +8 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +10 -0
- package/core/wms-capabilities.d.ts +56 -84
- package/core/wms-capabilities.d.ts.map +1 -1
- package/core/wms-capabilities.js +212 -509
- package/core/wmts-capabilities.d.ts +62 -62
- package/core/wmts-capabilities.d.ts.map +1 -1
- package/core/wmts-capabilities.js +224 -371
- package/esri-compat/editor.d.ts +8 -0
- package/esri-compat/editor.d.ts.map +1 -1
- package/esri-compat/editor.js +13 -0
- package/esri-compat/feature-layer.d.ts +2 -0
- package/esri-compat/feature-layer.d.ts.map +1 -1
- package/esri-compat/feature-layer.js +1 -0
- package/esri-compat/layer-list.d.ts +11 -0
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +49 -0
- package/esri-compat/legend.d.ts +10 -0
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +27 -0
- package/esri-compat/portal.d.ts +197 -0
- package/esri-compat/portal.d.ts.map +1 -0
- package/esri-compat/portal.js +251 -0
- package/esri-compat/renderer-objects.d.ts +40 -0
- package/esri-compat/renderer-objects.d.ts.map +1 -0
- package/esri-compat/renderer-objects.js +62 -0
- package/esri-compat/sketch.d.ts +43 -0
- package/esri-compat/sketch.d.ts.map +1 -1
- package/esri-compat/sketch.js +46 -0
- package/esri-compat/snapping.d.ts +43 -0
- package/esri-compat/snapping.d.ts.map +1 -0
- package/esri-compat/snapping.js +45 -0
- package/esri-compat/url.d.ts.map +1 -1
- package/esri-compat/url.js +1 -1
- package/esri-compat/widget-host.d.ts +84 -0
- package/esri-compat/widget-host.d.ts.map +1 -0
- package/esri-compat/widget-host.js +159 -0
- package/generated-app/errors.d.ts +18 -2
- package/generated-app/errors.d.ts.map +1 -1
- package/generated-app/errors.js +44 -11
- package/geocoding/index.d.ts +7 -0
- package/geocoding/index.d.ts.map +1 -1
- package/geocoding/index.js +5 -0
- package/geocoding/provider-http.d.ts +37 -0
- package/geocoding/provider-http.d.ts.map +1 -0
- package/geocoding/provider-http.js +91 -0
- package/geocoding/provider.d.ts +145 -0
- package/geocoding/provider.d.ts.map +1 -0
- package/geocoding/provider.js +41 -0
- package/geocoding/providers/honua.d.ts +28 -0
- package/geocoding/providers/honua.d.ts.map +1 -0
- package/geocoding/providers/honua.js +68 -0
- package/geocoding/providers/nominatim.d.ts +47 -0
- package/geocoding/providers/nominatim.d.ts.map +1 -0
- package/geocoding/providers/nominatim.js +105 -0
- package/geocoding/providers/pelias.d.ts +40 -0
- package/geocoding/providers/pelias.d.ts.map +1 -0
- package/geocoding/providers/pelias.js +115 -0
- package/geocoding/providers/photon.d.ts +39 -0
- package/geocoding/providers/photon.d.ts.map +1 -0
- package/geocoding/providers/photon.js +107 -0
- package/geometry/convert.d.ts +13 -0
- package/geometry/convert.d.ts.map +1 -0
- package/geometry/convert.js +12 -0
- package/geometry/index.d.ts +17 -0
- package/geometry/index.d.ts.map +1 -0
- package/geometry/index.js +15 -0
- package/geometry/internal.d.ts +25 -0
- package/geometry/internal.d.ts.map +1 -0
- package/geometry/internal.js +40 -0
- package/geometry/ops.d.ts +71 -0
- package/geometry/ops.d.ts.map +1 -0
- package/geometry/ops.js +147 -0
- package/geometry/project.d.ts +40 -0
- package/geometry/project.d.ts.map +1 -0
- package/geometry/project.js +88 -0
- package/geometry/types.d.ts +36 -0
- package/geometry/types.d.ts.map +1 -0
- package/geometry/types.js +12 -0
- package/map/automatic-mount-integration.d.ts +134 -0
- package/map/automatic-mount-integration.d.ts.map +1 -0
- package/map/automatic-mount-integration.js +305 -0
- package/map/automatic-source-strategy.d.ts +125 -0
- package/map/automatic-source-strategy.d.ts.map +1 -0
- package/map/automatic-source-strategy.js +494 -0
- package/map/data-to-map-bridge.d.ts +293 -0
- package/map/data-to-map-bridge.d.ts.map +1 -0
- package/map/data-to-map-bridge.js +817 -0
- package/map/geojson-projection.d.ts +33 -0
- package/map/geojson-projection.d.ts.map +1 -0
- package/map/geojson-projection.js +118 -0
- package/map/index.d.ts +14 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +8 -0
- package/map/raster-source-spec.d.ts +30 -0
- package/map/raster-source-spec.d.ts.map +1 -0
- package/map/raster-source-spec.js +190 -0
- package/map/raster-source-strategy.d.ts +65 -0
- package/map/raster-source-strategy.d.ts.map +1 -0
- package/map/raster-source-strategy.js +232 -0
- package/map/source-to-maplibre.d.ts +91 -0
- package/map/source-to-maplibre.d.ts.map +1 -0
- package/map/source-to-maplibre.js +454 -0
- package/map/temporal-playback.d.ts +139 -0
- package/map/temporal-playback.d.ts.map +1 -0
- package/map/temporal-playback.js +247 -0
- package/package.json +33 -2
- package/query-planner/canonical.d.ts +7 -0
- package/query-planner/canonical.d.ts.map +1 -0
- package/query-planner/canonical.js +145 -0
- package/query-planner/cql2-json.d.ts +17 -0
- package/query-planner/cql2-json.d.ts.map +1 -0
- package/query-planner/cql2-json.js +646 -0
- package/query-planner/diagnostics.d.ts +35 -0
- package/query-planner/diagnostics.d.ts.map +1 -0
- package/query-planner/diagnostics.js +373 -0
- package/query-planner/duckdb.d.ts +79 -0
- package/query-planner/duckdb.d.ts.map +1 -0
- package/query-planner/duckdb.js +758 -0
- package/query-planner/execution-context.d.ts +5 -0
- package/query-planner/execution-context.d.ts.map +1 -0
- package/query-planner/execution-context.js +50 -0
- package/query-planner/executor.d.ts +5 -0
- package/query-planner/executor.d.ts.map +1 -0
- package/query-planner/executor.js +223 -0
- package/query-planner/geoservices.d.ts +91 -0
- package/query-planner/geoservices.d.ts.map +1 -0
- package/query-planner/geoservices.js +667 -0
- package/query-planner/grpc.d.ts +100 -0
- package/query-planner/grpc.d.ts.map +1 -0
- package/query-planner/grpc.js +736 -0
- package/query-planner/index.d.ts +44 -0
- package/query-planner/index.d.ts.map +1 -0
- package/query-planner/index.js +30 -0
- package/query-planner/ir.d.ts +20 -0
- package/query-planner/ir.d.ts.map +1 -0
- package/query-planner/ir.js +417 -0
- package/query-planner/local-aggregate.d.ts +5 -0
- package/query-planner/local-aggregate.d.ts.map +1 -0
- package/query-planner/local-aggregate.js +126 -0
- package/query-planner/odata.d.ts +48 -0
- package/query-planner/odata.d.ts.map +1 -0
- package/query-planner/odata.js +662 -0
- package/query-planner/ogc-compiler.d.ts +55 -0
- package/query-planner/ogc-compiler.d.ts.map +1 -0
- package/query-planner/ogc-compiler.js +480 -0
- package/query-planner/ogc-features.d.ts +53 -0
- package/query-planner/ogc-features.d.ts.map +1 -0
- package/query-planner/ogc-features.js +617 -0
- package/query-planner/planner.d.ts +17 -0
- package/query-planner/planner.d.ts.map +1 -0
- package/query-planner/planner.js +818 -0
- package/query-planner/resource.d.ts +94 -0
- package/query-planner/resource.d.ts.map +1 -0
- package/query-planner/resource.js +526 -0
- package/query-planner/semantic-canonical.d.ts +15 -0
- package/query-planner/semantic-canonical.d.ts.map +1 -0
- package/query-planner/semantic-canonical.js +62 -0
- package/query-planner/semantic-compat.d.ts +10 -0
- package/query-planner/semantic-compat.d.ts.map +1 -0
- package/query-planner/semantic-compat.js +35 -0
- package/query-planner/semantic-compiler.d.ts +64 -0
- package/query-planner/semantic-compiler.d.ts.map +1 -0
- package/query-planner/semantic-compiler.js +81 -0
- package/query-planner/semantic-literals.d.ts +30 -0
- package/query-planner/semantic-literals.d.ts.map +1 -0
- package/query-planner/semantic-literals.js +133 -0
- package/query-planner/semantic-types.d.ts +265 -0
- package/query-planner/semantic-types.d.ts.map +1 -0
- package/query-planner/semantic-types.js +1 -0
- package/query-planner/semantic.d.ts +54 -0
- package/query-planner/semantic.d.ts.map +1 -0
- package/query-planner/semantic.js +1421 -0
- package/query-planner/types.d.ts +560 -0
- package/query-planner/types.d.ts.map +1 -0
- package/query-planner/types.js +49 -0
- package/query-planner/wfs-semantic.d.ts +58 -0
- package/query-planner/wfs-semantic.d.ts.map +1 -0
- package/query-planner/wfs-semantic.js +547 -0
- package/query-planner/wfs.d.ts +6 -0
- package/query-planner/wfs.d.ts.map +1 -0
- package/query-planner/wfs.js +71 -0
- package/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/honua-server.d.ts.map +1 -1
- package/realtime/honua-server.js +13 -8
- package/realtime/index.d.ts +19 -4
- package/realtime/index.d.ts.map +1 -1
- package/realtime/index.js +14 -4
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts.map +1 -1
- package/realtime/sse.js +27 -23
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +27 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +57 -0
- package/replica-sync/errors.d.ts +2 -1
- package/replica-sync/errors.d.ts.map +1 -1
- package/replica-sync/errors.js +33 -2
- package/runtime/edit-snapping.d.ts +107 -0
- package/runtime/edit-snapping.d.ts.map +1 -0
- package/runtime/edit-snapping.js +190 -0
- package/runtime/errors.d.ts +3 -2
- package/runtime/errors.d.ts.map +1 -1
- package/runtime/errors.js +19 -2
- package/runtime/index.d.ts +6 -0
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -0
- package/runtime/maplibre-renderer.d.ts +41 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -0
- package/runtime/maplibre-renderer.js +322 -0
- package/runtime/query-tiles.d.ts +2 -1
- package/runtime/query-tiles.d.ts.map +1 -1
- package/runtime/query-tiles.js +6 -2
- package/runtime/source-bridge.d.ts +4 -4
- package/runtime/source-bridge.d.ts.map +1 -1
- package/runtime/source-bridge.js +9 -56
- package/runtime/style-interactions.d.ts +3 -2
- package/runtime/style-interactions.d.ts.map +1 -1
- package/runtime/style-interactions.js +11 -3
- package/runtime/terra-draw-sketch.d.ts +275 -0
- package/runtime/terra-draw-sketch.d.ts.map +1 -0
- package/runtime/terra-draw-sketch.js +404 -0
- package/scene-workspace/index.d.ts +4 -0
- package/scene-workspace/index.d.ts.map +1 -1
- package/scene-workspace/index.js +2 -0
- package/scene-workspace/source-to-cesium.d.ts +116 -0
- package/scene-workspace/source-to-cesium.d.ts.map +1 -0
- package/scene-workspace/source-to-cesium.js +791 -0
- package/scene-workspace/state-sync.d.ts +147 -0
- package/scene-workspace/state-sync.d.ts.map +1 -0
- package/scene-workspace/state-sync.js +1123 -0
- package/source-capability-registry.d.ts +1 -0
- package/source-capability-registry.js +1 -0
- package/source-capability-types.d.ts +186 -0
- package/source-capability-types.js +7 -0
- package/style/index.d.ts +2 -0
- package/style/index.d.ts.map +1 -1
- package/style/index.js +2 -0
- package/style/renderers.d.ts +326 -0
- package/style/renderers.d.ts.map +1 -0
- package/style/renderers.js +517 -0
- package/web-components/controller.d.ts +2 -0
- package/web-components/controller.d.ts.map +1 -1
- package/web-components/controller.js +49 -8
- package/web-components/elements.d.ts +95 -1
- package/web-components/elements.d.ts.map +1 -1
- package/web-components/elements.js +458 -19
- package/web-components/index.d.ts +8 -1
- package/web-components/index.d.ts.map +1 -1
- package/web-components/index.js +7 -0
- package/web-components/maplibre-renderer.d.ts.map +1 -1
- package/web-components/maplibre-renderer.js +87 -0
- package/web-components/measurement.d.ts +69 -0
- package/web-components/measurement.d.ts.map +1 -0
- package/web-components/measurement.js +523 -0
- package/web-components/types.d.ts +109 -0
- package/web-components/types.d.ts.map +1 -1
- package/webmap/convert-renderer.d.ts +24 -3
- package/webmap/convert-renderer.d.ts.map +1 -1
- package/webmap/convert-renderer.js +93 -112
- package/webmap/index.d.ts +1 -1
- package/webmap/index.d.ts.map +1 -1
- package/webmap/index.js +1 -1
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<honua-measurement>` — the survival-tier distance/area measurement widget
|
|
3
|
+
* (issue #493).
|
|
4
|
+
*
|
|
5
|
+
* Unlike `<honua-measure-control>` (which requires a host-supplied drawing
|
|
6
|
+
* provider), this element ships a complete, deliberately simple drawing
|
|
7
|
+
* interaction on top of the map's own pointer events:
|
|
8
|
+
*
|
|
9
|
+
* - **click** adds a vertex;
|
|
10
|
+
* - **double-click** finishes the sketch (the map's double-click zoom is
|
|
11
|
+
* suspended while a mode is active when the map exposes the handle);
|
|
12
|
+
* - **Escape** (or the Cancel button) discards the in-progress sketch.
|
|
13
|
+
*
|
|
14
|
+
* Distance and area are computed with the geodesic ops from the public
|
|
15
|
+
* `@honua/geometry` surface (`length` / `area`), so the widget's numbers match
|
|
16
|
+
* `geometryEngine` parity math everywhere else in the SDK.
|
|
17
|
+
*
|
|
18
|
+
* ## Wiring
|
|
19
|
+
* - `element.map = map` (any MapLibre `Map`; duck-typed via
|
|
20
|
+
* {@link HonuaMeasurementMap}), or
|
|
21
|
+
* - declaratively via `for="<honua-map id>"` — the element listens for the
|
|
22
|
+
* `honua-map-ready` event dispatched by `<honua-map>`.
|
|
23
|
+
*
|
|
24
|
+
* When the map exposes style mutation methods (`addSource` / `addLayer`), the
|
|
25
|
+
* in-progress sketch renders as a line + fill overlay in a
|
|
26
|
+
* `honua-measurement` GeoJSON source; on plain stubs the overlay is skipped.
|
|
27
|
+
*
|
|
28
|
+
* ## Accessibility
|
|
29
|
+
* Mode buttons are a `role="group"` of toggle buttons with `aria-pressed`;
|
|
30
|
+
* the live result is announced through a `role="status"` region; Finish /
|
|
31
|
+
* Cancel buttons provide keyboard-operable equivalents of double-click /
|
|
32
|
+
* Escape. Theme via the `--honua-ui-*` CSS custom properties.
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import { area as geodesicArea, length as geodesicLength } from "../geometry/index.js";
|
|
37
|
+
const globalDom = globalThis;
|
|
38
|
+
const HTMLElementBase = globalDom.HTMLElement ?? class {
|
|
39
|
+
};
|
|
40
|
+
const OVERLAY_SOURCE_ID = "honua-measurement";
|
|
41
|
+
const OVERLAY_LINE_LAYER_ID = "honua-measurement-line";
|
|
42
|
+
const OVERLAY_FILL_LAYER_ID = "honua-measurement-fill";
|
|
43
|
+
export class HonuaMeasurementElement extends HTMLElementBase {
|
|
44
|
+
static get observedAttributes() {
|
|
45
|
+
return ["for", "label"];
|
|
46
|
+
}
|
|
47
|
+
#map;
|
|
48
|
+
#mode = "off";
|
|
49
|
+
#vertices = [];
|
|
50
|
+
#finished = false;
|
|
51
|
+
#result;
|
|
52
|
+
#connected = false;
|
|
53
|
+
#disposeMapReadyListener;
|
|
54
|
+
#clickListener;
|
|
55
|
+
#dblclickListener;
|
|
56
|
+
#keydownListener;
|
|
57
|
+
/** The MapLibre map this widget draws on. */
|
|
58
|
+
get map() {
|
|
59
|
+
return this.#map;
|
|
60
|
+
}
|
|
61
|
+
set map(map) {
|
|
62
|
+
if (this.#map === map)
|
|
63
|
+
return;
|
|
64
|
+
this.#deactivateDrawing();
|
|
65
|
+
this.#map = map;
|
|
66
|
+
if (this.#mode !== "off")
|
|
67
|
+
this.#activateDrawing();
|
|
68
|
+
this.render();
|
|
69
|
+
}
|
|
70
|
+
/** Wires the widget to a MapLibre map. Equivalent to setting `.map`. */
|
|
71
|
+
connect(map) {
|
|
72
|
+
this.map = map;
|
|
73
|
+
}
|
|
74
|
+
/** The active measure mode. */
|
|
75
|
+
get mode() {
|
|
76
|
+
return this.#mode;
|
|
77
|
+
}
|
|
78
|
+
/** The current (or last finished) measurement result. */
|
|
79
|
+
get result() {
|
|
80
|
+
return this.#result;
|
|
81
|
+
}
|
|
82
|
+
/** The in-progress vertex list (`[lng, lat]` positions). */
|
|
83
|
+
get vertices() {
|
|
84
|
+
return this.#vertices;
|
|
85
|
+
}
|
|
86
|
+
/** Activates a measure mode; `"off"` cancels any in-progress sketch. */
|
|
87
|
+
setMode(mode) {
|
|
88
|
+
if (this.#mode === mode)
|
|
89
|
+
return;
|
|
90
|
+
this.#deactivateDrawing();
|
|
91
|
+
this.#mode = mode;
|
|
92
|
+
this.#vertices = [];
|
|
93
|
+
this.#finished = false;
|
|
94
|
+
this.#result = undefined;
|
|
95
|
+
if (mode !== "off")
|
|
96
|
+
this.#activateDrawing();
|
|
97
|
+
this.#dispatchChange();
|
|
98
|
+
this.render();
|
|
99
|
+
}
|
|
100
|
+
/** Adds a vertex programmatically (same as a map click). */
|
|
101
|
+
addVertex(lngLat) {
|
|
102
|
+
if (this.#mode === "off")
|
|
103
|
+
return;
|
|
104
|
+
if (this.#finished) {
|
|
105
|
+
this.#vertices = [];
|
|
106
|
+
this.#finished = false;
|
|
107
|
+
}
|
|
108
|
+
this.#vertices = [...this.#vertices, [lngLat[0], lngLat[1]]];
|
|
109
|
+
this.#recompute();
|
|
110
|
+
this.#syncOverlay();
|
|
111
|
+
this.#dispatchChange();
|
|
112
|
+
this.render();
|
|
113
|
+
}
|
|
114
|
+
/** Finishes the in-progress sketch (same as double-click). */
|
|
115
|
+
finish() {
|
|
116
|
+
if (this.#mode === "off" || this.#vertices.length === 0 || this.#finished)
|
|
117
|
+
return;
|
|
118
|
+
this.#finished = true;
|
|
119
|
+
this.#recompute();
|
|
120
|
+
this.#syncOverlay();
|
|
121
|
+
this.#dispatchChange();
|
|
122
|
+
this.render();
|
|
123
|
+
}
|
|
124
|
+
/** Cancels the in-progress sketch (same as Escape); the mode stays active. */
|
|
125
|
+
cancel() {
|
|
126
|
+
if (this.#vertices.length === 0 && !this.#finished)
|
|
127
|
+
return;
|
|
128
|
+
this.#vertices = [];
|
|
129
|
+
this.#finished = false;
|
|
130
|
+
this.#result = undefined;
|
|
131
|
+
this.#syncOverlay();
|
|
132
|
+
this.#dispatchChange();
|
|
133
|
+
this.render();
|
|
134
|
+
}
|
|
135
|
+
attributeChangedCallback(name) {
|
|
136
|
+
if (name === "for") {
|
|
137
|
+
this.#resolveMapFromContext();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.render();
|
|
141
|
+
}
|
|
142
|
+
connectedCallback() {
|
|
143
|
+
this.#connected = true;
|
|
144
|
+
this.#ensureShadowRoot();
|
|
145
|
+
this.#listenForMapReady();
|
|
146
|
+
this.#resolveMapFromContext();
|
|
147
|
+
this.render();
|
|
148
|
+
}
|
|
149
|
+
disconnectedCallback() {
|
|
150
|
+
this.#connected = false;
|
|
151
|
+
this.#disposeMapReadyListener?.();
|
|
152
|
+
this.#disposeMapReadyListener = undefined;
|
|
153
|
+
this.#deactivateDrawing();
|
|
154
|
+
}
|
|
155
|
+
// ── drawing interaction ─────────────────────────────────────
|
|
156
|
+
#activateDrawing() {
|
|
157
|
+
const map = this.#map;
|
|
158
|
+
if (!map || this.#clickListener)
|
|
159
|
+
return;
|
|
160
|
+
const onClick = (event) => {
|
|
161
|
+
const lngLat = eventLngLat(event);
|
|
162
|
+
if (lngLat)
|
|
163
|
+
this.addVertex(lngLat);
|
|
164
|
+
};
|
|
165
|
+
const onDblclick = (event) => {
|
|
166
|
+
preventMapDefault(event);
|
|
167
|
+
// The double-click's own click events already appended a duplicate
|
|
168
|
+
// trailing vertex; drop it before finishing.
|
|
169
|
+
if (!this.#finished && this.#vertices.length >= 2) {
|
|
170
|
+
const last = this.#vertices[this.#vertices.length - 1];
|
|
171
|
+
const previous = this.#vertices[this.#vertices.length - 2];
|
|
172
|
+
if (last && previous && last[0] === previous[0] && last[1] === previous[1]) {
|
|
173
|
+
this.#vertices = this.#vertices.slice(0, -1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.finish();
|
|
177
|
+
};
|
|
178
|
+
map.on("click", onClick);
|
|
179
|
+
map.on("dblclick", onDblclick);
|
|
180
|
+
map.doubleClickZoom?.disable?.();
|
|
181
|
+
this.#clickListener = onClick;
|
|
182
|
+
this.#dblclickListener = onDblclick;
|
|
183
|
+
if (typeof window !== "undefined" && !this.#keydownListener) {
|
|
184
|
+
const onKeydown = (event) => {
|
|
185
|
+
if (event.key === "Escape")
|
|
186
|
+
this.cancel();
|
|
187
|
+
};
|
|
188
|
+
window.addEventListener("keydown", onKeydown);
|
|
189
|
+
this.#keydownListener = onKeydown;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
#deactivateDrawing() {
|
|
193
|
+
const map = this.#map;
|
|
194
|
+
if (map) {
|
|
195
|
+
if (this.#clickListener)
|
|
196
|
+
map.off?.("click", this.#clickListener);
|
|
197
|
+
if (this.#dblclickListener)
|
|
198
|
+
map.off?.("dblclick", this.#dblclickListener);
|
|
199
|
+
map.doubleClickZoom?.enable?.();
|
|
200
|
+
this.#removeOverlay(map);
|
|
201
|
+
}
|
|
202
|
+
this.#clickListener = undefined;
|
|
203
|
+
this.#dblclickListener = undefined;
|
|
204
|
+
if (this.#keydownListener && typeof window !== "undefined") {
|
|
205
|
+
window.removeEventListener("keydown", this.#keydownListener);
|
|
206
|
+
this.#keydownListener = undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
#recompute() {
|
|
210
|
+
const mode = this.#mode;
|
|
211
|
+
const coordinates = this.#vertices;
|
|
212
|
+
if (mode === "off" || coordinates.length === 0) {
|
|
213
|
+
this.#result = undefined;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const result = { mode, coordinates };
|
|
217
|
+
try {
|
|
218
|
+
if (mode === "distance" && coordinates.length >= 2) {
|
|
219
|
+
this.#result = {
|
|
220
|
+
...result,
|
|
221
|
+
distance: geodesicLength({ type: "LineString", coordinates: coordinates.map(toPosition) }, "meters"),
|
|
222
|
+
};
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (mode === "area" && coordinates.length >= 3) {
|
|
226
|
+
const ring = [...coordinates.map(toPosition), toPosition(coordinates[0])];
|
|
227
|
+
this.#result = {
|
|
228
|
+
...result,
|
|
229
|
+
area: geodesicArea({ type: "Polygon", coordinates: [ring] }),
|
|
230
|
+
};
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Degenerate geometry (repeated points, collinear ring): report the
|
|
236
|
+
// vertices without a computed value rather than throwing from an event.
|
|
237
|
+
}
|
|
238
|
+
this.#result = result;
|
|
239
|
+
}
|
|
240
|
+
// ── map overlay (best-effort; skipped on stubs) ─────────────
|
|
241
|
+
#syncOverlay() {
|
|
242
|
+
const map = this.#map;
|
|
243
|
+
if (!map?.addSource || !map.getSource)
|
|
244
|
+
return;
|
|
245
|
+
const data = this.#overlayGeoJson();
|
|
246
|
+
try {
|
|
247
|
+
const existing = map.getSource(OVERLAY_SOURCE_ID);
|
|
248
|
+
if (existing?.setData) {
|
|
249
|
+
existing.setData(data);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
map.addSource(OVERLAY_SOURCE_ID, { type: "geojson", data });
|
|
253
|
+
map.addLayer?.({
|
|
254
|
+
id: OVERLAY_FILL_LAYER_ID,
|
|
255
|
+
type: "fill",
|
|
256
|
+
source: OVERLAY_SOURCE_ID,
|
|
257
|
+
filter: ["==", ["geometry-type"], "Polygon"],
|
|
258
|
+
paint: { "fill-color": "#2563eb", "fill-opacity": 0.16 },
|
|
259
|
+
});
|
|
260
|
+
map.addLayer?.({
|
|
261
|
+
id: OVERLAY_LINE_LAYER_ID,
|
|
262
|
+
type: "line",
|
|
263
|
+
source: OVERLAY_SOURCE_ID,
|
|
264
|
+
paint: { "line-color": "#2563eb", "line-width": 2, "line-dasharray": [2, 1] },
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// Overlay rendering is cosmetic; measurement math never depends on it.
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
#removeOverlay(map) {
|
|
272
|
+
try {
|
|
273
|
+
if (map.getLayer?.(OVERLAY_LINE_LAYER_ID))
|
|
274
|
+
map.removeLayer?.(OVERLAY_LINE_LAYER_ID);
|
|
275
|
+
if (map.getLayer?.(OVERLAY_FILL_LAYER_ID))
|
|
276
|
+
map.removeLayer?.(OVERLAY_FILL_LAYER_ID);
|
|
277
|
+
if (map.getSource?.(OVERLAY_SOURCE_ID))
|
|
278
|
+
map.removeSource?.(OVERLAY_SOURCE_ID);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Best-effort cleanup.
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
#overlayGeoJson() {
|
|
285
|
+
const coordinates = this.#vertices.map(toPosition);
|
|
286
|
+
const features = [];
|
|
287
|
+
if (this.#mode === "area" && coordinates.length >= 3) {
|
|
288
|
+
features.push({
|
|
289
|
+
type: "Feature",
|
|
290
|
+
properties: {},
|
|
291
|
+
geometry: { type: "Polygon", coordinates: [[...coordinates, coordinates[0]]] },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (coordinates.length >= 2) {
|
|
295
|
+
features.push({
|
|
296
|
+
type: "Feature",
|
|
297
|
+
properties: {},
|
|
298
|
+
geometry: { type: "LineString", coordinates },
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return { type: "FeatureCollection", features };
|
|
302
|
+
}
|
|
303
|
+
// ── map wiring (matches the `for` + honua-map-ready idiom) ──
|
|
304
|
+
#listenForMapReady() {
|
|
305
|
+
if (this.#disposeMapReadyListener)
|
|
306
|
+
return;
|
|
307
|
+
const target = this.#rootEventTarget();
|
|
308
|
+
if (!target)
|
|
309
|
+
return;
|
|
310
|
+
const listener = (event) => {
|
|
311
|
+
const detail = event.detail;
|
|
312
|
+
if (!detail?.map)
|
|
313
|
+
return;
|
|
314
|
+
const forId = this.#attr("for");
|
|
315
|
+
if (forId && event.target?.id !== forId)
|
|
316
|
+
return;
|
|
317
|
+
if (!forId && this.#map)
|
|
318
|
+
return;
|
|
319
|
+
this.map = detail.map;
|
|
320
|
+
};
|
|
321
|
+
target.addEventListener("honua-map-ready", listener);
|
|
322
|
+
this.#disposeMapReadyListener = () => target.removeEventListener("honua-map-ready", listener);
|
|
323
|
+
}
|
|
324
|
+
#resolveMapFromContext() {
|
|
325
|
+
if (this.#map)
|
|
326
|
+
return;
|
|
327
|
+
const forId = this.#attr("for");
|
|
328
|
+
if (!forId)
|
|
329
|
+
return;
|
|
330
|
+
const root = this.getRootNode?.();
|
|
331
|
+
const host = root && "getElementById" in root
|
|
332
|
+
? root.getElementById(forId)
|
|
333
|
+
: typeof document !== "undefined"
|
|
334
|
+
? document.getElementById(forId)
|
|
335
|
+
: null;
|
|
336
|
+
const map = host?.map;
|
|
337
|
+
if (map)
|
|
338
|
+
this.map = map;
|
|
339
|
+
}
|
|
340
|
+
#rootEventTarget() {
|
|
341
|
+
const root = this.getRootNode?.();
|
|
342
|
+
if (root && "addEventListener" in root)
|
|
343
|
+
return root;
|
|
344
|
+
return typeof document !== "undefined" ? document : undefined;
|
|
345
|
+
}
|
|
346
|
+
// ── rendering ──────────────────────────────────────────────
|
|
347
|
+
#ensureShadowRoot() {
|
|
348
|
+
if (!this.shadowRoot && typeof this.attachShadow === "function") {
|
|
349
|
+
this.attachShadow({ mode: "open" });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
render() {
|
|
353
|
+
const root = this.shadowRoot;
|
|
354
|
+
if (!root || !this.#connected)
|
|
355
|
+
return;
|
|
356
|
+
const label = this.#attr("label") ?? "Measure";
|
|
357
|
+
const ready = this.#map !== undefined;
|
|
358
|
+
const sketchActive = this.#mode !== "off" && this.#vertices.length > 0 && !this.#finished;
|
|
359
|
+
root.innerHTML = `
|
|
360
|
+
<style>${structuralStyles()}</style>
|
|
361
|
+
<section class="panel" part="panel" aria-label="${escapeAttribute(label)}">
|
|
362
|
+
<div class="bar">
|
|
363
|
+
<h2>${escapeHtml(label)}</h2>
|
|
364
|
+
<span class="state">${escapeHtml(ready ? "ready" : "waiting for map")}</span>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="segmented" role="group" aria-label="${escapeAttribute(`${label} mode`)}">
|
|
367
|
+
${["off", "distance", "area"]
|
|
368
|
+
.map((mode) => `
|
|
369
|
+
<button type="button" part="mode" data-measure-mode="${mode}" aria-pressed="${String(this.#mode === mode)}"${ready || mode === "off" ? "" : " disabled"}>${escapeHtml(modeLabel(mode))}</button>
|
|
370
|
+
`)
|
|
371
|
+
.join("")}
|
|
372
|
+
</div>
|
|
373
|
+
<p class="value" part="value" role="status" aria-live="polite">${escapeHtml(this.#resultText())}</p>
|
|
374
|
+
<div class="actions">
|
|
375
|
+
<button type="button" part="finish" data-measure-finish${sketchActive ? "" : " disabled"}>Finish</button>
|
|
376
|
+
<button type="button" part="cancel" data-measure-cancel${this.#vertices.length > 0 ? "" : " disabled"}>Cancel</button>
|
|
377
|
+
</div>
|
|
378
|
+
<p class="hint">${escapeHtml(this.#mode === "off"
|
|
379
|
+
? "Pick a mode, then click the map to add vertices."
|
|
380
|
+
: "Click adds a vertex; double-click or Finish completes; Escape or Cancel discards.")}</p>
|
|
381
|
+
</section>
|
|
382
|
+
`;
|
|
383
|
+
root.querySelectorAll("button[data-measure-mode]").forEach((button) => {
|
|
384
|
+
button.addEventListener("click", () => {
|
|
385
|
+
this.setMode((button.dataset.measureMode ?? "off"));
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
root.querySelector("button[data-measure-finish]")?.addEventListener("click", () => {
|
|
389
|
+
this.finish();
|
|
390
|
+
});
|
|
391
|
+
root.querySelector("button[data-measure-cancel]")?.addEventListener("click", () => {
|
|
392
|
+
this.cancel();
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
#resultText() {
|
|
396
|
+
const result = this.#result;
|
|
397
|
+
if (this.#mode === "off")
|
|
398
|
+
return "Measurement off.";
|
|
399
|
+
if (!result || this.#vertices.length === 0) {
|
|
400
|
+
return this.#mode === "distance" ? "Click the map to start a line." : "Click the map to outline an area.";
|
|
401
|
+
}
|
|
402
|
+
if (this.#mode === "distance") {
|
|
403
|
+
if (result.distance === undefined)
|
|
404
|
+
return `${this.#vertices.length} vertex — add another to measure.`;
|
|
405
|
+
return `Distance: ${formatDistance(result.distance)}${this.#finished ? " (finished)" : ""}`;
|
|
406
|
+
}
|
|
407
|
+
if (result.area === undefined) {
|
|
408
|
+
return `${this.#vertices.length} of 3 vertices needed for an area.`;
|
|
409
|
+
}
|
|
410
|
+
return `Area: ${formatArea(result.area)}${this.#finished ? " (finished)" : ""}`;
|
|
411
|
+
}
|
|
412
|
+
#dispatchChange() {
|
|
413
|
+
if (!globalDom.CustomEvent || typeof this.dispatchEvent !== "function")
|
|
414
|
+
return;
|
|
415
|
+
const detail = {
|
|
416
|
+
mode: this.#mode,
|
|
417
|
+
status: this.#map ? "ready" : "idle",
|
|
418
|
+
...(this.#result ? { result: this.#result } : {}),
|
|
419
|
+
};
|
|
420
|
+
this.dispatchEvent(new globalDom.CustomEvent("honua-measure-change", { bubbles: true, composed: true, detail }));
|
|
421
|
+
}
|
|
422
|
+
#attr(name) {
|
|
423
|
+
return typeof this.getAttribute === "function" ? this.getAttribute(name) : null;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Registers `<honua-measurement>`; skipped when the tag is already defined. */
|
|
427
|
+
export function defineHonuaMeasurement(registry = globalDom.customElements) {
|
|
428
|
+
if (!registry)
|
|
429
|
+
return;
|
|
430
|
+
if (!registry.get("honua-measurement")) {
|
|
431
|
+
registry.define("honua-measurement", HonuaMeasurementElement);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function toPosition(lngLat) {
|
|
435
|
+
return [lngLat[0], lngLat[1]];
|
|
436
|
+
}
|
|
437
|
+
function eventLngLat(event) {
|
|
438
|
+
if (typeof event !== "object" || event === null)
|
|
439
|
+
return undefined;
|
|
440
|
+
const lngLat = event.lngLat;
|
|
441
|
+
if (typeof lngLat?.lng === "number" && typeof lngLat.lat === "number") {
|
|
442
|
+
return [lngLat.lng, lngLat.lat];
|
|
443
|
+
}
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
function preventMapDefault(event) {
|
|
447
|
+
if (typeof event !== "object" || event === null)
|
|
448
|
+
return;
|
|
449
|
+
const preventDefault = event.preventDefault;
|
|
450
|
+
if (typeof preventDefault === "function")
|
|
451
|
+
preventDefault.call(event);
|
|
452
|
+
}
|
|
453
|
+
function formatDistance(meters) {
|
|
454
|
+
if (meters >= 1000)
|
|
455
|
+
return `${(meters / 1000).toFixed(2)} km`;
|
|
456
|
+
return `${meters.toFixed(1)} m`;
|
|
457
|
+
}
|
|
458
|
+
function formatArea(squareMeters) {
|
|
459
|
+
if (squareMeters >= 1_000_000)
|
|
460
|
+
return `${(squareMeters / 1_000_000).toFixed(2)} km²`;
|
|
461
|
+
if (squareMeters >= 10_000)
|
|
462
|
+
return `${(squareMeters / 10_000).toFixed(2)} ha`;
|
|
463
|
+
return `${squareMeters.toFixed(1)} m²`;
|
|
464
|
+
}
|
|
465
|
+
function modeLabel(mode) {
|
|
466
|
+
return mode.charAt(0).toUpperCase() + mode.slice(1);
|
|
467
|
+
}
|
|
468
|
+
/** Structural styles themable via the shared `--honua-ui-*` custom properties. */
|
|
469
|
+
function structuralStyles() {
|
|
470
|
+
return `
|
|
471
|
+
:host {
|
|
472
|
+
--honua-ui-bg: #ffffff;
|
|
473
|
+
--honua-ui-fg: #172033;
|
|
474
|
+
--honua-ui-muted: #667085;
|
|
475
|
+
--honua-ui-border: #d0d5dd;
|
|
476
|
+
--honua-ui-accent: #1d4ed8;
|
|
477
|
+
--honua-ui-accent-fg: #ffffff;
|
|
478
|
+
box-sizing: border-box;
|
|
479
|
+
color: var(--honua-ui-fg);
|
|
480
|
+
display: block;
|
|
481
|
+
font: 14px/1.4 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
482
|
+
}
|
|
483
|
+
*, *::before, *::after { box-sizing: inherit; }
|
|
484
|
+
button {
|
|
485
|
+
border: 1px solid var(--honua-ui-border);
|
|
486
|
+
background: var(--honua-ui-bg);
|
|
487
|
+
border-radius: 6px;
|
|
488
|
+
color: inherit;
|
|
489
|
+
cursor: pointer;
|
|
490
|
+
font: inherit;
|
|
491
|
+
min-height: 32px;
|
|
492
|
+
padding: 0 10px;
|
|
493
|
+
}
|
|
494
|
+
button:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
495
|
+
button[aria-pressed="true"] {
|
|
496
|
+
background: var(--honua-ui-accent);
|
|
497
|
+
border-color: var(--honua-ui-accent);
|
|
498
|
+
color: var(--honua-ui-accent-fg);
|
|
499
|
+
}
|
|
500
|
+
h2 { font-size: 14px; margin: 0; }
|
|
501
|
+
.panel {
|
|
502
|
+
background: var(--honua-ui-bg);
|
|
503
|
+
border: 1px solid var(--honua-ui-border);
|
|
504
|
+
border-radius: 8px;
|
|
505
|
+
display: grid;
|
|
506
|
+
gap: 10px;
|
|
507
|
+
min-width: 180px;
|
|
508
|
+
padding: 10px;
|
|
509
|
+
}
|
|
510
|
+
.bar { align-items: center; display: flex; gap: 8px; justify-content: space-between; }
|
|
511
|
+
.state { color: var(--honua-ui-muted); font-size: 12px; }
|
|
512
|
+
.segmented { display: grid; gap: 6px; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr)); }
|
|
513
|
+
.actions { display: flex; gap: 6px; }
|
|
514
|
+
.value { margin: 0; }
|
|
515
|
+
.hint { color: var(--honua-ui-muted); font-size: 12px; margin: 0; }
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
function escapeHtml(value) {
|
|
519
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
520
|
+
}
|
|
521
|
+
function escapeAttribute(value) {
|
|
522
|
+
return escapeHtml(value).replace(/'/g, "'");
|
|
523
|
+
}
|
|
@@ -10,6 +10,11 @@ export interface HonuaLayerModel {
|
|
|
10
10
|
sourceId?: string;
|
|
11
11
|
type?: string;
|
|
12
12
|
visible: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Layer paint opacity in the `[0, 1]` range. `undefined` means the style's
|
|
15
|
+
* own opacity is untouched (treated as fully opaque by UI controls).
|
|
16
|
+
*/
|
|
17
|
+
opacity?: number;
|
|
13
18
|
metadata?: Record<string, unknown>;
|
|
14
19
|
}
|
|
15
20
|
export interface HonuaLegendItem {
|
|
@@ -149,6 +154,20 @@ export interface HonuaWebComponentController<T = Record<string, unknown>> {
|
|
|
149
154
|
getState(): HonuaWebComponentState<T>;
|
|
150
155
|
subscribe(listener: HonuaControllerStateListener<T>): HonuaControllerSubscription;
|
|
151
156
|
setLayerVisibility(layerId: string, visible: boolean): void;
|
|
157
|
+
/**
|
|
158
|
+
* Sets a layer's paint opacity (clamped to `[0, 1]`). Optional so that
|
|
159
|
+
* pre-existing controller implementations keep compiling; `<honua-layer-list>`
|
|
160
|
+
* only renders opacity sliders when the controller implements it.
|
|
161
|
+
*/
|
|
162
|
+
setLayerOpacity?(layerId: string, opacity: number): void;
|
|
163
|
+
/**
|
|
164
|
+
* Reorders a layer: inserts `layerId` before `beforeId` in the layer stack,
|
|
165
|
+
* or moves it to the end (rendered on top) when `beforeId` is omitted.
|
|
166
|
+
* Optional so that pre-existing controller implementations keep compiling;
|
|
167
|
+
* `<honua-layer-list>` only renders reorder affordances when the controller
|
|
168
|
+
* implements it.
|
|
169
|
+
*/
|
|
170
|
+
moveLayer?(layerId: string, beforeId?: string): void;
|
|
152
171
|
setViewport(viewport: HonuaViewportState): void;
|
|
153
172
|
setFilter(filter: HonuaFilterState): void;
|
|
154
173
|
selectFeature(selection: HonuaSelectionState<T>): void;
|
|
@@ -206,6 +225,10 @@ export interface HonuaWebComponentRuntimeLike<T = Record<string, unknown>> {
|
|
|
206
225
|
};
|
|
207
226
|
getLegend(): readonly HonuaMapPackageLegendEntry[];
|
|
208
227
|
setLayerVisibility(layerId: string, visible: boolean): void;
|
|
228
|
+
/** Optional runtime support for writing layer paint opacity. */
|
|
229
|
+
setLayerOpacity?(layerId: string, opacity: number): void;
|
|
230
|
+
/** Optional runtime support for reordering layers (MapLibre `beforeId` semantics). */
|
|
231
|
+
moveLayer?(layerId: string, beforeId?: string): void;
|
|
209
232
|
setViewState(viewport: HonuaViewportState): void;
|
|
210
233
|
readonly dataset?: {
|
|
211
234
|
source(id: string): {
|
|
@@ -231,6 +254,21 @@ export interface HonuaLayerVisibilityChangeDetail {
|
|
|
231
254
|
layerId: string;
|
|
232
255
|
visible: boolean;
|
|
233
256
|
}
|
|
257
|
+
/** Detail of the `honua-layer-opacity-change` event dispatched by `<honua-layer-list>`. */
|
|
258
|
+
export interface HonuaLayerOpacityChangeDetail {
|
|
259
|
+
layerId: string;
|
|
260
|
+
/** New opacity in `[0, 1]`. */
|
|
261
|
+
opacity: number;
|
|
262
|
+
}
|
|
263
|
+
/** Detail of the `honua-layer-order-change` event dispatched by `<honua-layer-list>`. */
|
|
264
|
+
export interface HonuaLayerOrderChangeDetail {
|
|
265
|
+
/** The layer that was moved. */
|
|
266
|
+
layerId: string;
|
|
267
|
+
/** Layer the moved layer was inserted before; absent when moved to the end (top). */
|
|
268
|
+
beforeId?: string;
|
|
269
|
+
/** Resulting layer id order (style order: last id renders on top). */
|
|
270
|
+
order: readonly string[];
|
|
271
|
+
}
|
|
234
272
|
export interface HonuaViewportChangeDetail extends HonuaViewportState {
|
|
235
273
|
}
|
|
236
274
|
export interface HonuaFilterChangeDetail extends HonuaFilterState {
|
|
@@ -270,6 +308,53 @@ export interface HonuaSearchDetail<T = Record<string, unknown>> {
|
|
|
270
308
|
query: string;
|
|
271
309
|
results: readonly HonuaSearchResult<T>[];
|
|
272
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* One typeahead suggestion rendered by `<honua-search>` when a geocoding
|
|
313
|
+
* provider is configured. Structurally compatible with the
|
|
314
|
+
* `GeocodeSuggestion` shape returned by `HonuaGeocodingClient.suggest`
|
|
315
|
+
* (`@honua/sdk-js/geocoding`).
|
|
316
|
+
*/
|
|
317
|
+
export interface HonuaSearchGeocodeSuggestion {
|
|
318
|
+
text: string;
|
|
319
|
+
magicKey?: string;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* One forward-geocode candidate consumed by `<honua-search>`. Structurally
|
|
323
|
+
* compatible with the `GeocodeResult` shape returned by
|
|
324
|
+
* `HonuaGeocodingClient.forwardGeocode` (`@honua/sdk-js/geocoding`).
|
|
325
|
+
*/
|
|
326
|
+
export interface HonuaSearchGeocodeCandidate {
|
|
327
|
+
address: string;
|
|
328
|
+
latitude: number;
|
|
329
|
+
longitude: number;
|
|
330
|
+
score?: number;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Duck-typed geocoding provider accepted by the `<honua-search>` `geocoder`
|
|
334
|
+
* property. A `HonuaGeocodingClient` instance from the stable
|
|
335
|
+
* `@honua/sdk-js/geocoding` entrypoint satisfies this interface structurally
|
|
336
|
+
* — the web-component kit never imports the geocoding client itself, proving
|
|
337
|
+
* the provider seam. Custom providers (other geocoders, offline gazetteers)
|
|
338
|
+
* only need `forwardGeocode`; `suggest` powers debounced typeahead when
|
|
339
|
+
* available.
|
|
340
|
+
*/
|
|
341
|
+
export interface HonuaSearchGeocoderLike {
|
|
342
|
+
suggest?(text: string, options?: {
|
|
343
|
+
maxSuggestions?: number;
|
|
344
|
+
}): Promise<readonly HonuaSearchGeocodeSuggestion[]>;
|
|
345
|
+
forwardGeocode(address: string, options?: {
|
|
346
|
+
maxResults?: number;
|
|
347
|
+
}): Promise<readonly HonuaSearchGeocodeCandidate[]>;
|
|
348
|
+
}
|
|
349
|
+
/** Detail of the `honua-geocode-select` event dispatched by `<honua-search>`. */
|
|
350
|
+
export interface HonuaGeocodeSelectDetail {
|
|
351
|
+
/** The query text that produced the candidate. */
|
|
352
|
+
query: string;
|
|
353
|
+
/** The chosen forward-geocode candidate. */
|
|
354
|
+
candidate: HonuaSearchGeocodeCandidate;
|
|
355
|
+
/** The viewport applied to the shared controller (center + zoom). */
|
|
356
|
+
viewport: HonuaViewportState;
|
|
357
|
+
}
|
|
273
358
|
export interface HonuaEditChangeDetail<T = Record<string, unknown>> {
|
|
274
359
|
status: HonuaComponentStatus;
|
|
275
360
|
request?: HonuaEditRequest<T>;
|
|
@@ -298,6 +383,30 @@ export interface HonuaLocateChangeDetail {
|
|
|
298
383
|
message?: string;
|
|
299
384
|
}
|
|
300
385
|
export type HonuaMeasureMode = "off" | "distance" | "area";
|
|
386
|
+
/**
|
|
387
|
+
* Minimal duck-typed subset of `maplibre-gl.Map` required by
|
|
388
|
+
* `<honua-measurement>`. Any MapLibre-GL `Map` instance satisfies this
|
|
389
|
+
* interface, and tests can pass a plain stub (matching the duck-typing
|
|
390
|
+
* posture of the `@honua/sdk-js/controls` kit and `src/runtime/runtime.ts`).
|
|
391
|
+
*/
|
|
392
|
+
export interface HonuaMeasurementMap {
|
|
393
|
+
/** Subscribes to map events (`click`, `dblclick`). */
|
|
394
|
+
on(type: string, listener: (event?: unknown) => void): unknown;
|
|
395
|
+
/** Unsubscribes a listener registered with `on`. */
|
|
396
|
+
off?(type: string, listener: (event?: unknown) => void): unknown;
|
|
397
|
+
/** Optional style access used to render the in-progress sketch overlay. */
|
|
398
|
+
addSource?(id: string, source: unknown): void;
|
|
399
|
+
getSource?(id: string): unknown;
|
|
400
|
+
addLayer?(layer: unknown, beforeId?: string): void;
|
|
401
|
+
removeLayer?(id: string): void;
|
|
402
|
+
getLayer?(id: string): unknown;
|
|
403
|
+
removeSource?(id: string): void;
|
|
404
|
+
/** Optional double-click-zoom handle disabled while a drawing mode is active. */
|
|
405
|
+
doubleClickZoom?: {
|
|
406
|
+
disable?(): void;
|
|
407
|
+
enable?(): void;
|
|
408
|
+
};
|
|
409
|
+
}
|
|
301
410
|
/**
|
|
302
411
|
* A drawn measurement geometry plus its computed result.
|
|
303
412
|
*
|