@maptiler/geocoding-control 0.0.31

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.
@@ -0,0 +1,123 @@
1
+ import * as L from "leaflet";
2
+ import GeocodingControlComponent from "./GeocodingControl.svelte";
3
+ import { createLeafletMapController } from "./leafletMapController";
4
+ import type { ControlOptions, Feature } from "./types";
5
+
6
+ export type { Feature } from "./types";
7
+
8
+ type LeafletControlOptions = ControlOptions &
9
+ L.ControlOptions & {
10
+ /**
11
+ * If `true`, a [Marker](https://leafletjs.com/reference.html#marker) will be added to the map at the location of the user-selected result using a default set of Marker options.
12
+ * If the value is an object, the marker will be constructed using these options.
13
+ * If `false`, no marker will be added to the map.
14
+ *
15
+ * @default true
16
+ */
17
+ marker?: boolean | L.MarkerOptions;
18
+
19
+ /**
20
+ * If `true`, [Markers](https://leafletjs.com/reference.html#marker) will be added to the map at the location the top results for the query.
21
+ * If the value is an object, the marker will be constructed using these options.
22
+ * If `false`, no marker will be added to the map.
23
+ *
24
+ * @default true
25
+ */
26
+ showResultMarkers?: boolean | L.MarkerOptions;
27
+
28
+ /**
29
+ * If `false`, animating the map to a selected result is disabled.
30
+ * If `true`, animating the map will use the default animation parameters.
31
+ * If an object, it will be passed as options to the map `flyTo` or `fitBounds` method providing control over the animation of the transition.
32
+ *
33
+ * @default true
34
+ */
35
+ flyTo?: boolean | (L.ZoomPanOptions & L.FitBoundsOptions);
36
+ };
37
+
38
+ export class GeocodingControl extends L.Control {
39
+ #gc?: GeocodingControlComponent;
40
+
41
+ #options: LeafletControlOptions;
42
+
43
+ constructor(options: LeafletControlOptions) {
44
+ super();
45
+
46
+ this.#options = options;
47
+ }
48
+
49
+ onAdd(map: L.Map) {
50
+ const div = document.createElement("div");
51
+
52
+ div.className = "leaflet-ctrl-geocoder";
53
+
54
+ L.DomEvent.disableClickPropagation(div);
55
+ L.DomEvent.disableScrollPropagation(div);
56
+
57
+ const { marker, showResultMarkers, flyTo, ...restOptions } = this.#options;
58
+
59
+ const flyToOptions = typeof flyTo === "boolean" ? {} : flyTo;
60
+
61
+ const mapController = createLeafletMapController(
62
+ map,
63
+ marker,
64
+ showResultMarkers,
65
+ flyToOptions,
66
+ flyToOptions
67
+ );
68
+
69
+ this.#gc = new GeocodingControlComponent({
70
+ target: div,
71
+ props: {
72
+ mapController,
73
+ flyTo: flyTo === undefined ? true : !!flyTo,
74
+ ...restOptions,
75
+ },
76
+ });
77
+
78
+ for (const eventName of [
79
+ "select",
80
+ "pick",
81
+ "featuresListed",
82
+ "featuresMarked",
83
+ "response",
84
+ "optionsVisibilityChange",
85
+ "reverseToggle",
86
+ "queryChange",
87
+ ]) {
88
+ this.#gc.$on(eventName, (event) =>
89
+ map.fire(eventName.toLowerCase(), event.detail)
90
+ );
91
+ }
92
+
93
+ return div;
94
+ }
95
+
96
+ setOptions(options: LeafletControlOptions) {
97
+ this.#options = options;
98
+
99
+ const { marker, showResultMarkers, flyTo, ...restOptions } = this.#options;
100
+
101
+ this.#gc?.$set(restOptions);
102
+ }
103
+
104
+ setQuery(value: string, submit = true) {
105
+ (this.#gc as any)?.setQuery(value, submit);
106
+ }
107
+
108
+ setReverseMode(value: boolean) {
109
+ this.#gc?.$set({ reverseActive: value });
110
+ }
111
+
112
+ focus() {
113
+ (this.#gc as any)?.focus();
114
+ }
115
+
116
+ blur() {
117
+ (this.#gc as any)?.blur();
118
+ }
119
+
120
+ onRemove() {
121
+ (this.#gc as any)?.$destroy();
122
+ }
123
+ }
@@ -0,0 +1,40 @@
1
+ <div>
2
+ <svg viewBox="0 0 18 18" width="24" height="24">
3
+ <path
4
+ fill="#333"
5
+ d="M4.4 4.4l.8.8c2.1-2.1 5.5-2.1 7.6 0l.8-.8c-2.5-2.5-6.7-2.5-9.2 0z"
6
+ />
7
+ <path
8
+ opacity=".1"
9
+ d="M12.8 12.9c-2.1 2.1-5.5 2.1-7.6 0-2.1-2.1-2.1-5.5 0-7.7l-.8-.8c-2.5 2.5-2.5 6.7 0 9.2s6.6 2.5 9.2 0 2.5-6.6 0-9.2l-.8.8c2.2 2.1 2.2 5.6 0 7.7z"
10
+ />
11
+ </svg>
12
+ </div>
13
+
14
+ <style>
15
+ div {
16
+ position: absolute;
17
+ left: 50%;
18
+ top: 50%;
19
+ transform: translate(-50%, -50%);
20
+ pointer-events: none;
21
+
22
+ display: flex;
23
+ align-items: center;
24
+ }
25
+
26
+ svg {
27
+ animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95);
28
+ }
29
+
30
+ @keyframes rotate {
31
+ from {
32
+ -webkit-transform: rotate(0);
33
+ transform: rotate(0);
34
+ }
35
+ to {
36
+ -webkit-transform: rotate(360deg);
37
+ transform: rotate(360deg);
38
+ }
39
+ }
40
+ </style>
@@ -0,0 +1,141 @@
1
+ import {
2
+ type Map,
3
+ type IControl,
4
+ type MarkerOptions,
5
+ type FlyToOptions,
6
+ type FitBoundsOptions,
7
+ Evented,
8
+ } from "maplibre-gl";
9
+ import type maplibregl from "maplibre-gl";
10
+ import GeocodingControlComponent from "./GeocodingControl.svelte";
11
+ import type { ControlOptions, Feature } from "./types";
12
+ import { createMaplibreMapController } from "./maplibreMapController";
13
+
14
+ export type { Feature } from "./types";
15
+
16
+ type MapLibreGL = typeof maplibregl;
17
+
18
+ type MapLibreControlOptions = ControlOptions & {
19
+ /**
20
+ * A Maplibre GL instance to use when creating [Markers](https://maplibre.org/maplibre-gl-js-docs/api/markers/#marker).
21
+ * Required if `options.marker` is `true`.
22
+ */
23
+ maplibregl?: MapLibreGL;
24
+
25
+ /**
26
+ * If `true`, a [Marker](https://maplibre.org/maplibre-gl-js-docs/api/markers/#marker) will be added to the map at the location of the user-selected result using a default set of Marker options.
27
+ * If the value is an object, the marker will be constructed using these options.
28
+ * If `false`, no marker will be added to the map.
29
+ * Requires that `options.maplibregl` also be set.
30
+ *
31
+ * @default true
32
+ */
33
+ marker?: boolean | MarkerOptions;
34
+
35
+ /**
36
+ * If `true`, [Markers](https://maplibre.org/maplibre-gl-js-docs/api/markers/#marker) will be added to the map at the location the top results for the query.
37
+ * If the value is an object, the marker will be constructed using these options.
38
+ * If `false`, no marker will be added to the map.
39
+ * Requires that `options.maplibregl` also be set.
40
+ *
41
+ * @default true
42
+ */
43
+ showResultMarkers?: boolean | MarkerOptions;
44
+
45
+ /**
46
+ * If `false`, animating the map to a selected result is disabled.
47
+ * If `true`, animating the map will use the default animation parameters.
48
+ * If an object, it will be passed as options to the map `flyTo` or `fitBounds` method providing control over the animation of the transition.
49
+ *
50
+ * @default true
51
+ */
52
+ flyTo?: boolean | (FlyToOptions & FitBoundsOptions);
53
+ };
54
+
55
+ export class GeocodingControl extends Evented implements IControl {
56
+ #gc?: GeocodingControlComponent;
57
+
58
+ #options: MapLibreControlOptions;
59
+
60
+ constructor(options: MapLibreControlOptions) {
61
+ super();
62
+
63
+ this.#options = options;
64
+ }
65
+
66
+ onAdd(map: Map) {
67
+ const div = document.createElement("div");
68
+
69
+ div.className =
70
+ "mapboxgl-ctrl-geocoder mapboxgl-ctrl maplibregl-ctrl-geocoder maplibregl-ctrl";
71
+
72
+ const { maplibregl, marker, showResultMarkers, flyTo, ...restOptions } =
73
+ this.#options;
74
+
75
+ const flyToOptions = typeof flyTo === "boolean" ? {} : flyTo;
76
+
77
+ const mapController = createMaplibreMapController(
78
+ map,
79
+ maplibregl,
80
+ marker,
81
+ showResultMarkers,
82
+ flyToOptions,
83
+ flyToOptions
84
+ );
85
+
86
+ this.#gc = new GeocodingControlComponent({
87
+ target: div,
88
+ props: {
89
+ mapController,
90
+ flyTo: flyTo === undefined ? true : !!flyTo,
91
+ ...restOptions,
92
+ },
93
+ });
94
+
95
+ for (const eventName of [
96
+ "select",
97
+ "pick",
98
+ "featuresListed",
99
+ "featuresMarked",
100
+ "response",
101
+ "optionsVisibilityChange",
102
+ "reverseToggle",
103
+ "queryChange",
104
+ ]) {
105
+ this.#gc.$on(eventName, (event) =>
106
+ this.fire(eventName.toLowerCase(), event.detail)
107
+ );
108
+ }
109
+
110
+ return div;
111
+ }
112
+
113
+ setOptions(options: MapLibreControlOptions) {
114
+ this.#options = options;
115
+
116
+ const { maplibregl, marker, showResultMarkers, flyTo, ...restOptions } =
117
+ this.#options;
118
+
119
+ this.#gc?.$set(restOptions);
120
+ }
121
+
122
+ setQuery(value: string, submit = true) {
123
+ (this.#gc as any)?.setQuery(value, submit);
124
+ }
125
+
126
+ setReverseMode(value: boolean) {
127
+ this.#gc?.$set({ reverseActive: value });
128
+ }
129
+
130
+ focus() {
131
+ (this.#gc as any)?.focus();
132
+ }
133
+
134
+ blur() {
135
+ (this.#gc as any)?.blur();
136
+ }
137
+
138
+ onRemove() {
139
+ (this.#gc as any)?.$destroy();
140
+ }
141
+ }
@@ -0,0 +1,63 @@
1
+ <script type="ts">
2
+ export let displayIn: "list" | "leaflet" | "maplibre";
3
+ </script>
4
+
5
+ <svg
6
+ width={displayIn !== "list" ? undefined : "20"}
7
+ viewBox="0 0 70 85"
8
+ fill="none"
9
+ class:in-map={displayIn !== "list"}
10
+ class:for-maplibre={displayIn === "maplibre"}
11
+ class:for-leaflet={displayIn === "leaflet"}
12
+ class:list-icon={displayIn === "list"}
13
+ >
14
+ <path
15
+ stroke-width="4"
16
+ fill-rule="evenodd"
17
+ clip-rule="evenodd"
18
+ d="M 5,33.103579 C 5,17.607779 18.457,5 35,5 C 51.543,5 65,17.607779 65,33.103579 C 65,56.388679 40.4668,76.048179 36.6112,79.137779 C 36.3714,79.329879 36.2116,79.457979 36.1427,79.518879 C 35.8203,79.800879 35.4102,79.942779 35,79.942779 C 34.5899,79.942779 34.1797,79.800879 33.8575,79.518879 C 33.7886,79.457979 33.6289,79.330079 33.3893,79.138079 C 29.5346,76.049279 5,56.389379 5,33.103579 Z M 35.0001,49.386379 C 43.1917,49.386379 49.8323,42.646079 49.8323,34.331379 C 49.8323,26.016779 43.1917,19.276479 35.0001,19.276479 C 26.8085,19.276479 20.1679,26.016779 20.1679,34.331379 C 20.1679,42.646079 26.8085,49.386379 35.0001,49.386379 Z"
19
+ />
20
+ </svg>
21
+
22
+ <style>
23
+ svg {
24
+ fill: #6b7c93;
25
+ stroke: #6b7c93;
26
+ }
27
+
28
+ .list-icon {
29
+ grid-row: 1/3;
30
+ align-self: center;
31
+ margin: 8px;
32
+ }
33
+
34
+ .in-map {
35
+ height: 30px;
36
+ }
37
+
38
+ .for-maplibre {
39
+ position: relative;
40
+ top: -10px;
41
+ }
42
+
43
+ .for-leaflet {
44
+ position: relative;
45
+ top: -23px;
46
+ left: -50%;
47
+ }
48
+
49
+ :global(.maplibregl-canvas-container .marker-selected) {
50
+ z-index: 1;
51
+ }
52
+
53
+ :global(.maplibregl-canvas-container) svg path,
54
+ :global(.leaflet-map-pane) svg path {
55
+ fill: #3170fe;
56
+ stroke: #3170fe;
57
+ }
58
+
59
+ :global(.marker-selected) svg path {
60
+ fill: #98b7ff;
61
+ stroke: #3170fe;
62
+ }
63
+ </style>
@@ -0,0 +1,12 @@
1
+ <svg viewBox="0 0 18 18" xml:space="preserve" width="20">
2
+ <path
3
+ d="M7.4 2.5c-2.7 0-4.9 2.2-4.9 4.9s2.2 4.9 4.9 4.9c1 0 1.8-.2 2.5-.8l3.7 3.7c.2.2.4.3.8.3.7 0 1.1-.4 1.1-1.1 0-.3-.1-.5-.3-.8L11.4 10c.4-.8.8-1.6.8-2.5.1-2.8-2.1-5-4.8-5zm0 1.6c1.8 0 3.2 1.4 3.2 3.2s-1.4 3.2-3.2 3.2-3.3-1.3-3.3-3.1 1.4-3.3 3.3-3.3z"
4
+ />
5
+ </svg>
6
+
7
+ <style>
8
+ svg {
9
+ display: block;
10
+ fill: var(--color-icon-button);
11
+ }
12
+ </style>
@@ -0,0 +1,146 @@
1
+ import * as L from "leaflet";
2
+ import MarkerIcon from "./MarkerIcon.svelte";
3
+ import type { Feature, MapController, Proximity } from "./types";
4
+
5
+ export function createLeafletMapController(
6
+ map: L.Map,
7
+ marker: boolean | L.MarkerOptions = true,
8
+ showResultMarkers: boolean | L.MarkerOptions = true,
9
+ flyToOptions: L.ZoomPanOptions = {},
10
+ flyToBounds: L.FitBoundsOptions = {}
11
+ ) {
12
+ let proximityChangeHandler: ((proximity: Proximity) => void) | undefined;
13
+
14
+ let mapClickHandler: ((coordinates: [number, number]) => void) | undefined;
15
+
16
+ let prevProximity: Proximity = undefined;
17
+
18
+ let markers: L.Marker[] = [];
19
+
20
+ let selectedMarker: L.Marker | undefined;
21
+
22
+ const handleMoveEnd = () => {
23
+ if (!proximityChangeHandler) {
24
+ prevProximity = undefined;
25
+
26
+ return;
27
+ }
28
+
29
+ let c: L.LatLng;
30
+
31
+ const proximity =
32
+ map.getZoom() > 10
33
+ ? ([(c = map.getCenter().wrap()).lng, c.lat] as [number, number])
34
+ : undefined;
35
+
36
+ if (prevProximity !== proximity) {
37
+ prevProximity = proximity;
38
+
39
+ proximityChangeHandler(proximity);
40
+ }
41
+ };
42
+
43
+ const handleMapClick = (e: L.LeafletMouseEvent) => {
44
+ mapClickHandler?.([e.latlng.lng, e.latlng.lat]);
45
+ };
46
+
47
+ const ctrl: MapController = {
48
+ setProximityChangeHandler(
49
+ _proximityChangeHandler: ((proximity: Proximity) => void) | undefined
50
+ ): void {
51
+ if (_proximityChangeHandler) {
52
+ proximityChangeHandler = _proximityChangeHandler;
53
+
54
+ map.on("moveend", handleMoveEnd);
55
+
56
+ handleMoveEnd();
57
+ } else {
58
+ map.off("moveend", handleMoveEnd);
59
+
60
+ proximityChangeHandler?.(undefined);
61
+
62
+ proximityChangeHandler = undefined;
63
+ }
64
+ },
65
+
66
+ setMapClickHandler(
67
+ _mapClickHandler: ((coordinates: [number, number]) => void) | undefined
68
+ ): void {
69
+ mapClickHandler = _mapClickHandler;
70
+
71
+ if (mapClickHandler) {
72
+ map.on("click", handleMapClick);
73
+ } else {
74
+ map.off("click", handleMapClick);
75
+ }
76
+ },
77
+
78
+ flyTo(center: [number, number], zoom: number) {
79
+ map.flyTo(center, zoom, flyToOptions);
80
+ },
81
+
82
+ fitBounds(bbox: [number, number, number, number], padding: number): void {
83
+ map.flyToBounds(
84
+ [
85
+ [bbox[1], bbox[0]],
86
+ [bbox[3], bbox[2]],
87
+ ],
88
+ { ...flyToBounds, padding: [padding, padding] }
89
+ );
90
+ },
91
+
92
+ indicateReverse(reverse: boolean): void {
93
+ map.getContainer().style.cursor = reverse ? "crosshair" : "";
94
+ },
95
+
96
+ setMarkers(
97
+ markedFeatures: Feature[] | undefined,
98
+ picked: Feature | undefined
99
+ ): void {
100
+ for (const marker of markers) {
101
+ marker.remove();
102
+ }
103
+
104
+ markers.length = 0;
105
+
106
+ for (const feature of picked
107
+ ? [...(markedFeatures ?? []), picked]
108
+ : markedFeatures ?? []) {
109
+ let m: L.Marker;
110
+
111
+ const pos: L.LatLngExpression = [feature.center[1], feature.center[0]];
112
+
113
+ if (feature === picked && typeof marker === "object") {
114
+ m = new L.Marker(pos, marker);
115
+ } else if (
116
+ feature !== picked &&
117
+ typeof showResultMarkers === "object"
118
+ ) {
119
+ m = new L.Marker(pos, showResultMarkers);
120
+ } else {
121
+ const element = document.createElement("div");
122
+
123
+ new MarkerIcon({ props: { displayIn: "leaflet" }, target: element });
124
+
125
+ m = new L.Marker(pos, {
126
+ icon: new L.DivIcon({ html: element, className: "" }),
127
+ });
128
+ }
129
+
130
+ markers.push(m.addTo(map));
131
+ }
132
+ },
133
+
134
+ setSelectedMarker(index: number): void {
135
+ if (selectedMarker) {
136
+ selectedMarker.getElement()?.classList.toggle("marker-selected", false);
137
+ }
138
+
139
+ selectedMarker = index > -1 ? markers[index] : undefined;
140
+
141
+ selectedMarker?.getElement()?.classList.toggle("marker-selected", true);
142
+ },
143
+ };
144
+
145
+ return ctrl;
146
+ }
@@ -0,0 +1,149 @@
1
+ import type MapLibreGL from "maplibre-gl";
2
+ import type {
3
+ FitBoundsOptions,
4
+ MapMouseEvent,
5
+ LngLat,
6
+ Map,
7
+ Marker,
8
+ FlyToOptions,
9
+ } from "maplibre-gl";
10
+ import MarkerIcon from "./MarkerIcon.svelte";
11
+ import type { Feature, MapController, Proximity } from "./types";
12
+
13
+ export function createMaplibreMapController(
14
+ map: Map,
15
+ maplibregl?: typeof MapLibreGL | undefined,
16
+ marker: boolean | maplibregl.MarkerOptions = true,
17
+ showResultMarkers: boolean | maplibregl.MarkerOptions = true,
18
+ flyToOptions: FlyToOptions = {},
19
+ fitBoundsOptions: FitBoundsOptions = {}
20
+ ) {
21
+ let proximityChangeHandler: ((proximity: Proximity) => void) | undefined;
22
+
23
+ let mapClickHandler: ((coordinates: [number, number]) => void) | undefined;
24
+
25
+ let prevProximity: Proximity = undefined;
26
+
27
+ let markers: Marker[] = [];
28
+
29
+ let selectedMarker: maplibregl.Marker | undefined;
30
+
31
+ const handleMapClick = (e: MapMouseEvent) => {
32
+ mapClickHandler?.([e.lngLat.lng, e.lngLat.lat]);
33
+ };
34
+
35
+ const handleMoveEnd = () => {
36
+ let c: LngLat;
37
+
38
+ const proximity =
39
+ map.getZoom() > 9
40
+ ? ([(c = map.getCenter().wrap()).lng, c.lat] as [number, number])
41
+ : undefined;
42
+
43
+ if (prevProximity !== proximity) {
44
+ prevProximity = proximity;
45
+
46
+ proximityChangeHandler?.(proximity);
47
+ }
48
+ };
49
+
50
+ const ctrl: MapController = {
51
+ setProximityChangeHandler(
52
+ _proximityChangeHandler: ((proximity: Proximity) => void) | undefined
53
+ ): void {
54
+ if (_proximityChangeHandler) {
55
+ proximityChangeHandler = _proximityChangeHandler;
56
+
57
+ map.on("moveend", handleMoveEnd);
58
+
59
+ handleMoveEnd();
60
+ } else {
61
+ map.off("moveend", handleMoveEnd);
62
+
63
+ proximityChangeHandler?.(undefined);
64
+
65
+ proximityChangeHandler = undefined;
66
+ }
67
+ },
68
+
69
+ setMapClickHandler(
70
+ _mapClickHandler: ((coordinates: [number, number]) => void) | undefined
71
+ ): void {
72
+ mapClickHandler = _mapClickHandler;
73
+
74
+ if (mapClickHandler) {
75
+ map.on("click", handleMapClick);
76
+ } else {
77
+ map.off("click", handleMapClick);
78
+ }
79
+ },
80
+
81
+ flyTo(center: [number, number], zoom: number): void {
82
+ map.flyTo({ center, zoom, ...flyToOptions });
83
+ },
84
+
85
+ fitBounds(bbox: [number, number, number, number], padding: number): void {
86
+ map.fitBounds(
87
+ [
88
+ [bbox[0], bbox[1]],
89
+ [bbox[2], bbox[3]],
90
+ ],
91
+ { ...fitBoundsOptions, padding }
92
+ );
93
+ },
94
+
95
+ indicateReverse(reverse: boolean): void {
96
+ map.getCanvas().style.cursor = reverse ? "crosshair" : "";
97
+ },
98
+
99
+ setMarkers(
100
+ markedFeatures: Feature[] | undefined,
101
+ picked: Feature | undefined
102
+ ): void {
103
+ for (const marker of markers) {
104
+ marker.remove();
105
+ }
106
+
107
+ markers.length = 0;
108
+
109
+ if (!maplibregl) {
110
+ return;
111
+ }
112
+
113
+ for (const feature of picked
114
+ ? [...(markedFeatures ?? []), picked]
115
+ : markedFeatures ?? []) {
116
+ let m: Marker;
117
+
118
+ if (feature === picked && typeof marker === "object") {
119
+ m = new maplibregl.Marker(marker);
120
+ } else if (
121
+ feature !== picked &&
122
+ typeof showResultMarkers === "object"
123
+ ) {
124
+ m = new maplibregl.Marker(showResultMarkers);
125
+ } else {
126
+ const element = document.createElement("div");
127
+
128
+ new MarkerIcon({ props: { displayIn: "maplibre" }, target: element });
129
+
130
+ m = new maplibregl.Marker({ element });
131
+ }
132
+
133
+ markers.push(m.setLngLat(feature.center).addTo(map));
134
+ }
135
+ },
136
+
137
+ setSelectedMarker(index: number): void {
138
+ if (selectedMarker) {
139
+ selectedMarker.getElement().classList.toggle("marker-selected", false);
140
+ }
141
+
142
+ selectedMarker = index > -1 ? markers[index] : undefined;
143
+
144
+ selectedMarker?.getElement().classList.toggle("marker-selected", true);
145
+ },
146
+ };
147
+
148
+ return ctrl;
149
+ }