@defra/interactive-map 0.0.4-alpha → 0.0.5-alpha
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/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api.md +8 -0
- package/package.json +3 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -1
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/datasets.js +55 -2
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
- package/plugins/beta/datasets/src/handleSetMapStyle.js +8 -1
- package/plugins/beta/datasets/src/mapLayers.js +41 -3
- package/plugins/beta/datasets/src/utils/bbox.js +113 -2
- package/plugins/beta/draw-ml/dist/css/index.css +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/index.js +1 -1
- package/plugins/beta/draw-ml/src/api/addFeature.js +15 -3
- package/plugins/beta/draw-ml/src/api/editFeature.js +16 -5
- package/plugins/beta/draw-ml/src/api/newLine.js +32 -7
- package/plugins/beta/draw-ml/src/api/newPolygon.js +33 -8
- package/plugins/beta/draw-ml/src/defaults.js +1 -0
- package/plugins/beta/draw-ml/src/draw.scss +23 -0
- package/plugins/beta/draw-ml/src/events.js +55 -113
- package/plugins/beta/draw-ml/src/manifest.js +64 -38
- package/plugins/beta/draw-ml/src/mapboxSnap.js +16 -12
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +4 -3
- package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +51 -406
- package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +1 -1
- package/plugins/beta/map-styles/dist/css/index.css +1 -1
- package/plugins/beta/map-styles/dist/esm/index.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/map-styles/src/mapStyles.scss +4 -1
- package/plugins/interact/dist/css/index.css +1 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +1 -1
- package/plugins/interact/src/defaults.js +1 -0
- package/plugins/interact/src/events.js +37 -52
- package/plugins/interact/src/events.test.js +35 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -48
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +64 -8
- package/plugins/interact/src/interact.scss +6 -0
- package/plugins/interact/src/reducer.js +25 -27
- package/plugins/interact/src/reducer.test.js +19 -6
- package/plugins/interact/src/utils/featureQueries.js +2 -2
- package/plugins/interact/src/utils/spatial.js +95 -0
- package/plugins/interact/src/utils/spatial.test.js +93 -0
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +2 -2
- package/plugins/search/src/components/Form/Form.jsx +1 -1
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
- package/plugins/search/src/datasets.js +1 -1
- package/plugins/search/src/defaults.js +3 -0
- package/plugins/search/src/events/fetchSuggestions.js +48 -53
- package/plugins/search/src/events/formHandlers.js +3 -2
- package/plugins/search/src/events/index.js +1 -1
- package/plugins/search/src/events/inputHandlers.js +3 -1
- package/plugins/search/src/events/suggestionHandlers.js +12 -3
- package/plugins/search/src/search.scss +4 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +12 -12
- package/providers/beta/esri/src/esriProvider.js +1 -1
- package/providers/beta/esri/src/index.js +1 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/defaults.js +3 -2
- package/providers/maplibre/src/index.js +22 -19
- package/providers/maplibre/src/maplibreProvider.js +13 -11
- package/providers/maplibre/src/utils/detectWebgl.js +1 -2
- package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
- package/providers/maplibre/src/utils/labels.js +174 -120
- package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
- package/providers/maplibre/src/utils/queryFeatures.js +146 -0
- package/providers/maplibre/src/utils/spatial.js +14 -17
- package/sonar-project.properties +39 -2
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +2 -2
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
- package/src/App/components/Logo/Logo.jsx +1 -1
- package/src/App/components/MapButton/MapButton.jsx +217 -41
- package/src/App/components/MapButton/MapButton.module.scss +32 -21
- package/src/App/components/MapButton/MapButton.test.jsx +128 -84
- package/src/App/components/Markers/Markers.jsx +1 -1
- package/src/App/components/Markers/Markers.module.scss +0 -5
- package/src/App/components/Panel/Panel.jsx +75 -45
- package/src/App/components/Panel/Panel.module.scss +18 -24
- package/src/App/components/Panel/Panel.test.jsx +18 -0
- package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
- package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
- package/src/App/components/Tooltip/Tooltip.jsx +4 -2
- package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
- package/src/App/components/Viewport/MapController.jsx +1 -1
- package/src/App/components/Viewport/MapStatus.jsx +1 -1
- package/src/App/components/Viewport/Viewport.jsx +2 -2
- package/src/App/components/Viewport/Viewport.module.scss +2 -2
- package/src/App/controls/keyboardActions.js +3 -2
- package/src/App/controls/keyboardShortcuts.js +4 -2
- package/src/App/hooks/useButtonStateEvaluator.js +58 -45
- package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
- package/src/App/hooks/useCrossHairAPI.js +49 -47
- package/src/App/hooks/useFocusVisible.js +2 -2
- package/src/App/hooks/useInterfaceAPI.js +16 -4
- package/src/App/hooks/useKeyboardHint.js +1 -1
- package/src/App/hooks/useKeyboardShortcuts.js +4 -2
- package/src/App/hooks/useMapAnnouncements.js +34 -32
- package/src/App/hooks/useMapEvents.js +12 -1
- package/src/App/hooks/useMapProviderOverrides.js +3 -3
- package/src/App/hooks/useMapStateSync.js +7 -1
- package/src/App/hooks/useMapURLSync.js +6 -1
- package/src/App/hooks/useMarkersAPI.js +88 -50
- package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
- package/src/App/hooks/useModalPanelBehaviour.js +46 -40
- package/src/App/hooks/useResizeObserver.js +2 -2
- package/src/App/initialiseApp.js +45 -38
- package/src/App/layout/Layout.jsx +6 -4
- package/src/App/layout/layout.module.scss +13 -17
- package/src/App/registry/keyboardShortcutRegistry.js +12 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
- package/src/App/registry/mergeManifests.js +1 -1
- package/src/App/registry/panelRegistry.js +1 -1
- package/src/App/registry/panelRegistry.test.js +30 -1
- package/src/App/registry/pluginRegistry.js +14 -51
- package/src/App/registry/pluginRegistry.test.js +54 -6
- package/src/App/renderer/HtmlElementHost.jsx +186 -0
- package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
- package/src/App/renderer/mapButtons.js +11 -8
- package/src/App/renderer/mapButtons.test.js +6 -4
- package/src/App/renderer/mapControls.js +6 -0
- package/src/App/renderer/mapControls.test.js +10 -2
- package/src/App/renderer/mapPanels.js +39 -26
- package/src/App/renderer/mapPanels.test.js +29 -0
- package/src/App/renderer/pluginWrapper.test.js +28 -0
- package/src/App/renderer/slotHelpers.js +60 -0
- package/src/App/renderer/slotHelpers.test.js +82 -0
- package/src/App/store/AppProvider.jsx +3 -0
- package/src/App/store/AppProvider.test.jsx +40 -0
- package/src/App/store/PluginProvider.jsx +7 -5
- package/src/App/store/appActionsMap.js +25 -1
- package/src/App/store/appActionsMap.test.js +21 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +12 -24
- package/src/App/store/mapReducer.js +1 -1
- package/src/InteractiveMap/InteractiveMap.js +79 -6
- package/src/InteractiveMap/InteractiveMap.test.js +164 -2
- package/src/InteractiveMap/behaviourController.js +12 -2
- package/src/InteractiveMap/behaviourController.test.js +82 -1
- package/src/InteractiveMap/deviceChecker.js +1 -1
- package/src/InteractiveMap/deviceChecker.test.js +2 -2
- package/src/InteractiveMap/historyManager.js +41 -5
- package/src/InteractiveMap/historyManager.test.js +37 -6
- package/src/InteractiveMap/polyfills.js +39 -0
- package/src/InteractiveMap/polyfills.test.js +127 -0
- package/src/config/appConfig.js +5 -5
- package/src/config/appConfig.test.js +107 -42
- package/src/config/defaults.js +1 -0
- package/src/index.umd.js +1 -1
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_dimensions.scss +6 -2
- package/src/services/eventBus.test.js +24 -1
- package/src/test-utils.js +1 -0
- package/src/types.js +5 -0
- package/src/utils/detectBreakpoint.js +78 -77
- package/src/utils/detectBreakpoint.test.js +50 -4
- package/src/utils/detectInterfaceType.js +4 -4
- package/src/utils/getIsFullscreen.js +3 -1
- package/src/utils/getSafeZoneInset.js +23 -7
- package/src/utils/getSafeZoneInset.test.js +18 -0
- package/src/utils/mapStateSync.js +3 -3
- package/src/utils/queryString.js +1 -1
- package/src/utils/stringToKebab.js +1 -1
- package/src/utils/toggleInertElements.js +37 -12
- package/webpack.dev.mjs +3 -4
- package/plugins/interact/src/utils/turfHelpers.js +0 -30
- package/plugins/interact/src/utils/turfHelpers.test.js +0 -30
- package/plugins/search/src/utils/fetchDataset.js +0 -23
- package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
- package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import '../scss/main.scss'
|
|
12
|
+
// Polyfills to ensure entry point works on all devices
|
|
13
|
+
import './polyfills.js'
|
|
12
14
|
import historyManager from './historyManager.js'
|
|
13
15
|
import { parseDataProperties } from './parseDataProperties.js'
|
|
14
16
|
import { checkDeviceSupport } from './deviceChecker.js'
|
|
@@ -22,6 +24,7 @@ import { createInterfaceDetector, getInterfaceType } from '../utils/detectInterf
|
|
|
22
24
|
import { createReverseGeocode } from '../services/reverseGeocode.js'
|
|
23
25
|
import { EVENTS as events } from '../config/events.js'
|
|
24
26
|
import { createEventBus } from '../services/eventBus.js'
|
|
27
|
+
import { toggleInertElements } from '../utils/toggleInertElements.js'
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* Main entry point for the Interactive Map component.
|
|
@@ -33,6 +36,7 @@ export default class InteractiveMap {
|
|
|
33
36
|
_breakpointDetector = null
|
|
34
37
|
_interfaceDetectorCleanup = null
|
|
35
38
|
_hybridBehaviourCleanup = null
|
|
39
|
+
_isHidden = false // tracks if map is hidden but preserved (hybrid mode)
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* Create a new InteractiveMap instance.
|
|
@@ -98,16 +102,39 @@ export default class InteractiveMap {
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
_handleButtonClick (e) {
|
|
101
|
-
this.loadApp()
|
|
102
105
|
history.pushState({ isBack: true }, '', e.currentTarget.getAttribute('href'))
|
|
106
|
+
if (this._isHidden) {
|
|
107
|
+
this.showApp()
|
|
108
|
+
} else {
|
|
109
|
+
this.loadApp()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_removeMapParamFromUrl (href, key) {
|
|
114
|
+
const regex = new RegExp(`[?&]${key}=[^&]*(&|$)`)
|
|
115
|
+
|
|
116
|
+
if (!regex.test(href)) {
|
|
117
|
+
return href
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return href
|
|
121
|
+
.replace(regex, (_, p1) => {
|
|
122
|
+
return p1 === '&' ? '?' : ''
|
|
123
|
+
})
|
|
124
|
+
.replace(/\?$/, '')
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
_handleExitClick () {
|
|
106
|
-
this.
|
|
107
|
-
|
|
128
|
+
if (this.config.preserveStateOnClose) {
|
|
129
|
+
this.hideApp()
|
|
130
|
+
} else {
|
|
131
|
+
this.removeApp()
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
const key = this.config.mapViewParamKey
|
|
109
135
|
const href = location.href
|
|
110
|
-
const newUrl =
|
|
136
|
+
const newUrl = this._removeMapParamFromUrl(href, key)
|
|
137
|
+
|
|
111
138
|
history.replaceState(history.state, '', newUrl)
|
|
112
139
|
}
|
|
113
140
|
|
|
@@ -153,10 +180,10 @@ export default class InteractiveMap {
|
|
|
153
180
|
delete appInstance._root
|
|
154
181
|
|
|
155
182
|
// Only assign properties but don't eventBus methods
|
|
156
|
-
const protectedKeys = ['on', 'off', 'emit']
|
|
183
|
+
const protectedKeys = new Set(['on', 'off', 'emit'])
|
|
157
184
|
|
|
158
185
|
Object.keys(appInstance).forEach(key => {
|
|
159
|
-
if (!protectedKeys.
|
|
186
|
+
if (!protectedKeys.has(key)) {
|
|
160
187
|
this[key] = appInstance[key]
|
|
161
188
|
}
|
|
162
189
|
})
|
|
@@ -190,6 +217,52 @@ export default class InteractiveMap {
|
|
|
190
217
|
this.eventBus.emit(events.MAP_DESTROY, { mapId: this.id })
|
|
191
218
|
}
|
|
192
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Hide the map application without destroying it (preserves state).
|
|
222
|
+
* Used in hybrid mode when resizing below breakpoint.
|
|
223
|
+
*
|
|
224
|
+
* @internal Not intended for end-user use.
|
|
225
|
+
*/
|
|
226
|
+
hideApp () {
|
|
227
|
+
this._isHidden = true
|
|
228
|
+
this.rootEl.style.display = 'none'
|
|
229
|
+
|
|
230
|
+
// Restore inert elements before focusing button
|
|
231
|
+
toggleInertElements({ containerEl: this.rootEl, isFullscreen: false })
|
|
232
|
+
|
|
233
|
+
if (this._openButton) {
|
|
234
|
+
this._openButton.removeAttribute('style')
|
|
235
|
+
this._openButton.focus()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Remove fullscreen classes
|
|
239
|
+
document.documentElement.classList.remove('im-is-fullscreen')
|
|
240
|
+
this.rootEl.classList.remove('im-is-fullscreen')
|
|
241
|
+
|
|
242
|
+
// Reset page title (remove prepended map title)
|
|
243
|
+
const parts = document.title.split(': ')
|
|
244
|
+
if (parts.length > 1) {
|
|
245
|
+
document.title = parts[parts.length - 1]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Show a previously hidden map application.
|
|
251
|
+
* Used in hybrid mode when resizing above breakpoint or clicking button.
|
|
252
|
+
*
|
|
253
|
+
* @internal Not intended for end-user use.
|
|
254
|
+
*/
|
|
255
|
+
showApp () {
|
|
256
|
+
this._isHidden = false
|
|
257
|
+
this.rootEl.style.display = ''
|
|
258
|
+
|
|
259
|
+
if (this._openButton) {
|
|
260
|
+
this._openButton.style.display = 'none'
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
updateDOMState(this)
|
|
264
|
+
}
|
|
265
|
+
|
|
193
266
|
/**
|
|
194
267
|
* Destroy the map instance and clean up all resources.
|
|
195
268
|
*
|
|
@@ -268,20 +268,23 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
268
268
|
expect(() => map.destroy()).not.toThrow()
|
|
269
269
|
})
|
|
270
270
|
|
|
271
|
-
it('_handleExitClick removes app
|
|
271
|
+
it('_handleExitClick removes app when preserveStateOnClose is false', () => {
|
|
272
272
|
const replaceStateSpy = jest.spyOn(history, 'replaceState').mockImplementation(() => {})
|
|
273
273
|
|
|
274
274
|
const map = new InteractiveMap('map', {
|
|
275
275
|
behaviour: 'buttonFirst',
|
|
276
276
|
mapProvider: mapProviderMock,
|
|
277
|
-
mapViewParamKey: 'mv'
|
|
277
|
+
mapViewParamKey: 'mv',
|
|
278
|
+
preserveStateOnClose: false
|
|
278
279
|
})
|
|
279
280
|
|
|
280
281
|
const removeAppSpy = jest.spyOn(map, 'removeApp').mockImplementation(() => {})
|
|
282
|
+
const hideAppSpy = jest.spyOn(map, 'hideApp').mockImplementation(() => {})
|
|
281
283
|
|
|
282
284
|
map._handleExitClick()
|
|
283
285
|
|
|
284
286
|
expect(removeAppSpy).toHaveBeenCalled()
|
|
287
|
+
expect(hideAppSpy).not.toHaveBeenCalled()
|
|
285
288
|
expect(replaceStateSpy).toHaveBeenCalledWith(
|
|
286
289
|
history.state,
|
|
287
290
|
'',
|
|
@@ -289,8 +292,158 @@ describe('InteractiveMap Core Functionality', () => {
|
|
|
289
292
|
)
|
|
290
293
|
|
|
291
294
|
removeAppSpy.mockRestore()
|
|
295
|
+
hideAppSpy.mockRestore()
|
|
292
296
|
replaceStateSpy.mockRestore()
|
|
293
297
|
})
|
|
298
|
+
|
|
299
|
+
it('_handleExitClick hides app when preserveStateOnClose is true', () => {
|
|
300
|
+
const replaceStateSpy = jest.spyOn(history, 'replaceState').mockImplementation(() => {})
|
|
301
|
+
|
|
302
|
+
const map = new InteractiveMap('map', {
|
|
303
|
+
behaviour: 'buttonFirst',
|
|
304
|
+
mapProvider: mapProviderMock,
|
|
305
|
+
mapViewParamKey: 'mv',
|
|
306
|
+
preserveStateOnClose: true
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const removeAppSpy = jest.spyOn(map, 'removeApp').mockImplementation(() => {})
|
|
310
|
+
const hideAppSpy = jest.spyOn(map, 'hideApp').mockImplementation(() => {})
|
|
311
|
+
|
|
312
|
+
map._handleExitClick()
|
|
313
|
+
|
|
314
|
+
expect(hideAppSpy).toHaveBeenCalled()
|
|
315
|
+
expect(removeAppSpy).not.toHaveBeenCalled()
|
|
316
|
+
expect(replaceStateSpy).toHaveBeenCalled()
|
|
317
|
+
|
|
318
|
+
removeAppSpy.mockRestore()
|
|
319
|
+
hideAppSpy.mockRestore()
|
|
320
|
+
replaceStateSpy.mockRestore()
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('_handleButtonClick calls showApp when map is hidden', async () => {
|
|
324
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
325
|
+
map._isHidden = true
|
|
326
|
+
const showAppSpy = jest.spyOn(map, 'showApp').mockImplementation(() => {})
|
|
327
|
+
const loadAppSpy = jest.spyOn(map, 'loadApp').mockResolvedValue()
|
|
328
|
+
const pushStateSpy = jest.spyOn(history, 'pushState').mockImplementation(() => {})
|
|
329
|
+
const fakeEvent = { currentTarget: { getAttribute: jest.fn().mockReturnValue('/?mv=map') } }
|
|
330
|
+
|
|
331
|
+
await openButtonCallback(fakeEvent)
|
|
332
|
+
|
|
333
|
+
expect(showAppSpy).toHaveBeenCalled()
|
|
334
|
+
expect(loadAppSpy).not.toHaveBeenCalled()
|
|
335
|
+
expect(pushStateSpy).toHaveBeenCalled()
|
|
336
|
+
|
|
337
|
+
showAppSpy.mockRestore()
|
|
338
|
+
loadAppSpy.mockRestore()
|
|
339
|
+
pushStateSpy.mockRestore()
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('hideApp sets _isHidden and hides element', () => {
|
|
343
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
344
|
+
map._openButton = mockButtonInstance
|
|
345
|
+
|
|
346
|
+
map.hideApp()
|
|
347
|
+
|
|
348
|
+
expect(map._isHidden).toBe(true)
|
|
349
|
+
expect(map.rootEl.style.display).toBe('none')
|
|
350
|
+
expect(mockButtonInstance.removeAttribute).toHaveBeenCalledWith('style')
|
|
351
|
+
expect(mockButtonInstance.focus).toHaveBeenCalled()
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('hideApp restores document title when it contains a map prefix', () => {
|
|
355
|
+
document.title = 'Map View: Original Page Title'
|
|
356
|
+
|
|
357
|
+
const map = new InteractiveMap('map', {
|
|
358
|
+
behaviour: 'buttonFirst',
|
|
359
|
+
mapProvider: mapProviderMock
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
map._openButton = mockButtonInstance
|
|
363
|
+
map.hideApp()
|
|
364
|
+
|
|
365
|
+
expect(document.title).toBe('Original Page Title')
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it('hideApp works when _openButton is null', () => {
|
|
369
|
+
const map = new InteractiveMap('map', {
|
|
370
|
+
behaviour: 'buttonFirst',
|
|
371
|
+
mapProvider: mapProviderMock
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
map._openButton = null
|
|
375
|
+
map.hideApp()
|
|
376
|
+
|
|
377
|
+
expect(map._isHidden).toBe(true)
|
|
378
|
+
expect(map.rootEl.style.display).toBe('none')
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('showApp sets _isHidden false and shows element', () => {
|
|
382
|
+
const map = new InteractiveMap('map', { behaviour: 'buttonFirst', mapProvider: mapProviderMock })
|
|
383
|
+
map._isHidden = true
|
|
384
|
+
map._openButton = mockButtonInstance
|
|
385
|
+
map.rootEl.style.display = 'none'
|
|
386
|
+
|
|
387
|
+
map.showApp()
|
|
388
|
+
|
|
389
|
+
expect(map._isHidden).toBe(false)
|
|
390
|
+
expect(map.rootEl.style.display).toBe('')
|
|
391
|
+
expect(mockButtonInstance.style.display).toBe('none')
|
|
392
|
+
expect(updateDOMState).toHaveBeenCalledWith(map)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('showApp works when _openButton is null', () => {
|
|
396
|
+
const map = new InteractiveMap('map', {
|
|
397
|
+
behaviour: 'buttonFirst',
|
|
398
|
+
mapProvider: mapProviderMock
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
map._isHidden = true
|
|
402
|
+
map._openButton = null
|
|
403
|
+
map.rootEl.style.display = 'none'
|
|
404
|
+
|
|
405
|
+
map.showApp()
|
|
406
|
+
|
|
407
|
+
expect(map._isHidden).toBe(false)
|
|
408
|
+
expect(map.rootEl.style.display).toBe('')
|
|
409
|
+
expect(updateDOMState).toHaveBeenCalledWith(map)
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
describe('_removeMapParamFromUrl', () => {
|
|
414
|
+
let map
|
|
415
|
+
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
document.body.innerHTML = '<div id="map"></div>'
|
|
418
|
+
map = new InteractiveMap('map', {
|
|
419
|
+
mapProvider: { load: jest.fn() },
|
|
420
|
+
mapViewParamKey: 'mv'
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it('removes param when followed by another param (& branch)', () => {
|
|
425
|
+
const href = 'https://example.com/page?mv=map&foo=1'
|
|
426
|
+
const result = map._removeMapParamFromUrl(href, 'mv')
|
|
427
|
+
expect(result).toBe('https://example.com/page?foo=1')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('removes param when it is the last param (end-of-string branch)', () => {
|
|
431
|
+
const href = 'https://example.com/page?foo=1&mv=map'
|
|
432
|
+
const result = map._removeMapParamFromUrl(href, 'mv')
|
|
433
|
+
expect(result).toBe('https://example.com/page?foo=1')
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it('removes lone param and trailing ?', () => {
|
|
437
|
+
const href = 'https://example.com/page?mv=map'
|
|
438
|
+
const result = map._removeMapParamFromUrl(href, 'mv')
|
|
439
|
+
expect(result).toBe('https://example.com/page')
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
it('returns href unchanged when param does not exist (no-match branch)', () => {
|
|
443
|
+
const href = 'https://example.com/page?foo=1'
|
|
444
|
+
const result = map._removeMapParamFromUrl(href, 'mv')
|
|
445
|
+
expect(result).toBe(href)
|
|
446
|
+
})
|
|
294
447
|
})
|
|
295
448
|
|
|
296
449
|
describe('InteractiveMap Public API Methods', () => {
|
|
@@ -347,4 +500,13 @@ describe('InteractiveMap Public API Methods', () => {
|
|
|
347
500
|
expect(map.eventBus.emit).toHaveBeenCalledWith('app:showpanel', 'panel2')
|
|
348
501
|
expect(map.eventBus.emit).toHaveBeenCalledWith('app:hidepanel', 'panel3')
|
|
349
502
|
})
|
|
503
|
+
|
|
504
|
+
it('delegates toggleButtonState correctly', () => {
|
|
505
|
+
map.toggleButtonState('btn-1', 'disabled', true)
|
|
506
|
+
|
|
507
|
+
expect(map.eventBus.emit).toHaveBeenCalledWith(
|
|
508
|
+
'app:togglebuttonstate',
|
|
509
|
+
{ id: 'btn-1', prop: 'disabled', value: true }
|
|
510
|
+
)
|
|
511
|
+
})
|
|
350
512
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getQueryParam } from '../utils/queryString.js'
|
|
2
2
|
import { isHybridFullscreen } from '../utils/getIsFullscreen.js'
|
|
3
|
+
import { updateDOMState } from './domStateManager.js'
|
|
3
4
|
import defaults from '../config/defaults.js'
|
|
4
5
|
|
|
5
6
|
// -----------------------------------------------------------------------------
|
|
@@ -56,9 +57,18 @@ function setupBehavior (mapInstance) {
|
|
|
56
57
|
|
|
57
58
|
const handleChange = () => {
|
|
58
59
|
if (shouldLoadComponent(mapInstance.config)) {
|
|
59
|
-
mapInstance.
|
|
60
|
+
if (mapInstance._isHidden) {
|
|
61
|
+
mapInstance.showApp()
|
|
62
|
+
} else if (mapInstance._root == null) {
|
|
63
|
+
mapInstance.loadApp()
|
|
64
|
+
} else {
|
|
65
|
+
// Map is showing - update DOM state for fullscreen/inline transition
|
|
66
|
+
updateDOMState(mapInstance)
|
|
67
|
+
}
|
|
68
|
+
} else if (mapInstance._root) {
|
|
69
|
+
mapInstance.hideApp()
|
|
60
70
|
} else {
|
|
61
|
-
|
|
71
|
+
// No action
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { setupBehavior, shouldLoadComponent } from './behaviourController.js'
|
|
6
6
|
import * as queryString from '../utils/queryString.js'
|
|
7
|
+
import { updateDOMState } from './domStateManager.js'
|
|
7
8
|
|
|
8
9
|
jest.mock('../utils/queryString.js')
|
|
10
|
+
jest.mock('./domStateManager.js', () => ({ updateDOMState: jest.fn() }))
|
|
9
11
|
|
|
10
12
|
describe('shouldLoadComponent', () => {
|
|
11
13
|
beforeEach(() => {
|
|
@@ -60,8 +62,12 @@ describe('setupBehavior', () => {
|
|
|
60
62
|
mockMapInstance = {
|
|
61
63
|
config: { hybridWidth: null, maxMobileWidth: 640 },
|
|
62
64
|
_breakpointDetector: mockBreakpointDetector,
|
|
65
|
+
_root: null,
|
|
66
|
+
_isHidden: false,
|
|
63
67
|
loadApp: jest.fn(),
|
|
64
|
-
removeApp: jest.fn()
|
|
68
|
+
removeApp: jest.fn(),
|
|
69
|
+
hideApp: jest.fn(),
|
|
70
|
+
showApp: jest.fn()
|
|
65
71
|
}
|
|
66
72
|
// Default: viewport is wide
|
|
67
73
|
window.matchMedia = jest.fn().mockImplementation(() => ({
|
|
@@ -145,4 +151,79 @@ describe('setupBehavior', () => {
|
|
|
145
151
|
const cleanup = setupBehavior(mockMapInstance)
|
|
146
152
|
expect(cleanup).toBeNull()
|
|
147
153
|
})
|
|
154
|
+
|
|
155
|
+
describe('hybrid behaviour handleChange', () => {
|
|
156
|
+
let handleChange
|
|
157
|
+
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
const mockAddEventListener = jest.fn((event, cb) => { handleChange = cb })
|
|
160
|
+
window.matchMedia = jest.fn().mockImplementation(() => ({
|
|
161
|
+
matches: false,
|
|
162
|
+
addEventListener: mockAddEventListener,
|
|
163
|
+
removeEventListener: jest.fn()
|
|
164
|
+
}))
|
|
165
|
+
mockMapInstance.config = { id: 'test', behaviour: 'hybrid', hybridWidth: null, maxMobileWidth: 640 }
|
|
166
|
+
setupBehavior(mockMapInstance)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('calls showApp when map is hidden and should load', () => {
|
|
170
|
+
mockMapInstance._isHidden = true
|
|
171
|
+
queryString.getQueryParam.mockReturnValue(null) // wide viewport, should load
|
|
172
|
+
|
|
173
|
+
handleChange()
|
|
174
|
+
|
|
175
|
+
expect(mockMapInstance.showApp).toHaveBeenCalled()
|
|
176
|
+
expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('calls loadApp when map has no root and should load', () => {
|
|
180
|
+
mockMapInstance._isHidden = false
|
|
181
|
+
mockMapInstance._root = null
|
|
182
|
+
queryString.getQueryParam.mockReturnValue(null)
|
|
183
|
+
|
|
184
|
+
handleChange()
|
|
185
|
+
|
|
186
|
+
expect(mockMapInstance.loadApp).toHaveBeenCalled()
|
|
187
|
+
expect(mockMapInstance.showApp).not.toHaveBeenCalled()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('calls updateDOMState when map is showing and should load', () => {
|
|
191
|
+
mockMapInstance._isHidden = false
|
|
192
|
+
mockMapInstance._root = {} // has root
|
|
193
|
+
queryString.getQueryParam.mockReturnValue(null)
|
|
194
|
+
|
|
195
|
+
handleChange()
|
|
196
|
+
|
|
197
|
+
expect(updateDOMState).toHaveBeenCalledWith(mockMapInstance)
|
|
198
|
+
expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
|
|
199
|
+
expect(mockMapInstance.showApp).not.toHaveBeenCalled()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('calls hideApp when should not load and has root', () => {
|
|
203
|
+
mockMapInstance._root = {}
|
|
204
|
+
// Simulate narrow viewport where shouldLoadComponent returns false
|
|
205
|
+
window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true }))
|
|
206
|
+
queryString.getQueryParam.mockReturnValue(null)
|
|
207
|
+
|
|
208
|
+
handleChange()
|
|
209
|
+
|
|
210
|
+
expect(mockMapInstance.hideApp).toHaveBeenCalled()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('does nothing when should not load and map has no root', () => {
|
|
214
|
+
mockMapInstance._root = null
|
|
215
|
+
mockMapInstance._isHidden = false
|
|
216
|
+
|
|
217
|
+
// Force shouldLoadComponent === false
|
|
218
|
+
window.matchMedia = jest.fn().mockImplementation(() => ({ matches: true }))
|
|
219
|
+
queryString.getQueryParam.mockReturnValue(null)
|
|
220
|
+
|
|
221
|
+
handleChange()
|
|
222
|
+
|
|
223
|
+
expect(mockMapInstance.hideApp).not.toHaveBeenCalled()
|
|
224
|
+
expect(mockMapInstance.loadApp).not.toHaveBeenCalled()
|
|
225
|
+
expect(mockMapInstance.showApp).not.toHaveBeenCalled()
|
|
226
|
+
expect(updateDOMState).not.toHaveBeenCalled()
|
|
227
|
+
})
|
|
228
|
+
})
|
|
148
229
|
})
|
|
@@ -49,13 +49,13 @@ describe('checkDeviceSupport', () => {
|
|
|
49
49
|
expect(console.log).toHaveBeenCalledWith('WebGL not available')
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
it('logs "No map provider" and returns
|
|
52
|
+
it('logs "No map provider" and returns false if mapProvider is missing', () => {
|
|
53
53
|
config = {
|
|
54
54
|
mapProvider: null,
|
|
55
55
|
deviceNotSupportedText: 'Device not supported'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
expect(checkDeviceSupport(rootEl, config)).
|
|
58
|
+
expect(checkDeviceSupport(rootEl, config)).toBe(false)
|
|
59
59
|
expect(console.log).toHaveBeenCalledWith('No map provider')
|
|
60
60
|
expect(renderError).not.toHaveBeenCalled()
|
|
61
61
|
expect(removeLoadingState).not.toHaveBeenCalled()
|
|
@@ -6,6 +6,40 @@ import defaults from '../config/defaults.js'
|
|
|
6
6
|
// Internal helpers
|
|
7
7
|
// -----------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Opens the map application for a given map instance.
|
|
11
|
+
*
|
|
12
|
+
* If the map instance was previously hidden, it restores the existing app.
|
|
13
|
+
* Otherwise, it loads the app for the first time.
|
|
14
|
+
*
|
|
15
|
+
* @param {MapInstance} mapInstance
|
|
16
|
+
* The map instance whose application should be opened.
|
|
17
|
+
*/
|
|
18
|
+
function openMap (mapInstance) {
|
|
19
|
+
if (mapInstance._isHidden) {
|
|
20
|
+
mapInstance.showApp?.()
|
|
21
|
+
} else {
|
|
22
|
+
mapInstance.loadApp?.()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Closes the map application for a given map instance.
|
|
28
|
+
*
|
|
29
|
+
* Depending on the configuration, the map state is either preserved
|
|
30
|
+
* by hiding the app or fully removed from the DOM.
|
|
31
|
+
*
|
|
32
|
+
* @param {MapInstance} mapInstance
|
|
33
|
+
* The map instance whose application should be closed.
|
|
34
|
+
*/
|
|
35
|
+
function closeMap (mapInstance) {
|
|
36
|
+
if (mapInstance.config.preserveStateOnClose) {
|
|
37
|
+
mapInstance.hideApp?.()
|
|
38
|
+
} else {
|
|
39
|
+
mapInstance.removeApp?.()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
9
43
|
/**
|
|
10
44
|
* Handles the `popstate` event triggered by browser back/forward navigation.
|
|
11
45
|
*
|
|
@@ -28,11 +62,13 @@ function handlePopstate () {
|
|
|
28
62
|
const isHybridVisible = mapInstance.config.behaviour === 'hybrid' && !isHybridFullscreen(mapInstance.config)
|
|
29
63
|
const isOpen = mapInstance.rootEl?.children.length
|
|
30
64
|
|
|
31
|
-
if (shouldBeOpen && !isOpen) {
|
|
32
|
-
mapInstance
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
if (shouldBeOpen && (!isOpen || mapInstance._isHidden)) {
|
|
66
|
+
openMap(mapInstance)
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!shouldBeOpen && isOpen && !isHybridVisible) {
|
|
71
|
+
closeMap(mapInstance)
|
|
36
72
|
}
|
|
37
73
|
}
|
|
38
74
|
}
|
|
@@ -13,19 +13,25 @@ describe('historyManager', () => {
|
|
|
13
13
|
beforeEach(() => {
|
|
14
14
|
component1 = {
|
|
15
15
|
id: 'map',
|
|
16
|
-
config: { behaviour: 'buttonFirst', hybridWidth: null, maxMobileWidth: 640 },
|
|
16
|
+
config: { behaviour: 'buttonFirst', hybridWidth: null, maxMobileWidth: 640, preserveStateOnClose: false },
|
|
17
17
|
rootEl: document.createElement('div'),
|
|
18
18
|
loadApp: jest.fn(),
|
|
19
19
|
removeApp: jest.fn(),
|
|
20
|
-
|
|
20
|
+
hideApp: jest.fn(),
|
|
21
|
+
showApp: jest.fn(),
|
|
22
|
+
openButton: { focus: jest.fn() },
|
|
23
|
+
_isHidden: false
|
|
21
24
|
}
|
|
22
25
|
component2 = {
|
|
23
26
|
id: 'list',
|
|
24
|
-
config: { behaviour: 'hybrid', hybridWidth: null, maxMobileWidth: 640 },
|
|
27
|
+
config: { behaviour: 'hybrid', hybridWidth: null, maxMobileWidth: 640, preserveStateOnClose: false },
|
|
25
28
|
rootEl: document.createElement('div'),
|
|
26
29
|
loadApp: jest.fn(),
|
|
27
30
|
removeApp: jest.fn(),
|
|
28
|
-
|
|
31
|
+
hideApp: jest.fn(),
|
|
32
|
+
showApp: jest.fn(),
|
|
33
|
+
openButton: { focus: jest.fn() },
|
|
34
|
+
_isHidden: false
|
|
29
35
|
}
|
|
30
36
|
popstateEvent = new PopStateEvent('popstate')
|
|
31
37
|
jest.clearAllMocks()
|
|
@@ -63,7 +69,8 @@ describe('historyManager', () => {
|
|
|
63
69
|
expect(component1.loadApp).not.toHaveBeenCalled()
|
|
64
70
|
})
|
|
65
71
|
|
|
66
|
-
it('removes component
|
|
72
|
+
it('removes component when view param does not match and preserveStateOnClose is false', () => {
|
|
73
|
+
component1.config.preserveStateOnClose = false
|
|
67
74
|
component1.rootEl.appendChild(document.createElement('div'))
|
|
68
75
|
historyManager.register(component1)
|
|
69
76
|
queryString.getQueryParam.mockReturnValue(null)
|
|
@@ -71,7 +78,19 @@ describe('historyManager', () => {
|
|
|
71
78
|
window.dispatchEvent(popstateEvent)
|
|
72
79
|
|
|
73
80
|
expect(component1.removeApp).toHaveBeenCalled()
|
|
74
|
-
expect(component1.
|
|
81
|
+
expect(component1.hideApp).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('hides component when view param does not match and preserveStateOnClose is true', () => {
|
|
85
|
+
component1.config.preserveStateOnClose = true
|
|
86
|
+
component1.rootEl.appendChild(document.createElement('div'))
|
|
87
|
+
historyManager.register(component1)
|
|
88
|
+
queryString.getQueryParam.mockReturnValue(null)
|
|
89
|
+
|
|
90
|
+
window.dispatchEvent(popstateEvent)
|
|
91
|
+
|
|
92
|
+
expect(component1.hideApp).toHaveBeenCalled()
|
|
93
|
+
expect(component1.removeApp).not.toHaveBeenCalled()
|
|
75
94
|
})
|
|
76
95
|
|
|
77
96
|
it('does not remove hybrid component when viewport is wide (inline mode)', () => {
|
|
@@ -87,6 +106,7 @@ describe('historyManager', () => {
|
|
|
87
106
|
})
|
|
88
107
|
|
|
89
108
|
it('removes hybrid component when viewport is narrow and view does not match', () => {
|
|
109
|
+
component2.config.preserveStateOnClose = false
|
|
90
110
|
component2.rootEl.appendChild(document.createElement('div'))
|
|
91
111
|
historyManager.register(component2)
|
|
92
112
|
queryString.getQueryParam.mockReturnValue(null)
|
|
@@ -98,6 +118,17 @@ describe('historyManager', () => {
|
|
|
98
118
|
expect(component2.removeApp).toHaveBeenCalled()
|
|
99
119
|
})
|
|
100
120
|
|
|
121
|
+
it('calls showApp when view param matches and component is hidden', () => {
|
|
122
|
+
component1._isHidden = true
|
|
123
|
+
historyManager.register(component1)
|
|
124
|
+
queryString.getQueryParam.mockReturnValue('map')
|
|
125
|
+
|
|
126
|
+
window.dispatchEvent(popstateEvent)
|
|
127
|
+
|
|
128
|
+
expect(component1.showApp).toHaveBeenCalled()
|
|
129
|
+
expect(component1.loadApp).not.toHaveBeenCalled()
|
|
130
|
+
})
|
|
131
|
+
|
|
101
132
|
it('uses hybridWidth for media query when provided', () => {
|
|
102
133
|
component2.config.hybridWidth = 768
|
|
103
134
|
component2.rootEl.appendChild(document.createElement('div'))
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// crypto.randomUUID
|
|
2
|
+
if (typeof crypto !== 'undefined' && !crypto.randomUUID) {
|
|
3
|
+
let last = 0
|
|
4
|
+
crypto.randomUUID = () => {
|
|
5
|
+
last = Math.max(Date.now(), last + 1)
|
|
6
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c, i) => {
|
|
7
|
+
const v = i < 12 ? Number.parseInt(last.toString(16).padStart(12, '0')[i], 16) : Math.random() * 16 | 0 // NOSONAR
|
|
8
|
+
return (c === 'x' ? v : (v & 0x3 | 0x8)).toString(16)
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// AbortSignal.throwIfAborted
|
|
14
|
+
const needsThrowIfAborted = typeof AbortController !== 'undefined' &&
|
|
15
|
+
!Object.getPrototypeOf(new AbortController().signal).throwIfAborted
|
|
16
|
+
|
|
17
|
+
if (needsThrowIfAborted) {
|
|
18
|
+
const signalProto = Object.getPrototypeOf(new AbortController().signal)
|
|
19
|
+
signalProto.throwIfAborted = function () {
|
|
20
|
+
if (this.aborted) {
|
|
21
|
+
const err = new Error('The operation was aborted.')
|
|
22
|
+
err.name = 'AbortError'
|
|
23
|
+
throw err
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Inject polyfill into web workers created from blob URLs (e.g. MapLibre GL)
|
|
29
|
+
// Workers have their own global scope so main thread polyfills don't apply
|
|
30
|
+
if (needsThrowIfAborted && typeof URL !== 'undefined' && URL.createObjectURL) {
|
|
31
|
+
const _createObjectURL = URL.createObjectURL.bind(URL)
|
|
32
|
+
URL.createObjectURL = (blob) => {
|
|
33
|
+
if (blob instanceof Blob && blob.type === 'text/javascript') {
|
|
34
|
+
const p = 'if(typeof AbortController!=="undefined"){var _p=Object.getPrototypeOf(new AbortController().signal);if(!_p.throwIfAborted){_p.throwIfAborted=function(){if(this.aborted){var e=new Error("The operation was aborted.");e.name="AbortError";throw e}}}}\n'
|
|
35
|
+
blob = new Blob([p, blob], { type: 'text/javascript' })
|
|
36
|
+
}
|
|
37
|
+
return _createObjectURL(blob)
|
|
38
|
+
}
|
|
39
|
+
}
|