@excom/mapbox-view 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/chunked-rush-logs/mapbox-view.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/mapbox-view.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/mapbox-view.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +35 -0
- package/config/rig.json +5 -0
- package/index.css +5 -0
- package/index.ts +7 -0
- package/mapbox-view-marker.ts +279 -0
- package/mapbox-view.ts +582 -0
- package/package.json +46 -0
- package/rush-logs/mapbox-view.apply-exports.cache.log +1 -0
- package/rush-logs/mapbox-view.apply-exports.log +1 -0
- package/rush-logs/mapbox-view.build_docs.cache.log +1 -0
- package/rush-logs/mapbox-view.build_docs.log +1 -0
- package/rush-logs/mapbox-view.build_package-metas.cache.log +1 -0
- package/rush-logs/mapbox-view.build_package-metas.log +1 -0
- package/src/mapbox-view.css +10 -0
- package/src/types.ts +32 -0
- package/src/utils.ts +78 -0
- package/support/custom-elements.json +77 -0
- package/support/dist-docs/mapbox-view-marker.md +3 -0
- package/support/dist-docs/mapbox-view.md +33 -0
- package/support/tests/mapbox-view.test.ts +41 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"../../packages/mapbox-view": "../../packages/mapbox-view:waN6IYkT0kWJoHv+Il2v7PKH1oqmE6rt1niWmjXrbj4=:",
|
|
3
|
+
"@mapbox/jsonlint-lines-primitives@2.0.2": "@mapbox/jsonlint-lines-primitives@2.0.2:pTMNfjSWqxSbYnhqr8pm9NAFrBp+ZeCfZLGTVw0e0pM=:",
|
|
4
|
+
"@mapbox/mapbox-gl-supported@3.0.0": "@mapbox/mapbox-gl-supported@3.0.0:IhPd0a0B6DvTT119ufIe8zot6xYG5lAIRFAdFb6JeUA=:",
|
|
5
|
+
"@mapbox/point-geometry@1.1.0": "@mapbox/point-geometry@1.1.0:Bm0cSoJTXQgIUMfVOHRP/37hAUqNcit6uJkjCmqmFOc=:",
|
|
6
|
+
"@mapbox/tiny-sdf@2.0.7": "@mapbox/tiny-sdf@2.0.7:AXIff3JeANbTLZP6eJCp/Mk2QZexnR9MnxCMjX9sBTU=:",
|
|
7
|
+
"@mapbox/unitbezier@0.0.1": "@mapbox/unitbezier@0.0.1:sGC7+jQh2i9whO8vuKDKffOHvdDMTmePjF2ooKlFlio=:",
|
|
8
|
+
"@mapbox/vector-tile@2.0.4": "@mapbox/vector-tile@2.0.4:hh5E9i+Eclgnk5M529e59ytFHhJ6dZmUwq88DHmwBVM=:",
|
|
9
|
+
"@mapbox/whoots-js@3.1.0": "@mapbox/whoots-js@3.1.0:Vk9XwLe0n7cWbOaabSlAesXX0UDIgp+mJGtLX0GHIqQ=:",
|
|
10
|
+
"@types/geojson-vt@3.2.5": "@types/geojson-vt@3.2.5:UvTNEt3a+ghKLyVCTroNhY23UAAeXdabZUMrEvPXWB8=:",
|
|
11
|
+
"@types/geojson@7946.0.16": "@types/geojson@7946.0.16:gcMlSqEHJU4IX6+c7qnAWFvEefycVi66cMxuADjE2B0=:",
|
|
12
|
+
"@types/mapbox__point-geometry@0.1.4": "@types/mapbox__point-geometry@0.1.4:GygVrmKyj0wDWwdQcNQ5I0saPTBG2bzUI4Nrdm2Z0ZU=:",
|
|
13
|
+
"@types/pbf@3.0.5": "@types/pbf@3.0.5:0S+vW4kiwqZjQXiuTNXGnKze6PvnUmgQ6rtCtMncWtg=:",
|
|
14
|
+
"@types/supercluster@7.1.3": "@types/supercluster@7.1.3:JJw17OdaacIMfw3OSnYTBqj5hvfH8lzR+udEUSxNWWI=:",
|
|
15
|
+
"cheap-ruler@4.0.0": "cheap-ruler@4.0.0:yiDcBxPrql3UwD/Uox6B4cGVBbkPoRGkLGyQ1wi0LzI=:",
|
|
16
|
+
"csscolorparser@1.0.3": "csscolorparser@1.0.3:S3na1TrpSMzZ48LFKxCgcGaL/GayChj6fZtGMOYQYOc=:",
|
|
17
|
+
"earcut@3.0.2": "earcut@3.0.2:h0+7LalGP3yo5ywfF/AwqcULA73JcRn8Q4bD4g0Sjhw=:",
|
|
18
|
+
"geojson-vt@4.0.2": "geojson-vt@4.0.2:0jHmKg55n/7M6aFWRHOB0rFJ7xm4zO5UteGqnXsTKas=:",
|
|
19
|
+
"gl-matrix@3.4.4": "gl-matrix@3.4.4:ZP629NFaBeO6xsS7/LcCCBKRCikT7jTFadMFSsQpX6M=:",
|
|
20
|
+
"grid-index@1.1.0": "grid-index@1.1.0:qzgVCoYo9QfK5abOV1Q3VnyhPqa2mRYJS5fLX6EWmTY=:",
|
|
21
|
+
"kdbush@4.0.2": "kdbush@4.0.2:+tgNxS9hvxLrm+MYkGLLPUmiBDxnM9zTJUMxzogXQiQ=:",
|
|
22
|
+
"mapbox-gl@3.17.0": "mapbox-gl@3.17.0:N95/XxVSFICm4Ki8C926ltIrBw1PFDSXlitMPtJ6ias=:",
|
|
23
|
+
"martinez-polygon-clipping@0.7.4": "martinez-polygon-clipping@0.7.4:z26W/5T0GI+vPPsdA2VZNWwQRByfLefZU9i1aHjncLA=:",
|
|
24
|
+
"murmurhash-js@1.0.0": "murmurhash-js@1.0.0:4fRjg8epvWyUGtJngQ0YSpU5ay8QkF4cWsUUybHnDIA=:",
|
|
25
|
+
"pbf@4.0.1": "pbf@4.0.1:62nhmgjuKKsgpt20FlfO4XgIh1v2U8ppQsVz4tDU+g8=:",
|
|
26
|
+
"potpack@2.1.0": "potpack@2.1.0:4AusZbGjTmOT1kl7bYViTmsF5+Jm/P20F86FTRJAC9Y=:",
|
|
27
|
+
"protocol-buffers-schema@3.6.0": "protocol-buffers-schema@3.6.0:Y5nXV/1DfdBj9epKmmnoDYDnxbbhQWBdE9OM3eWs6y4=:",
|
|
28
|
+
"quickselect@3.0.0": "quickselect@3.0.0:3hNUJ5B8vj6I5Dzp6CvCvxglDbhHnQS6NImDgEV4V8A=:",
|
|
29
|
+
"resolve-protobuf-schema@2.1.0": "resolve-protobuf-schema@2.1.0:uOoqxXADHusstNUJDzpYjj+Mckn0qAlKYaSzULxUwQk=:",
|
|
30
|
+
"robust-predicates@2.0.4": "robust-predicates@2.0.4:IFDQpMXlUKSPbQFa277oYEL5MbvjlW7HjIQRkhac0/U=:",
|
|
31
|
+
"splaytree@0.1.4": "splaytree@0.1.4:hxchdprRjVa5N/GohBO7ymNF2j+Py1vqN/+yBAJo9JY=:",
|
|
32
|
+
"supercluster@8.0.1": "supercluster@8.0.1:9QZIbpvOBX1e4YwV2UFvF35hAK2QDzEpi0yoFCdP51I=:",
|
|
33
|
+
"tinyqueue@1.2.3": "tinyqueue@1.2.3:TFqtbhraVvlBBp22m8Yjhqu+iBsracFKYBqVOPrzo2g=:",
|
|
34
|
+
"tinyqueue@3.0.0": "tinyqueue@3.0.0:WYmogjNW144eAmI23uxK91JRv9Q0SXiCck2JAbE8hGY=:"
|
|
35
|
+
}
|
package/config/rig.json
ADDED
package/index.css
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import type { MapboxView as TMapboxView } from "./mapbox-view";
|
|
2
|
+
import type {
|
|
3
|
+
MapInstance,
|
|
4
|
+
TMapboxgl,
|
|
5
|
+
TMapboxglMarkerOptions,
|
|
6
|
+
} from "./src/types";
|
|
7
|
+
import {
|
|
8
|
+
parseCoordsFromTokens,
|
|
9
|
+
parseNumsFromTokens,
|
|
10
|
+
queueTask,
|
|
11
|
+
} from "./src/utils";
|
|
12
|
+
import { deleteUndefined, selectOne } from "@excom/kit-utils";
|
|
13
|
+
import { ConstructorType, Neutron, TokenList } from "@excom/neutron";
|
|
14
|
+
|
|
15
|
+
// Workaround until partial type inference lands: https://github.com/Microsoft/TypeScript/pull/26349
|
|
16
|
+
|
|
17
|
+
type T_HTMLMapboxViewMarkerElement = typeof TMapboxView.CustomElement;
|
|
18
|
+
|
|
19
|
+
export const MapboxViewMarker = Neutron({
|
|
20
|
+
tag: "mapbox-view-marker",
|
|
21
|
+
props: {
|
|
22
|
+
mapRef: String,
|
|
23
|
+
mapElement:
|
|
24
|
+
HTMLElement as unknown as ConstructorType<T_HTMLMapboxViewMarkerElement>,
|
|
25
|
+
mapInstance: Object as unknown as ConstructorType<MapInstance>,
|
|
26
|
+
markerInstance: Object as unknown as TMapboxgl["Marker"],
|
|
27
|
+
markerElement: HTMLElement,
|
|
28
|
+
pinTag: String,
|
|
29
|
+
pinDataText: String,
|
|
30
|
+
pinDataPopoverText: String,
|
|
31
|
+
pinClass: String,
|
|
32
|
+
pinCoords: TokenList,
|
|
33
|
+
pinOffset: TokenList,
|
|
34
|
+
pinAnchor: String,
|
|
35
|
+
pinColor: String,
|
|
36
|
+
pinScale: Number,
|
|
37
|
+
pinDraggable: Boolean,
|
|
38
|
+
pinClickTolerance: Number,
|
|
39
|
+
pinRotation: Number,
|
|
40
|
+
pinRotationAlignment: String,
|
|
41
|
+
pinPitchAlignment: String,
|
|
42
|
+
pinOccludedOpacity: Number,
|
|
43
|
+
pinStyle: String,
|
|
44
|
+
focusEvents: TokenList,
|
|
45
|
+
panBoundaries: TokenList,
|
|
46
|
+
blurOnMapClick: Boolean,
|
|
47
|
+
singletonName: String,
|
|
48
|
+
},
|
|
49
|
+
}).defineMethods({
|
|
50
|
+
panToMarker: ({ panBoundaries, markerElement, mapElement, pinCoords }) => {
|
|
51
|
+
const markerRect = (markerElement as Element).getBoundingClientRect?.();
|
|
52
|
+
const mapRect = (mapElement as Element).getBoundingClientRect?.();
|
|
53
|
+
const _panBoundaries = parseNumsFromTokens(panBoundaries) || [
|
|
54
|
+
15, 15, 15, 15,
|
|
55
|
+
];
|
|
56
|
+
const isTop = markerRect.top < mapRect.height * (_panBoundaries[0] / 100);
|
|
57
|
+
const isRight =
|
|
58
|
+
markerRect.right > mapRect.width * (1 - _panBoundaries[1] / 100);
|
|
59
|
+
const isBottom =
|
|
60
|
+
markerRect.bottom > mapRect.height * (1 - _panBoundaries[2] / 100);
|
|
61
|
+
const isLeft = markerRect.left < mapRect.width * (_panBoundaries[3] / 100);
|
|
62
|
+
return {
|
|
63
|
+
...((isTop || isRight || isBottom || isLeft) && {
|
|
64
|
+
mapElement: {
|
|
65
|
+
activeCoords: pinCoords || [],
|
|
66
|
+
emit: ["mapbox-view-reactive"],
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
emit: ["mapbox-view-marker-focus"],
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
handleMarkerClick: ({ blurOnMapClick }, e: Event) => {
|
|
73
|
+
if (blurOnMapClick) {
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
}
|
|
76
|
+
return { panToMarker: [], emit: ["mapbox-view-marker-interacted"] };
|
|
77
|
+
},
|
|
78
|
+
setMapElement: (element) => {
|
|
79
|
+
const existingMapElement = element.mapElement;
|
|
80
|
+
const mapElement = selectOne(element.mapRef || "mapbox-view", {
|
|
81
|
+
scope: element,
|
|
82
|
+
}) as T_HTMLMapboxViewMarkerElement;
|
|
83
|
+
if (mapElement && mapElement !== existingMapElement) {
|
|
84
|
+
return {
|
|
85
|
+
mapElement,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
setMapAndMarker: (element, mapInstance: MapInstance, mb: TMapboxgl) => ({
|
|
90
|
+
mapInstance,
|
|
91
|
+
markerInstance: new mb.Marker(markerOpts(element)),
|
|
92
|
+
}),
|
|
93
|
+
blurCallback: () => ({
|
|
94
|
+
emit: ["mapbox-view-marker-blur"],
|
|
95
|
+
}),
|
|
96
|
+
singletonCallback: (element, { detail }: CustomEvent<typeof element>) => {
|
|
97
|
+
if (
|
|
98
|
+
element !== detail &&
|
|
99
|
+
element.singletonName &&
|
|
100
|
+
detail?.singletonName &&
|
|
101
|
+
element.singletonName === detail?.singletonName
|
|
102
|
+
) {
|
|
103
|
+
element.markerElement?.classList.remove("is-active");
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
type PartialProps = Partial<(typeof MapboxViewMarker)["Props"]>;
|
|
109
|
+
|
|
110
|
+
const markerOpts = (element: PartialProps): TMapboxglMarkerOptions => {
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
return deleteUndefined({
|
|
113
|
+
element: element.pinTag
|
|
114
|
+
? document.createElement(element.pinTag)
|
|
115
|
+
: undefined,
|
|
116
|
+
offset: parseCoordsFromTokens(element.pinOffset),
|
|
117
|
+
color: element.pinColor,
|
|
118
|
+
scale: element.pinScale,
|
|
119
|
+
anchor: (element.pinAnchor ||
|
|
120
|
+
undefined) as TMapboxglMarkerOptions["anchor"],
|
|
121
|
+
draggable: element.pinDraggable,
|
|
122
|
+
rotation: element.pinRotation,
|
|
123
|
+
rotationAlignment: element.pinRotationAlignment,
|
|
124
|
+
pitchAlignment: element.pinPitchAlignment,
|
|
125
|
+
occludedOpacity: element.pinOccludedOpacity,
|
|
126
|
+
className: element.pinClass,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
MapboxViewMarker.onConnected(({ isMoving, mapRef, setMapElement }) => {
|
|
131
|
+
// setTimeout to ensure map element is instantiated; the timer id must not
|
|
132
|
+
// be returned
|
|
133
|
+
if (!isMoving && !mapRef) setTimeout(setMapElement, 0);
|
|
134
|
+
})
|
|
135
|
+
.onPropSet("mapRef", ({ setMapElement }) => {
|
|
136
|
+
// Defer so the map element is instantiated
|
|
137
|
+
setTimeout(setMapElement, 0);
|
|
138
|
+
})
|
|
139
|
+
.onPropSet("mapElement", ({ mapElement, setMapAndMarker }) => {
|
|
140
|
+
if (mapElement.mapInstance) {
|
|
141
|
+
queueTask(mapElement.mapInstance, setMapAndMarker);
|
|
142
|
+
} else {
|
|
143
|
+
// Map not ready yet (first load / Mapbox GL still loading).
|
|
144
|
+
// Wait for load, then init the marker.
|
|
145
|
+
mapElement.addEventListener(
|
|
146
|
+
"mapbox-view-loaded",
|
|
147
|
+
() => queueTask(mapElement.mapInstance, setMapAndMarker),
|
|
148
|
+
{ once: true }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
.onDisconnected(({ markerInstance, isMoving }) => {
|
|
153
|
+
if (!isMoving) {
|
|
154
|
+
if (markerInstance) {
|
|
155
|
+
markerInstance.remove();
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
mapElement: null,
|
|
159
|
+
mapInstance: null,
|
|
160
|
+
markerInstance: null,
|
|
161
|
+
markerElement: null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
.onPropChanged(
|
|
166
|
+
["pinCoords", "markerInstance"],
|
|
167
|
+
({ pinCoords, markerInstance, mapInstance, markerElement }) => {
|
|
168
|
+
const coords = parseCoordsFromTokens(pinCoords);
|
|
169
|
+
if (markerInstance && coords && mapInstance) {
|
|
170
|
+
markerInstance.setLngLat(coords);
|
|
171
|
+
markerInstance.addTo(mapInstance);
|
|
172
|
+
return (
|
|
173
|
+
!markerElement && {
|
|
174
|
+
markerElement: markerInstance.getElement(),
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
.onPropChanged(
|
|
181
|
+
["markerInstance", "pinClass"],
|
|
182
|
+
({ markerInstance, pinClass }, previous) => {
|
|
183
|
+
if (markerInstance) {
|
|
184
|
+
if (previous?.pinClass) {
|
|
185
|
+
previous.pinClass
|
|
186
|
+
.split(" ")
|
|
187
|
+
.forEach((c) => markerInstance.removeClassName(c));
|
|
188
|
+
}
|
|
189
|
+
pinClass?.split(" ")?.forEach((c) => markerInstance.addClassName(c));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
.onPropChanged(
|
|
194
|
+
["markerElement", "pinDataText"],
|
|
195
|
+
({ markerElement, pinDataText }) => {
|
|
196
|
+
if (markerElement) {
|
|
197
|
+
return {
|
|
198
|
+
markerElement: {
|
|
199
|
+
setAttribute: ["data-text", pinDataText || ""],
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
.onPropChanged(
|
|
206
|
+
["markerElement", "pinDataPopoverText"],
|
|
207
|
+
({ markerElement, pinDataPopoverText }) => {
|
|
208
|
+
if (markerElement) {
|
|
209
|
+
return {
|
|
210
|
+
markerElement: {
|
|
211
|
+
setAttribute: ["data-popover-text", pinDataPopoverText || ""],
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
.onPropChanged(
|
|
218
|
+
["pinStyle", "markerElement"],
|
|
219
|
+
({ pinStyle, markerElement }) => {
|
|
220
|
+
if (markerElement) {
|
|
221
|
+
markerElement.setAttribute(
|
|
222
|
+
"style",
|
|
223
|
+
`${markerElement.getAttribute("style") ?? ""}; ${pinStyle ?? ""}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
.onPropChanged(
|
|
229
|
+
["markerElement", "focusEvents"],
|
|
230
|
+
({ markerElement, handleMarkerClick, focusEvents }, previous) => {
|
|
231
|
+
if (markerElement) {
|
|
232
|
+
return {
|
|
233
|
+
markerElement: {
|
|
234
|
+
toggleListeners: [
|
|
235
|
+
...(focusEvents || []).map((k) => [k, handleMarkerClick, true]),
|
|
236
|
+
...(previous?.focusEvents
|
|
237
|
+
? (previous.focusEvents || []).map((k) => [
|
|
238
|
+
k,
|
|
239
|
+
handleMarkerClick,
|
|
240
|
+
false,
|
|
241
|
+
])
|
|
242
|
+
: []),
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
.onEvent("mapbox-view-marker-focus-trigger", (_, e) => {
|
|
250
|
+
e.stopPropagation();
|
|
251
|
+
return {
|
|
252
|
+
panToMarker: [],
|
|
253
|
+
};
|
|
254
|
+
})
|
|
255
|
+
.onEventDefault("mapbox-view-marker-focus", ({ markerElement }) => {
|
|
256
|
+
markerElement?.classList.add("is-active");
|
|
257
|
+
return {
|
|
258
|
+
broadcast: ["mapbox-view-marker-focus", { detail: markerElement }],
|
|
259
|
+
};
|
|
260
|
+
})
|
|
261
|
+
.onEvent("mapbox-view-marker-blur-trigger", (_, e) => {
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
return {
|
|
264
|
+
emit: ["mapbox-view-marker-blur"],
|
|
265
|
+
};
|
|
266
|
+
})
|
|
267
|
+
.onEventDefault("mapbox-view-marker-blur", ({ markerElement }) => {
|
|
268
|
+
markerElement?.classList.remove("is-active");
|
|
269
|
+
})
|
|
270
|
+
.onPropChanged("blurOnMapClick", ({ blurOnMapClick, blurCallback }) => ({
|
|
271
|
+
toggleBroadcastListeners: [
|
|
272
|
+
["mapbox-view-clicked", blurCallback, !!blurOnMapClick],
|
|
273
|
+
],
|
|
274
|
+
}))
|
|
275
|
+
.onPropChanged("singletonName", ({ singletonName, singletonCallback }) => ({
|
|
276
|
+
toggleBroadcastListeners: [
|
|
277
|
+
["mapbox-view-marker-focus", singletonCallback, !!singletonName],
|
|
278
|
+
],
|
|
279
|
+
}));
|
package/mapbox-view.ts
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LayersConfig,
|
|
3
|
+
MapInstance,
|
|
4
|
+
TAnimationOptions,
|
|
5
|
+
TCameraOptions,
|
|
6
|
+
TMapboxgl,
|
|
7
|
+
TMapTouchEvent,
|
|
8
|
+
} from "./src/types";
|
|
9
|
+
import { EASINGS, parseCoordsFromTokens, queueTask } from "./src/utils";
|
|
10
|
+
import { createElement, deleteUndefined, isPojo } from "@excom/kit-utils";
|
|
11
|
+
import { loadDependency } from "@excom/kit-utils/load-dependency";
|
|
12
|
+
import { ConstructorType, Neutron, TokenList } from "@excom/neutron";
|
|
13
|
+
|
|
14
|
+
const centerCameraOpts = (el: any): TCameraOptions => {
|
|
15
|
+
const coords = parseCoordsFromTokens(el.centerCoords, el.camOffset) || [0, 0];
|
|
16
|
+
// `maxBounds` must be a TokenList / array of 4 finite numbers
|
|
17
|
+
let maxBounds: [[number, number], [number, number]] | undefined = undefined;
|
|
18
|
+
if (el.maxBounds?.length === 4) {
|
|
19
|
+
const nums = Array.from(el.maxBounds, (v) => Number(v));
|
|
20
|
+
if (nums.every((v) => typeof v === "number" && isFinite(v))) {
|
|
21
|
+
maxBounds = [
|
|
22
|
+
[nums[0], nums[1]], // SW [lng, lat]
|
|
23
|
+
[nums[2], nums[3]], // NE [lng, lat]
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return deleteUndefined({
|
|
28
|
+
center: coords, // [lng, lat]; lat must be -90..90
|
|
29
|
+
zoom: el.centerZoom || 1, // starting zoom
|
|
30
|
+
pitch: el.centerPitch || 0, // degrees
|
|
31
|
+
bearing: el.centerBearing || 0, // degrees
|
|
32
|
+
minZoom: el.minZoom ?? 10,
|
|
33
|
+
maxZoom: el.maxZoom ?? 20,
|
|
34
|
+
maxBounds,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const activeCameraOpts = (el: any): TCameraOptions => {
|
|
38
|
+
const coords = parseCoordsFromTokens(el.activeCoords, el.camOffset) ||
|
|
39
|
+
parseCoordsFromTokens(el.centerCoords, el.camOffset) || [0, 0];
|
|
40
|
+
return deleteUndefined({
|
|
41
|
+
center: coords,
|
|
42
|
+
zoom: el.activeZoom ?? undefined,
|
|
43
|
+
pitch: el.activePitch ?? undefined,
|
|
44
|
+
bearing: el.activeBearing ?? undefined,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const shouldAnimate = (el: any): boolean => {
|
|
48
|
+
return !!(
|
|
49
|
+
typeof el.camAnimation === "string" ||
|
|
50
|
+
el.camAnimationDuration ||
|
|
51
|
+
el.camAnimationOffset?.length ||
|
|
52
|
+
el.camAnimationEssential
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
const animateCameraOpts = (el: any): TAnimationOptions => {
|
|
56
|
+
return deleteUndefined({
|
|
57
|
+
// Even `cam-animate=""` still animates with the default easing
|
|
58
|
+
animate: shouldAnimate(el),
|
|
59
|
+
easing:
|
|
60
|
+
(el.camAnimation && EASINGS[el.camAnimation]) ||
|
|
61
|
+
EASINGS["ease-out-quint"],
|
|
62
|
+
duration: el.camAnimationDuration || undefined,
|
|
63
|
+
offset: (el.camAnimationOffset?.length === 2
|
|
64
|
+
? el.camAnimationOffset.map((n) => Number(n))
|
|
65
|
+
: undefined) as [number, number],
|
|
66
|
+
essential: el.camAnimationEssential || undefined,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const checkMapMoved = (el: any): boolean => {
|
|
70
|
+
if (!el.mapInstance) return false;
|
|
71
|
+
const opts = centerCameraOpts(el);
|
|
72
|
+
const camCoords = el.mapInstance?.getCenter();
|
|
73
|
+
const coordsTolerance = el.centerCoordsTolerance || 0.0005;
|
|
74
|
+
const zoomTolerance = el.centerZoomTolerance || 0.5;
|
|
75
|
+
const pitchTolerance = el.centerPitchTolerance || 0;
|
|
76
|
+
const bearingTolerance = el.centerBearingTolerance || 0;
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
!opts.center ||
|
|
80
|
+
opts.zoom === undefined ||
|
|
81
|
+
opts.pitch === undefined ||
|
|
82
|
+
opts.bearing === undefined
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const r = el.preserveCamera || [];
|
|
88
|
+
|
|
89
|
+
return !(
|
|
90
|
+
(r.includes("coords") ||
|
|
91
|
+
Math.abs(camCoords.lng - opts.center[0]) <= coordsTolerance) &&
|
|
92
|
+
(r.includes("coords") ||
|
|
93
|
+
Math.abs(camCoords.lat - opts.center[1]) <= coordsTolerance) &&
|
|
94
|
+
(r.includes("zoom") ||
|
|
95
|
+
Math.abs(el.mapInstance.getZoom() - opts.zoom) <= zoomTolerance) &&
|
|
96
|
+
(r.includes("pitch") ||
|
|
97
|
+
Math.abs(el.mapInstance.getPitch() - opts.pitch) <= pitchTolerance) &&
|
|
98
|
+
(r.includes("bearing") ||
|
|
99
|
+
Math.abs(el.mapInstance.getBearing() - opts.bearing) <= bearingTolerance)
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const MapboxView = Neutron({
|
|
104
|
+
tag: "mapbox-view",
|
|
105
|
+
renderRoot: {
|
|
106
|
+
tag: "div",
|
|
107
|
+
shadow: "open",
|
|
108
|
+
},
|
|
109
|
+
props: {
|
|
110
|
+
mapboxPromise: Promise as ConstructorType<Promise<TMapboxgl>>,
|
|
111
|
+
containerElement: HTMLElement,
|
|
112
|
+
mapInstance: Object as unknown as ConstructorType<MapInstance>,
|
|
113
|
+
accessToken: String,
|
|
114
|
+
mapStyle: String,
|
|
115
|
+
isLoaded: Boolean,
|
|
116
|
+
isAnimated: Boolean,
|
|
117
|
+
isMoved: Boolean,
|
|
118
|
+
centerCoords: TokenList,
|
|
119
|
+
centerCoordsTolerance: Number,
|
|
120
|
+
centerZoom: Number,
|
|
121
|
+
centerZoomTolerance: Number,
|
|
122
|
+
centerPitch: Number,
|
|
123
|
+
centerPitchTolerance: Number,
|
|
124
|
+
centerBearing: Number,
|
|
125
|
+
centerBearingTolerance: Number,
|
|
126
|
+
activeCoords: TokenList,
|
|
127
|
+
activeZoom: Number,
|
|
128
|
+
activePitch: Number,
|
|
129
|
+
activeBearing: Number,
|
|
130
|
+
preserveCamera: TokenList,
|
|
131
|
+
minZoom: Number,
|
|
132
|
+
maxZoom: Number,
|
|
133
|
+
maxBounds: TokenList,
|
|
134
|
+
camOffset: TokenList,
|
|
135
|
+
camAnimation: String,
|
|
136
|
+
camAnimationDuration: Number,
|
|
137
|
+
camAnimationOffset: TokenList,
|
|
138
|
+
camAnimationEssential: Boolean,
|
|
139
|
+
timerId: Number,
|
|
140
|
+
isDragging: Boolean,
|
|
141
|
+
clickholdMs: Number,
|
|
142
|
+
droppedPin: Object,
|
|
143
|
+
layers: Object as unknown as ConstructorType<LayersConfig>,
|
|
144
|
+
activeLayer: String,
|
|
145
|
+
visibleLayers: TokenList,
|
|
146
|
+
camPadding: TokenList,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
.defineMethods({
|
|
150
|
+
holdStartCb: (
|
|
151
|
+
// @ts-ignore TODO defineMethods
|
|
152
|
+
{ isDragging, clickholdMs, emitClickhold },
|
|
153
|
+
e: TMapTouchEvent
|
|
154
|
+
) => {
|
|
155
|
+
// Ignore multi-touch
|
|
156
|
+
if (e.originalEvent?.touches && e.originalEvent.touches.length !== 1)
|
|
157
|
+
return;
|
|
158
|
+
return (
|
|
159
|
+
!isDragging &&
|
|
160
|
+
clickholdMs && {
|
|
161
|
+
timerId: setTimeout(
|
|
162
|
+
() => emitClickhold(e.lngLat),
|
|
163
|
+
clickholdMs
|
|
164
|
+
) as unknown as number,
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
holdEndCb: ({ timerId }) => {
|
|
169
|
+
if (timerId) clearTimeout(timerId);
|
|
170
|
+
},
|
|
171
|
+
holdDragstartCb: ({ timerId }) => {
|
|
172
|
+
if (timerId) clearTimeout(timerId);
|
|
173
|
+
return {
|
|
174
|
+
isDragging: true,
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
holdDragendCb: () => {
|
|
178
|
+
return {
|
|
179
|
+
isDragging: false,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
mapClickedCb: (_, e: TMapTouchEvent) => ({
|
|
183
|
+
broadcast: ["mapbox-view-clicked", { detail: e }],
|
|
184
|
+
}),
|
|
185
|
+
emitClickhold: (_, lngLat) => {
|
|
186
|
+
return {
|
|
187
|
+
emit: [
|
|
188
|
+
"mapbox-view-clickhold",
|
|
189
|
+
{
|
|
190
|
+
detail: lngLat,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
loadedCallback: () => ({
|
|
196
|
+
isLoaded: true,
|
|
197
|
+
emit: ["mapbox-view-loaded"],
|
|
198
|
+
}),
|
|
199
|
+
calcIsMoved: (element, override: boolean) => ({
|
|
200
|
+
isMoved: override ?? checkMapMoved(element),
|
|
201
|
+
}),
|
|
202
|
+
updateCamera: (
|
|
203
|
+
element,
|
|
204
|
+
destination: "center" | "active",
|
|
205
|
+
cb?: () => Record<string, any> | any
|
|
206
|
+
) => {
|
|
207
|
+
if (element.mapInstance) {
|
|
208
|
+
// Already at the destination (within tolerance): skip animation,
|
|
209
|
+
// but still update `isMoved` to match.
|
|
210
|
+
if (destination === "center" && !checkMapMoved(element)) {
|
|
211
|
+
return { calcIsMoved: [false] };
|
|
212
|
+
}
|
|
213
|
+
queueTask(element.mapInstance, (m) => {
|
|
214
|
+
const cbResult = cb?.();
|
|
215
|
+
if (!cb || cbResult) {
|
|
216
|
+
const updateObject = isPojo(cbResult) ? cbResult : {};
|
|
217
|
+
const cameraOpts = deleteUndefined({
|
|
218
|
+
...(destination === "center"
|
|
219
|
+
? centerCameraOpts(element)
|
|
220
|
+
: activeCameraOpts(element)),
|
|
221
|
+
// Drop any properties in the `preserveCamera` list
|
|
222
|
+
...Object.fromEntries(
|
|
223
|
+
(element.preserveCamera || []).map((key: string) => [
|
|
224
|
+
key === "coords" ? "center" : key,
|
|
225
|
+
undefined,
|
|
226
|
+
])
|
|
227
|
+
),
|
|
228
|
+
...deleteUndefined(updateObject),
|
|
229
|
+
});
|
|
230
|
+
if (destination === "center" && element.isAnimated) {
|
|
231
|
+
// Continuous tracking: `easeTo` for short incremental updates
|
|
232
|
+
m.easeTo({ ...cameraOpts, duration: 250 });
|
|
233
|
+
} else {
|
|
234
|
+
m[element.isAnimated ? "flyTo" : "jumpTo"]({
|
|
235
|
+
...cameraOpts,
|
|
236
|
+
...(element.isAnimated ? animateCameraOpts(element) : {}),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
/* Return outside `queueTask` so Neutron sees the state update.
|
|
242
|
+
A return inside the callback was discarded, leaving `isMoved`
|
|
243
|
+
stuck after programmatic camera moves (recenter, bearing). */
|
|
244
|
+
return {
|
|
245
|
+
calcIsMoved: [destination === "active"],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
zoomToOverview: (element) => {
|
|
250
|
+
if (element.mapInstance) {
|
|
251
|
+
queueTask(element.mapInstance, (m) => {
|
|
252
|
+
m[element.isAnimated ? "easeTo" : "jumpTo"]({
|
|
253
|
+
zoom: element.centerZoom ?? undefined,
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
.onConnected((el) => {
|
|
260
|
+
if (el.isMoving) return;
|
|
261
|
+
const cssLink = createElement("link", {
|
|
262
|
+
rel: "stylesheet",
|
|
263
|
+
href: "https://api.mapbox.com/mapbox-gl-js/v3.9.2/mapbox-gl.css",
|
|
264
|
+
});
|
|
265
|
+
const div = createElement("div", {
|
|
266
|
+
style: "height: 100%; width: 100%;",
|
|
267
|
+
id: `mapbox-container-${Math.random().toString(36).substr(2, 9)}`,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Layers from a script slot
|
|
271
|
+
const layersScript = el.querySelector('script[slot="layers"]');
|
|
272
|
+
if (layersScript && layersScript.textContent) {
|
|
273
|
+
try {
|
|
274
|
+
const layersData = JSON.parse(layersScript.textContent);
|
|
275
|
+
el.layers = layersData;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error("Failed to parse layers JSON:", error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
renderRoot: {
|
|
283
|
+
append: [cssLink, div],
|
|
284
|
+
},
|
|
285
|
+
containerElement: div,
|
|
286
|
+
};
|
|
287
|
+
})
|
|
288
|
+
.onPropChanged(["accessToken", "containerElement"], (el, previous) => {
|
|
289
|
+
if (el.accessToken && el.containerElement) {
|
|
290
|
+
// `accessToken` changed: recreate the map
|
|
291
|
+
const accessTokenChanged =
|
|
292
|
+
previous?.accessToken && previous.accessToken !== el.accessToken;
|
|
293
|
+
|
|
294
|
+
if (!el.mapInstance || accessTokenChanged) {
|
|
295
|
+
// Tear down the existing map after an access-token change
|
|
296
|
+
if (accessTokenChanged && el.mapInstance) {
|
|
297
|
+
el.mapInstance.remove();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Empty the container before creating a new map
|
|
301
|
+
el.containerElement.innerHTML = "";
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
mapboxPromise: loadDependency<TMapboxgl>(
|
|
305
|
+
"umd",
|
|
306
|
+
"https://api.mapbox.com/mapbox-gl-js/v3.9.2/mapbox-gl.js",
|
|
307
|
+
"mapboxgl"
|
|
308
|
+
),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
.onPromiseResolved("mapboxPromise", (el, result) => {
|
|
314
|
+
const mapbox = result.mapboxPromise as unknown as TMapboxgl;
|
|
315
|
+
if (mapbox) {
|
|
316
|
+
mapbox.accessToken = el.accessToken;
|
|
317
|
+
return {
|
|
318
|
+
mapboxPromise: null,
|
|
319
|
+
// TODO support dark mode
|
|
320
|
+
mapInstance: new mapbox.Map({
|
|
321
|
+
container: el.containerElement!,
|
|
322
|
+
style: el.mapStyle || undefined,
|
|
323
|
+
...centerCameraOpts(el),
|
|
324
|
+
}),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
.onPropSet(
|
|
329
|
+
"mapInstance",
|
|
330
|
+
({ mapInstance, loadedCallback, mapClickedCb, calcIsMoved }) => {
|
|
331
|
+
// Recalc `isMoved` for user gestures only, not programmatic `flyTo` / `easeTo`
|
|
332
|
+
mapInstance.on("movestart", (e) => e.originalEvent && calcIsMoved(true));
|
|
333
|
+
mapInstance.on("moveend", (e) => e.originalEvent && calcIsMoved());
|
|
334
|
+
mapInstance.on("load", () => loadedCallback());
|
|
335
|
+
queueTask(mapInstance, (m) => m.on("click", mapClickedCb));
|
|
336
|
+
}
|
|
337
|
+
)
|
|
338
|
+
.onPropChanged("mapStyle", ({ mapInstance, mapStyle }) => {
|
|
339
|
+
if (mapInstance && mapStyle) {
|
|
340
|
+
queueTask(mapInstance, (m) => {
|
|
341
|
+
try {
|
|
342
|
+
m.setStyle(mapStyle);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.warn("Failed to set map style:", error);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
.onPropChanged("camPadding", ({ mapInstance, camPadding }) => {
|
|
350
|
+
if (mapInstance) {
|
|
351
|
+
if (Array.isArray(camPadding) && camPadding.length >= 1) {
|
|
352
|
+
const nums = camPadding.map((v: any) => Number(v) || 0);
|
|
353
|
+
// 1-4 values like CSS: [all], [vertical horizontal], [top right bottom left]
|
|
354
|
+
const top = nums[0];
|
|
355
|
+
const right = nums.length > 1 ? nums[1] : top;
|
|
356
|
+
const bottom = nums.length > 2 ? nums[2] : top;
|
|
357
|
+
const left = nums.length > 3 ? nums[3] : right;
|
|
358
|
+
queueTask(mapInstance, (m) =>
|
|
359
|
+
m.setPadding({ top, right, bottom, left })
|
|
360
|
+
);
|
|
361
|
+
} else {
|
|
362
|
+
queueTask(mapInstance, (m) =>
|
|
363
|
+
m.setPadding({ top: 0, right: 0, bottom: 0, left: 0 })
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
.onPropChanged("maxBounds", (el) => {
|
|
369
|
+
if (el.mapInstance && el.maxBounds?.length === 4) {
|
|
370
|
+
const nums = Array.from(el.maxBounds, (v) => Number(v));
|
|
371
|
+
if (nums.every((v) => isFinite(v))) {
|
|
372
|
+
queueTask(el.mapInstance, (m) =>
|
|
373
|
+
m.setMaxBounds([
|
|
374
|
+
[nums[0], nums[1]],
|
|
375
|
+
[nums[2], nums[3]],
|
|
376
|
+
])
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
.onPropChanged(
|
|
382
|
+
["centerCoords", "centerZoom", "centerPitch", "centerBearing"],
|
|
383
|
+
({ isMoved }) => !isMoved && { updateCamera: ["center"] }
|
|
384
|
+
)
|
|
385
|
+
.onPropChanged(
|
|
386
|
+
["activeCoords", "activeZoom", "activePitch", "activeBearing"],
|
|
387
|
+
() => ({
|
|
388
|
+
updateCamera: ["active"],
|
|
389
|
+
})
|
|
390
|
+
)
|
|
391
|
+
.onEvent(
|
|
392
|
+
"mapbox-view-recenter",
|
|
393
|
+
({ isMoved }) => isMoved && { updateCamera: ["center"] }
|
|
394
|
+
)
|
|
395
|
+
.onEvent("mapbox-view-reactive", () => ({
|
|
396
|
+
updateCamera: ["active"],
|
|
397
|
+
}))
|
|
398
|
+
.onEvent("mapbox-view-zoom-in", ({ activeZoom, centerZoom }) => ({
|
|
399
|
+
activeZoom: (activeZoom ?? centerZoom ?? 1) + 1,
|
|
400
|
+
}))
|
|
401
|
+
.onEvent("mapbox-view-zoom-out", ({ activeZoom, centerZoom }) => ({
|
|
402
|
+
activeZoom: (activeZoom ?? centerZoom ?? 1) - 1,
|
|
403
|
+
}))
|
|
404
|
+
.onEvent("mapbox-view-zoom-overview", () => ({
|
|
405
|
+
zoomToOverview: [],
|
|
406
|
+
}))
|
|
407
|
+
.onPropChanged(
|
|
408
|
+
["clickholdMs", "mapInstance"],
|
|
409
|
+
({
|
|
410
|
+
clickholdMs,
|
|
411
|
+
mapInstance,
|
|
412
|
+
holdStartCb,
|
|
413
|
+
holdEndCb,
|
|
414
|
+
holdDragstartCb,
|
|
415
|
+
holdDragendCb,
|
|
416
|
+
}) => {
|
|
417
|
+
if (mapInstance) {
|
|
418
|
+
// Drop previous mouse + touch listeners
|
|
419
|
+
mapInstance.off("mousedown", holdStartCb);
|
|
420
|
+
mapInstance.off("mouseup", holdEndCb);
|
|
421
|
+
mapInstance.off("touchstart", holdStartCb);
|
|
422
|
+
mapInstance.off("touchend", holdEndCb);
|
|
423
|
+
mapInstance.off("touchmove", holdEndCb);
|
|
424
|
+
mapInstance.off("touchcancel", holdEndCb);
|
|
425
|
+
mapInstance.off("dragstart", holdDragstartCb);
|
|
426
|
+
mapInstance.off("dragend", holdDragendCb);
|
|
427
|
+
if (clickholdMs) {
|
|
428
|
+
const nav = navigator as any;
|
|
429
|
+
const isTouch = "ontouchstart" in window || nav.maxTouchPoints > 0;
|
|
430
|
+
if (isTouch) {
|
|
431
|
+
mapInstance.on("touchstart", holdStartCb);
|
|
432
|
+
mapInstance.on("touchend", holdEndCb);
|
|
433
|
+
mapInstance.on("touchmove", holdEndCb);
|
|
434
|
+
mapInstance.on("touchcancel", holdEndCb);
|
|
435
|
+
} else {
|
|
436
|
+
mapInstance.on("mousedown", holdStartCb);
|
|
437
|
+
mapInstance.on("mouseup", holdEndCb);
|
|
438
|
+
}
|
|
439
|
+
// A drag cancels the hold
|
|
440
|
+
mapInstance.on("dragstart", holdDragstartCb);
|
|
441
|
+
mapInstance.on("dragend", holdDragendCb);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
)
|
|
446
|
+
.onPropSet("droppedPin", (_, previous) => {
|
|
447
|
+
(previous.droppedPin as any)?.remove();
|
|
448
|
+
})
|
|
449
|
+
.onPropChanged("activeLayer", ({ mapInstance, layers, activeLayer }) => {
|
|
450
|
+
if (!mapInstance || !activeLayer) return;
|
|
451
|
+
|
|
452
|
+
// Toggle custom layers from `layers`
|
|
453
|
+
if (layers) {
|
|
454
|
+
Object.keys(layers).forEach((layerId) => {
|
|
455
|
+
const fullLayerId = `custom-layer-${layerId}`;
|
|
456
|
+
if (mapInstance.getLayer(fullLayerId)) {
|
|
457
|
+
mapInstance.setLayoutProperty(
|
|
458
|
+
fullLayerId,
|
|
459
|
+
"visibility",
|
|
460
|
+
layerId === activeLayer ? "visible" : "none"
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Toggle style layers: hide others of the same type, show the active one
|
|
467
|
+
queueTask(mapInstance, (m) => {
|
|
468
|
+
try {
|
|
469
|
+
const targetLayer = m.getLayer(activeLayer);
|
|
470
|
+
if (!targetLayer) return;
|
|
471
|
+
const style = m.getStyle();
|
|
472
|
+
style.layers.forEach((layer) => {
|
|
473
|
+
if (layer.type === targetLayer.type) {
|
|
474
|
+
m.setLayoutProperty(
|
|
475
|
+
layer.id,
|
|
476
|
+
"visibility",
|
|
477
|
+
layer.id === activeLayer ? "visible" : "none"
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error(`Error setting active layer ${activeLayer}:`, error);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
})
|
|
486
|
+
.onPropChanged("visibleLayers", ({ mapInstance, visibleLayers }) => {
|
|
487
|
+
if (!mapInstance) return;
|
|
488
|
+
queueTask(mapInstance, (m) => {
|
|
489
|
+
const style = m.getStyle();
|
|
490
|
+
if (!style?.layers) return;
|
|
491
|
+
style.layers.forEach((layer) => {
|
|
492
|
+
try {
|
|
493
|
+
// Only layers with a layout property (skip required base layers)
|
|
494
|
+
if (layer.layout !== undefined) {
|
|
495
|
+
const shouldBeVisible = visibleLayers?.includes(layer.id);
|
|
496
|
+
const currentVisibility = m.getLayoutProperty(
|
|
497
|
+
layer.id,
|
|
498
|
+
"visibility"
|
|
499
|
+
);
|
|
500
|
+
if (currentVisibility !== (shouldBeVisible ? "visible" : "none")) {
|
|
501
|
+
m.setLayoutProperty(
|
|
502
|
+
layer.id,
|
|
503
|
+
"visibility",
|
|
504
|
+
shouldBeVisible ? "visible" : "none"
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.warn(
|
|
510
|
+
`Could not set visibility for layer ${layer.id}:`,
|
|
511
|
+
error
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
})
|
|
517
|
+
.onPropChanged(
|
|
518
|
+
["layers", "mapInstance"],
|
|
519
|
+
({ layers, mapInstance, activeLayer }) => {
|
|
520
|
+
if (mapInstance && layers) {
|
|
521
|
+
// Remove existing layers
|
|
522
|
+
const existingLayers = mapInstance.getStyle()?.layers || [];
|
|
523
|
+
existingLayers.forEach((layer) => {
|
|
524
|
+
if (layer.id.startsWith("custom-layer-")) {
|
|
525
|
+
mapInstance.removeLayer(layer.id);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Add the new layers
|
|
530
|
+
Object.entries(layers).forEach(
|
|
531
|
+
([layerId, layerConfig]: [string, any]) => {
|
|
532
|
+
const fullLayerId = `custom-layer-${layerId}`;
|
|
533
|
+
|
|
534
|
+
// Add the source if it's missing
|
|
535
|
+
if (!mapInstance.getSource(fullLayerId)) {
|
|
536
|
+
mapInstance.addSource(fullLayerId, layerConfig.source);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Add the layer
|
|
540
|
+
mapInstance.addLayer({
|
|
541
|
+
id: fullLayerId,
|
|
542
|
+
...layerConfig.layer,
|
|
543
|
+
source: fullLayerId,
|
|
544
|
+
layout: {
|
|
545
|
+
...layerConfig.layer.layout,
|
|
546
|
+
visibility: layerId === activeLayer ? "visible" : "none",
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
.onDisconnected(
|
|
555
|
+
({
|
|
556
|
+
mapInstance,
|
|
557
|
+
shadowRoot,
|
|
558
|
+
isMoving,
|
|
559
|
+
holdStartCb,
|
|
560
|
+
holdEndCb,
|
|
561
|
+
holdDragstartCb,
|
|
562
|
+
holdDragendCb,
|
|
563
|
+
}) => {
|
|
564
|
+
if (isMoving) return;
|
|
565
|
+
if (mapInstance) {
|
|
566
|
+
// Drop holdstart / holdend listeners from click-hold
|
|
567
|
+
mapInstance.off("mousedown", holdStartCb);
|
|
568
|
+
mapInstance.off("mouseup", holdEndCb);
|
|
569
|
+
mapInstance.off("touchstart", holdStartCb);
|
|
570
|
+
mapInstance.off("touchend", holdEndCb);
|
|
571
|
+
mapInstance.off("touchmove", holdEndCb);
|
|
572
|
+
mapInstance.off("touchcancel", holdEndCb);
|
|
573
|
+
mapInstance.off("dragstart", holdDragstartCb);
|
|
574
|
+
mapInstance.off("dragend", holdDragendCb);
|
|
575
|
+
// `remove()` drops built-in listeners (movestart, moveend, load, click)
|
|
576
|
+
mapInstance.remove();
|
|
577
|
+
}
|
|
578
|
+
if (shadowRoot) {
|
|
579
|
+
shadowRoot.innerHTML = "";
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@excom/mapbox-view",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "<mapbox-view> custom element",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.13.0"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"mapbox-gl": "^3.9.2",
|
|
12
|
+
"@excom/neutron": "^0.1.0",
|
|
13
|
+
"@excom/kit-utils": "^0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@excom/heft-rig": "^0.1.0"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"url": "excom-dev/nucleus",
|
|
21
|
+
"directory": "packages/mapbox-view"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/mapbox-view/support/docs/README.md",
|
|
24
|
+
"bugs": "https://github.com/excom-dev/nucleus/issues",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mapbox-view",
|
|
27
|
+
"neutron",
|
|
28
|
+
"custom-elements"
|
|
29
|
+
],
|
|
30
|
+
"excom": {
|
|
31
|
+
"coverageThreshold": false,
|
|
32
|
+
"documented": false,
|
|
33
|
+
"packageType": "kit-element"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
|
|
37
|
+
"build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
|
|
38
|
+
"format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
|
|
39
|
+
"test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
|
|
40
|
+
"coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
|
|
41
|
+
"dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
|
|
42
|
+
"preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
|
|
43
|
+
"build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
|
|
44
|
+
"build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Caching has been disabled for this project's "apply-exports" command.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Import mapbox-gl as `type` only. Never bundle it.
|
|
2
|
+
import type mapboxglDefault from "mapbox-gl";
|
|
3
|
+
|
|
4
|
+
export type TMapboxgl = typeof mapboxglDefault;
|
|
5
|
+
export type TMapboxglMarkerOptions = mapboxgl.MarkerOptions;
|
|
6
|
+
export type TMapTouchEvent = mapboxgl.MapTouchEvent;
|
|
7
|
+
export type TCameraOptions = mapboxgl.CameraOptions;
|
|
8
|
+
export type TAnimationOptions = mapboxgl.AnimationOptions;
|
|
9
|
+
export type MapInstance = InstanceType<TMapboxgl["Map"]>;
|
|
10
|
+
|
|
11
|
+
export interface LayerSource {
|
|
12
|
+
type: string;
|
|
13
|
+
data?: any;
|
|
14
|
+
url?: string;
|
|
15
|
+
tiles?: string[];
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LayerConfig {
|
|
20
|
+
source: LayerSource;
|
|
21
|
+
layer: {
|
|
22
|
+
type: string;
|
|
23
|
+
paint?: Record<string, any>;
|
|
24
|
+
layout?: Record<string, any>;
|
|
25
|
+
filter?: any[];
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LayersConfig {
|
|
31
|
+
[layerId: string]: LayerConfig;
|
|
32
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { MapInstance } from "./types";
|
|
2
|
+
import type { TMapboxgl } from "./types";
|
|
3
|
+
import { isNumber } from "@excom/kit-utils";
|
|
4
|
+
|
|
5
|
+
export const EASINGS = {
|
|
6
|
+
// Slow start, then speed up
|
|
7
|
+
["ease-in-cubic"]: function (t) {
|
|
8
|
+
return t * t * t;
|
|
9
|
+
},
|
|
10
|
+
// Fast start, long slow wind-down
|
|
11
|
+
["ease-out-quint"]: function (t) {
|
|
12
|
+
return 1 - Math.pow(1 - t, 5);
|
|
13
|
+
},
|
|
14
|
+
// Slow start and finish, fast middle
|
|
15
|
+
["ease-in-out-circ"]: function (t) {
|
|
16
|
+
return t < 0.5
|
|
17
|
+
? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2
|
|
18
|
+
: (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
|
|
19
|
+
},
|
|
20
|
+
// Fast start, bounce at the end
|
|
21
|
+
["ease-out-bounce"]: function (t) {
|
|
22
|
+
const n1 = 7.5625;
|
|
23
|
+
const d1 = 2.75;
|
|
24
|
+
|
|
25
|
+
if (t < 1 / d1) {
|
|
26
|
+
return n1 * t * t;
|
|
27
|
+
} else if (t < 2 / d1) {
|
|
28
|
+
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
29
|
+
} else if (t < 2.5 / d1) {
|
|
30
|
+
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
31
|
+
} else {
|
|
32
|
+
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export const parseCoordsFromTokens = (
|
|
37
|
+
tokens: string[] | null = [],
|
|
38
|
+
offsetTokens: string[] | null = []
|
|
39
|
+
): [number, number] | undefined => {
|
|
40
|
+
if (tokens?.length === 2) {
|
|
41
|
+
const long = Number(tokens[0]);
|
|
42
|
+
const lat = Number(tokens[1]);
|
|
43
|
+
if (isNumber(long) && isNumber(lat)) {
|
|
44
|
+
const offset = parseCoordsFromTokens(offsetTokens);
|
|
45
|
+
if (offset) {
|
|
46
|
+
return [long + offset[0], lat + offset[1]];
|
|
47
|
+
} else {
|
|
48
|
+
return [long, lat];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const parseNumsFromTokens = (
|
|
56
|
+
tokens: string[] | null = []
|
|
57
|
+
): number[] | undefined => {
|
|
58
|
+
const parsed = tokens?.map((t) => Number(t));
|
|
59
|
+
if (parsed?.every(isNumber)) {
|
|
60
|
+
return parsed as number[];
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const queueTask = (
|
|
66
|
+
mapInstance?: MapInstance | null,
|
|
67
|
+
task?: (m: MapInstance, mapboxgl: TMapboxgl) => any
|
|
68
|
+
) => {
|
|
69
|
+
if (mapInstance) {
|
|
70
|
+
if (mapInstance._loaded) {
|
|
71
|
+
task?.(mapInstance, window["mapboxgl"] as unknown as TMapboxgl);
|
|
72
|
+
} else {
|
|
73
|
+
mapInstance.on("load", () => {
|
|
74
|
+
task?.(mapInstance, window["mapboxgl"] as unknown as TMapboxgl);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"modules": [
|
|
4
|
+
{
|
|
5
|
+
"kind": "javascript-module",
|
|
6
|
+
"path": "mapbox-view.ts",
|
|
7
|
+
"declarations": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "class",
|
|
10
|
+
"name": "MapboxView",
|
|
11
|
+
"customElement": true,
|
|
12
|
+
"tagName": "mapbox-view",
|
|
13
|
+
"_neutron": {
|
|
14
|
+
"cssAliases": [
|
|
15
|
+
{
|
|
16
|
+
"name": ":--mapbox-view",
|
|
17
|
+
"selectors": [
|
|
18
|
+
"mapbox-view",
|
|
19
|
+
".tag-mapbox-view"
|
|
20
|
+
],
|
|
21
|
+
"kind": "element"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"exports": [
|
|
28
|
+
{
|
|
29
|
+
"kind": "js",
|
|
30
|
+
"name": "MapboxView",
|
|
31
|
+
"declaration": {
|
|
32
|
+
"name": "MapboxView",
|
|
33
|
+
"module": "mapbox-view.ts"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "custom-element-definition",
|
|
38
|
+
"name": "mapbox-view",
|
|
39
|
+
"declaration": {
|
|
40
|
+
"name": "MapboxView",
|
|
41
|
+
"module": "mapbox-view.ts"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"kind": "javascript-module",
|
|
48
|
+
"path": "mapbox-view-marker.ts",
|
|
49
|
+
"declarations": [
|
|
50
|
+
{
|
|
51
|
+
"kind": "class",
|
|
52
|
+
"name": "MapboxViewMarker",
|
|
53
|
+
"customElement": true,
|
|
54
|
+
"tagName": "mapbox-view-marker"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"exports": [
|
|
58
|
+
{
|
|
59
|
+
"kind": "js",
|
|
60
|
+
"name": "MapboxViewMarker",
|
|
61
|
+
"declaration": {
|
|
62
|
+
"name": "MapboxViewMarker",
|
|
63
|
+
"module": "mapbox-view-marker.ts"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"kind": "custom-element-definition",
|
|
68
|
+
"name": "mapbox-view-marker",
|
|
69
|
+
"declaration": {
|
|
70
|
+
"name": "MapboxViewMarker",
|
|
71
|
+
"module": "mapbox-view-marker.ts"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# `<mapbox-view>`
|
|
2
|
+
|
|
3
|
+
**Tag:** `<mapbox-view>`
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
`@excom/mapbox-view` v0.1.0
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @excom/mapbox-view
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @excom/mapbox-view
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add @excom/mapbox-view
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Import
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import "@excom/mapbox-view";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API
|
|
28
|
+
|
|
29
|
+
### CSS Aliases
|
|
30
|
+
|
|
31
|
+
| Alias | Kind | Matches | Description |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `:--mapbox-view` | element | `mapbox-view`, `.tag-mapbox-view` | |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseCoordsFromTokens,
|
|
3
|
+
parseNumsFromTokens,
|
|
4
|
+
queueTask,
|
|
5
|
+
} from "../../src/utils";
|
|
6
|
+
import {
|
|
7
|
+
describe,
|
|
8
|
+
expect,
|
|
9
|
+
it,
|
|
10
|
+
vi,
|
|
11
|
+
} from "@excom/heft-rig/node_modules/vitest";
|
|
12
|
+
|
|
13
|
+
describe("mapbox-view utils", () => {
|
|
14
|
+
it("parses coordinate tokens with offsets", () => {
|
|
15
|
+
const result = parseCoordsFromTokens(["1", "2"], ["3", "4"]);
|
|
16
|
+
expect(result).toEqual([4, 6]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("parses numeric tokens", () => {
|
|
20
|
+
expect(parseNumsFromTokens(["1", "2", "3"])).toEqual([1, 2, 3]);
|
|
21
|
+
expect(parseNumsFromTokens(["1", "x"])).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("queues tasks until map is loaded", () => {
|
|
25
|
+
const task = vi.fn();
|
|
26
|
+
(window as unknown as { mapboxgl?: object }).mapboxgl = { version: "1" };
|
|
27
|
+
const loadedMap = { _loaded: true, on: vi.fn() };
|
|
28
|
+
queueTask(loadedMap as any, task);
|
|
29
|
+
expect(task).toHaveBeenCalledWith(loadedMap, (window as any).mapboxgl);
|
|
30
|
+
|
|
31
|
+
const on = vi.fn((_, cb: () => void) => cb());
|
|
32
|
+
const pendingMap = { _loaded: false, on };
|
|
33
|
+
const pendingTask = vi.fn();
|
|
34
|
+
queueTask(pendingMap as any, pendingTask);
|
|
35
|
+
expect(on).toHaveBeenCalled();
|
|
36
|
+
expect(pendingTask).toHaveBeenCalledWith(
|
|
37
|
+
pendingMap,
|
|
38
|
+
(window as any).mapboxgl
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
|
|
3
|
+
"include": ["./*.ts"],
|
|
4
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"declaration": false,
|
|
7
|
+
"declarationMap": false,
|
|
8
|
+
"emitDeclarationOnly": false,
|
|
9
|
+
"noEmit": true
|
|
10
|
+
}
|
|
11
|
+
}
|