@defra/forms-engine-plugin 4.0.41 → 4.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.map +1 -1
  3. package/.public/javascripts/shared.min.js +1 -1
  4. package/.public/javascripts/shared.min.js.map +1 -1
  5. package/.public/stylesheets/application.min.css +2 -2
  6. package/.public/stylesheets/application.min.css.map +1 -1
  7. package/.server/client/javascripts/location-map.d.ts +93 -0
  8. package/.server/client/javascripts/location-map.js +749 -0
  9. package/.server/client/javascripts/location-map.js.map +1 -0
  10. package/.server/client/javascripts/shared.d.ts +4 -0
  11. package/.server/client/javascripts/shared.js +5 -0
  12. package/.server/client/javascripts/shared.js.map +1 -1
  13. package/.server/client/stylesheets/_location-fields.scss +11 -0
  14. package/.server/client/stylesheets/application.scss +0 -1
  15. package/.server/client/stylesheets/shared.scss +1 -0
  16. package/.server/config/index.js +1 -1
  17. package/.server/config/index.js.map +1 -1
  18. package/.server/server/plugins/engine/configureEnginePlugin.d.ts +1 -1
  19. package/.server/server/plugins/engine/configureEnginePlugin.js +4 -2
  20. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  21. package/.server/server/plugins/engine/options.js +2 -1
  22. package/.server/server/plugins/engine/options.js.map +1 -1
  23. package/.server/server/plugins/engine/plugin.js +14 -1
  24. package/.server/server/plugins/engine/plugin.js.map +1 -1
  25. package/.server/server/plugins/engine/types.d.ts +1 -0
  26. package/.server/server/plugins/engine/types.js.map +1 -1
  27. package/.server/server/plugins/engine/views/components/_location-field-base.html +1 -1
  28. package/.server/server/plugins/engine/views/components/osgridreffield.html +4 -2
  29. package/.server/server/plugins/map/index.d.ts +7 -0
  30. package/.server/server/plugins/map/index.js +20 -0
  31. package/.server/server/plugins/map/index.js.map +1 -0
  32. package/.server/server/plugins/map/routes/get-os-token.d.ts +6 -0
  33. package/.server/server/plugins/map/routes/get-os-token.js +41 -0
  34. package/.server/server/plugins/map/routes/get-os-token.js.map +1 -0
  35. package/.server/server/plugins/map/routes/get-os-token.test.js +49 -0
  36. package/.server/server/plugins/map/routes/get-os-token.test.js.map +1 -0
  37. package/.server/server/plugins/map/routes/index.d.ts +10 -0
  38. package/.server/server/plugins/map/routes/index.js +172 -0
  39. package/.server/server/plugins/map/routes/index.js.map +1 -0
  40. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark.json +690 -0
  41. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark.png +0 -0
  42. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark@2x.json +690 -0
  43. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark@2x.png +0 -0
  44. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale.json +690 -0
  45. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale.png +0 -0
  46. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale@2x.json +690 -0
  47. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale@2x.png +0 -0
  48. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite.json +690 -0
  49. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite.png +0 -0
  50. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite@2x.json +690 -0
  51. package/.server/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite@2x.png +0 -0
  52. package/.server/server/plugins/map/routes/vts/OS_VTS_3857_Black_and_White.json +7858 -0
  53. package/.server/server/plugins/map/routes/vts/OS_VTS_3857_Dark.json +7669 -0
  54. package/.server/server/plugins/map/routes/vts/OS_VTS_3857_Outdoor.json +7653 -0
  55. package/.server/server/plugins/map/routes/vts/README.md +5 -0
  56. package/.server/server/plugins/map/service.d.ts +14 -0
  57. package/.server/server/plugins/map/service.js +76 -0
  58. package/.server/server/plugins/map/service.js.map +1 -0
  59. package/.server/server/plugins/map/service.test.js +120 -0
  60. package/.server/server/plugins/map/service.test.js.map +1 -0
  61. package/.server/server/plugins/map/test/__stubs__/find.d.ts +3 -0
  62. package/.server/server/plugins/map/test/__stubs__/find.js +216 -0
  63. package/.server/server/plugins/map/test/__stubs__/find.js.map +1 -0
  64. package/.server/server/plugins/map/test/__stubs__/nearest.d.ts +37 -0
  65. package/.server/server/plugins/map/test/__stubs__/nearest.js +38 -0
  66. package/.server/server/plugins/map/test/__stubs__/nearest.js.map +1 -0
  67. package/.server/server/plugins/map/types.d.ts +233 -0
  68. package/.server/server/plugins/map/types.js +121 -0
  69. package/.server/server/plugins/map/types.js.map +1 -0
  70. package/.server/server/types.d.ts +1 -0
  71. package/.server/server/types.js.map +1 -1
  72. package/package.json +3 -1
  73. package/src/client/javascripts/location-map.js +774 -0
  74. package/src/client/javascripts/shared.js +4 -0
  75. package/src/client/stylesheets/_location-fields.scss +11 -0
  76. package/src/client/stylesheets/application.scss +0 -1
  77. package/src/client/stylesheets/shared.scss +1 -0
  78. package/src/config/index.ts +1 -1
  79. package/src/server/plugins/engine/configureEnginePlugin.ts +4 -2
  80. package/src/server/plugins/engine/options.js +2 -1
  81. package/src/server/plugins/engine/plugin.ts +14 -1
  82. package/src/server/plugins/engine/types.ts +1 -0
  83. package/src/server/plugins/engine/views/components/_location-field-base.html +1 -1
  84. package/src/server/plugins/engine/views/components/osgridreffield.html +4 -2
  85. package/src/server/plugins/map/index.js +19 -0
  86. package/src/server/plugins/map/routes/get-os-token.js +41 -0
  87. package/src/server/plugins/map/routes/get-os-token.test.js +55 -0
  88. package/src/server/plugins/map/routes/index.js +192 -0
  89. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark.json +690 -0
  90. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark.png +0 -0
  91. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark@2x.json +690 -0
  92. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/dark@2x.png +0 -0
  93. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale.json +690 -0
  94. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale.png +0 -0
  95. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale@2x.json +690 -0
  96. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/greyscale@2x.png +0 -0
  97. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite.json +690 -0
  98. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite.png +0 -0
  99. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite@2x.json +690 -0
  100. package/src/server/plugins/map/routes/vts/OS_VTS_3857/resources/sprites/sprite@2x.png +0 -0
  101. package/src/server/plugins/map/routes/vts/OS_VTS_3857_Black_and_White.json +7858 -0
  102. package/src/server/plugins/map/routes/vts/OS_VTS_3857_Dark.json +7669 -0
  103. package/src/server/plugins/map/routes/vts/OS_VTS_3857_Outdoor.json +7653 -0
  104. package/src/server/plugins/map/routes/vts/README.md +5 -0
  105. package/src/server/plugins/map/service.js +84 -0
  106. package/src/server/plugins/map/service.test.js +144 -0
  107. package/src/server/plugins/map/test/__stubs__/find.js +271 -0
  108. package/src/server/plugins/map/test/__stubs__/nearest.js +46 -0
  109. package/src/server/plugins/map/types.js +120 -0
  110. package/src/server/types.ts +1 -0
@@ -0,0 +1,749 @@
1
+ // @ts-expect-error - no types
2
+ import OsGridRef, { LatLon } from 'geodesy/osgridref.js';
3
+
4
+ /**
5
+ * Converts lat long to easting and northing
6
+ * @param {object} param
7
+ * @param {number} param.lat
8
+ * @param {number} param.long
9
+ * @returns {{ easting: number, northing: number }}
10
+ */
11
+ function latLongToEastingNorthing({
12
+ lat,
13
+ long
14
+ }) {
15
+ const point = new LatLon(lat, long);
16
+ return point.toOsGrid();
17
+ }
18
+
19
+ /**
20
+ * Converts easting and northing to lat long
21
+ * @param {object} param
22
+ * @param {number} param.easting
23
+ * @param {number} param.northing
24
+ * @returns {{ lat: number, long: number }}
25
+ */
26
+ function eastingNorthingToLatLong({
27
+ easting,
28
+ northing
29
+ }) {
30
+ const point = new OsGridRef(easting, northing);
31
+ const latLong = point.toLatLon();
32
+ return {
33
+ lat: latLong.latitude,
34
+ long: latLong.longitude
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Converts lat long to an ordnance survey grid reference
40
+ * @param {object} param
41
+ * @param {number} param.lat
42
+ * @param {number} param.long
43
+ * @returns {string}
44
+ */
45
+ function latLongToOsGridRef({
46
+ lat,
47
+ long
48
+ }) {
49
+ const point = new LatLon(lat, long);
50
+ return point.toOsGrid().toString();
51
+ }
52
+
53
+ /**
54
+ * Converts an ordnance survey grid reference to lat long
55
+ * @param {string} osGridRef
56
+ * @returns {{ lat: number, long: number }}
57
+ */
58
+ function osGridRefToLatLong(osGridRef) {
59
+ const point = OsGridRef.parse(osGridRef);
60
+ const latLong = point.toLatLon();
61
+ return {
62
+ lat: latLong.latitude,
63
+ long: latLong.longitude
64
+ };
65
+ }
66
+
67
+ // Center of UK
68
+ const DEFAULT_LAT = 53.825564;
69
+ const DEFAULT_LONG = -2.421975;
70
+
71
+ /** @type {InteractiveMapInitConfig} */
72
+ const defaultConfig = {
73
+ zoom: '6',
74
+ center: [DEFAULT_LONG, DEFAULT_LAT]
75
+ };
76
+ const COMPANY_SYMBOL_CODE = 169;
77
+ const LOCATION_FIELD_SELECTOR = 'input.govuk-input';
78
+ const EVENTS = {
79
+ interactMarkerChange: 'interact:markerchange'
80
+ };
81
+ const defaultData = {
82
+ VTS_OUTDOOR_URL: '/api/maps/vts/OS_VTS_3857_Outdoor.json',
83
+ VTS_DARK_URL: '/api/maps/vts/OS_VTS_3857_Dark.json',
84
+ VTS_BLACK_AND_WHITE_URL: '/api/maps/vts/OS_VTS_3857_Black_and_White.json'
85
+ };
86
+
87
+ /**
88
+ * Make a form submit handler that only allows submissions from allowed buttons
89
+ * @param {HTMLButtonElement[]} buttons - the form buttons to allow submissions
90
+ */
91
+ export function formSubmitFactory(buttons) {
92
+ /**
93
+ * The submit handler
94
+ * @param {SubmitEvent} e
95
+ */
96
+ const onFormSubmit = function (e) {
97
+ if (!(e.submitter instanceof HTMLButtonElement) || !buttons.includes(e.submitter)) {
98
+ e.preventDefault();
99
+ }
100
+ };
101
+ return onFormSubmit;
102
+ }
103
+
104
+ /**
105
+ * Initialise location maps
106
+ * @param {Partial<MapsEnvironmentConfig>} config - the map configuration
107
+ */
108
+ export function initMaps(config = {}) {
109
+ const {
110
+ assetPath = '/assets',
111
+ apiPath = '/form/api',
112
+ data = defaultData
113
+ } = config;
114
+ const locations = document.querySelectorAll('.app-location-field');
115
+
116
+ // TODO: Fix this in `interactive-map`
117
+ // If there are location components on the page fix up the main form submit
118
+ // handler so it doesn't fire when using the integrated map search feature
119
+ if (locations.length) {
120
+ const form = document.querySelector('form');
121
+ if (form === null) {
122
+ return;
123
+ }
124
+ const buttons = Array.from(form.querySelectorAll('button'));
125
+ form.addEventListener('submit', formSubmitFactory(buttons), false);
126
+ }
127
+ locations.forEach((location, index) => {
128
+ processLocation({
129
+ assetPath,
130
+ apiPath,
131
+ data
132
+ }, location, index);
133
+ });
134
+ }
135
+
136
+ /**
137
+ * OS API request proxy factory
138
+ * @param {string} apiPath - the root API path
139
+ */
140
+ export function makeTileRequestTransformer(apiPath) {
141
+ /**
142
+ * Proxy OS API requests via our server
143
+ * @param {string} url - the request URL
144
+ * @param {string} resourceType - the resource type
145
+ */
146
+ return function transformTileRequest(url, resourceType) {
147
+ if (url.startsWith('https://api.os.uk')) {
148
+ if (resourceType === 'Tile') {
149
+ return {
150
+ url: url.replace('https://api.os.uk/maps/vector/v1/vts', `${window.location.origin}${apiPath}`),
151
+ headers: {}
152
+ };
153
+ }
154
+ if (resourceType !== 'Style') {
155
+ return {
156
+ url: `${apiPath}/map-proxy?url=${encodeURIComponent(url)}`,
157
+ headers: {}
158
+ };
159
+ }
160
+ }
161
+ const spritesPath = 'https://raw.githubusercontent.com/OrdnanceSurvey/OS-Vector-Tile-API-Stylesheets/main';
162
+
163
+ // Proxy sprite requests
164
+ if (url.startsWith(spritesPath)) {
165
+ const path = url.substring(spritesPath.length);
166
+ return {
167
+ url: `${apiPath}/maps/vts${path}`,
168
+ headers: {}
169
+ };
170
+ }
171
+ return {
172
+ url,
173
+ headers: {}
174
+ };
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Processes a location field to add map capability
180
+ * @param {MapsEnvironmentConfig} config - the location field element
181
+ * @param {Element} location - the location field element
182
+ * @param {*} index - the 0-based index
183
+ */
184
+ function processLocation(config, location, index) {
185
+ if (!(location instanceof HTMLDivElement)) {
186
+ return;
187
+ }
188
+ const locationInputs = location.querySelector('.app-location-field-inputs');
189
+ if (!(locationInputs instanceof HTMLDivElement)) {
190
+ return;
191
+ }
192
+ const locationType = location.dataset.locationtype;
193
+
194
+ // Check for support
195
+ const supportedLocations = ['latlongfield', 'eastingnorthingfield', 'osgridreffield'];
196
+ if (!locationType || !supportedLocations.includes(locationType)) {
197
+ return;
198
+ }
199
+ const mapContainer = document.createElement('div');
200
+ const mapId = `map_${index}`;
201
+ mapContainer.setAttribute('id', mapId);
202
+ mapContainer.setAttribute('class', 'map-container');
203
+ const initConfig = getInitMapConfig(location) ?? defaultConfig;
204
+ locationInputs.after(mapContainer);
205
+ const {
206
+ map,
207
+ interactPlugin
208
+ } = createMap(mapId, initConfig, config);
209
+ map.on('map:ready',
210
+ /**
211
+ * Callback function which fires when the map is ready
212
+ * @param {object} e - the event
213
+ * @param {MapLibreMap} e.map - the map provider instance
214
+ */
215
+ function onMapReady(e) {
216
+ switch (locationType) {
217
+ case 'latlongfield':
218
+ bindLatLongField(location, map, e.map);
219
+ break;
220
+ case 'eastingnorthingfield':
221
+ bindEastingNorthingField(location, map, e.map);
222
+ break;
223
+ case 'osgridreffield':
224
+ bindOsGridRefField(location, map, e.map);
225
+ break;
226
+ default:
227
+ throw new Error('Not implemented');
228
+ }
229
+
230
+ // Add info panel
231
+ map.addPanel('info', {
232
+ showLabel: true,
233
+ label: 'How to use the map',
234
+ mobile: {
235
+ slot: 'bottom',
236
+ initiallyOpen: true,
237
+ dismissable: true,
238
+ modal: false
239
+ },
240
+ tablet: {
241
+ slot: 'bottom',
242
+ initiallyOpen: true,
243
+ dismissable: true,
244
+ modal: false
245
+ },
246
+ desktop: {
247
+ slot: 'bottom',
248
+ initiallyOpen: true,
249
+ dismissable: true,
250
+ modal: false
251
+ },
252
+ html: 'If using a map click on a point to update the location.<br><br>If using a keyboard, navigate to the point, centering the crosshair at the location and press enter.'
253
+ });
254
+
255
+ // Enable the interact plugin
256
+ interactPlugin.enable();
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Create a Defra map instance
262
+ * @param {string} mapId - the map id
263
+ * @param {InteractiveMapInitConfig} initConfig - the map initial configuration
264
+ * @param {MapsEnvironmentConfig} mapsConfig - the map environment params
265
+ */
266
+ function createMap(mapId, initConfig, mapsConfig) {
267
+ const {
268
+ assetPath,
269
+ apiPath,
270
+ data = defaultData
271
+ } = mapsConfig;
272
+ const logoAltText = 'Ordnance survey logo';
273
+
274
+ // @ts-expect-error - Defra namespace currently comes from UMD support files
275
+ const defra = window.defra;
276
+ const interactPlugin = defra.interactPlugin({
277
+ dataLayers: [],
278
+ markerColor: {
279
+ outdoor: '#ff0000',
280
+ dark: '#00ff00'
281
+ },
282
+ interactionMode: 'marker',
283
+ multiSelect: false
284
+ });
285
+
286
+ /** @type {InteractiveMap} */
287
+ const map = new defra.InteractiveMap(mapId, {
288
+ ...initConfig,
289
+ mapProvider: defra.maplibreProvider(),
290
+ reverseGeocodeProvider: defra.openNamesProvider({
291
+ url: `${apiPath}/reverse-geocode-proxy?easting={easting}&northing={northing}`
292
+ }),
293
+ behaviour: 'inline',
294
+ minZoom: 6,
295
+ maxZoom: 18,
296
+ containerHeight: '400px',
297
+ enableZoomControls: true,
298
+ transformRequest: makeTileRequestTransformer(apiPath),
299
+ plugins: [defra.mapStylesPlugin({
300
+ mapStyles: [{
301
+ id: 'outdoor',
302
+ label: 'Outdoor',
303
+ url: data.VTS_OUTDOOR_URL,
304
+ thumbnail: `${assetPath}/interactive-map/assets/images/outdoor-map-thumb.jpg`,
305
+ logo: `${assetPath}/interactive-map/assets/images/os-logo.svg`,
306
+ logoAltText,
307
+ attribution: `Contains OS data ${String.fromCodePoint(COMPANY_SYMBOL_CODE)} Crown copyright and database rights ${new Date().getFullYear()}`,
308
+ backgroundColor: '#f5f5f0'
309
+ }, {
310
+ id: 'dark',
311
+ label: 'Dark',
312
+ url: data.VTS_DARK_URL,
313
+ mapColorScheme: 'dark',
314
+ appColorScheme: 'dark',
315
+ thumbnail: `${assetPath}/interactive-map/assets/images/dark-map-thumb.jpg`,
316
+ logo: `${assetPath}/interactive-map/assets/images/os-logo-white.svg`,
317
+ logoAltText,
318
+ attribution: `Contains OS data ${String.fromCodePoint(COMPANY_SYMBOL_CODE)} Crown copyright and database rights ${new Date().getFullYear()}`
319
+ }, {
320
+ id: 'black-and-white',
321
+ label: 'Black/White',
322
+ url: data.VTS_BLACK_AND_WHITE_URL,
323
+ thumbnail: `${assetPath}/interactive-map/assets/images/black-and-white-map-thumb.jpg`,
324
+ logo: `${assetPath}/interactive-map/assets/images/os-logo-black.svg`,
325
+ logoAltText,
326
+ attribution: `Contains OS data ${String.fromCodePoint(COMPANY_SYMBOL_CODE)} Crown copyright and database rights ${new Date().getFullYear()}`
327
+ }]
328
+ }), interactPlugin, defra.searchPlugin({
329
+ osNamesURL: `${apiPath}/geocode-proxy?query={query}`,
330
+ width: '300px',
331
+ showMarker: false
332
+ }), defra.scaleBarPlugin({
333
+ units: 'metric'
334
+ })]
335
+ });
336
+ return {
337
+ map,
338
+ interactPlugin
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Gets initial map config for a location field
344
+ * @param {HTMLDivElement} locationField - the location field element
345
+ */
346
+ function getInitMapConfig(locationField) {
347
+ const locationType = locationField.dataset.locationtype;
348
+ switch (locationType) {
349
+ case 'latlongfield':
350
+ return getInitLatLongMapConfig(locationField);
351
+ case 'eastingnorthingfield':
352
+ return getInitEastingNorthingMapConfig(locationField);
353
+ case 'osgridreffield':
354
+ return getInitOsGridRefMapConfig(locationField);
355
+ default:
356
+ throw new Error('Not implemented');
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Validates lat and long is numeric and within UK bounds
362
+ * @param {string} strLat - the latitude string
363
+ * @param {string} strLong - the longitude string
364
+ * @returns {{ valid: false } | { valid: true, value: { lat: number, long: number } }}
365
+ */
366
+ function validateLatLong(strLat, strLong) {
367
+ const lat = strLat.trim() && Number(strLat.trim());
368
+ const long = strLong.trim() && Number(strLong.trim());
369
+ if (!lat || !long) {
370
+ return {
371
+ valid: false
372
+ };
373
+ }
374
+ const latMin = 49.85;
375
+ const latMax = 60.859;
376
+ const longMin = -13.687;
377
+ const longMax = 1.767;
378
+ const latInBounds = lat >= latMin && lat <= latMax;
379
+ const longInBounds = long >= longMin && long <= longMax;
380
+ if (!latInBounds || !longInBounds) {
381
+ return {
382
+ valid: false
383
+ };
384
+ }
385
+ return {
386
+ valid: true,
387
+ value: {
388
+ lat,
389
+ long
390
+ }
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Validates easting and northing is numeric and within UK bounds
396
+ * @param {string} strEasting - the easting string
397
+ * @param {string} strNorthing - the northing string
398
+ * @returns {{ valid: false } | { valid: true, value: { easting: number, northing: number } }}
399
+ */
400
+ function validateEastingNorthing(strEasting, strNorthing) {
401
+ const easting = strEasting.trim() && Number(strEasting.trim());
402
+ const northing = strNorthing.trim() && Number(strNorthing.trim());
403
+ if (!easting || !northing) {
404
+ return {
405
+ valid: false
406
+ };
407
+ }
408
+ const eastingMin = 0;
409
+ const eastingMax = 700000;
410
+ const northingMin = 0;
411
+ const northingMax = 1300000;
412
+ const latInBounds = easting >= eastingMin && easting <= eastingMax;
413
+ const longInBounds = northing >= northingMin && northing <= northingMax;
414
+ if (!latInBounds || !longInBounds) {
415
+ return {
416
+ valid: false
417
+ };
418
+ }
419
+ return {
420
+ valid: true,
421
+ value: {
422
+ easting,
423
+ northing
424
+ }
425
+ };
426
+ }
427
+
428
+ /**
429
+ * Validates OS grid reference is correct
430
+ * @param {string} osGridRef - the OsGridRef
431
+ * @returns {{ valid: false } | { valid: true, value: string }}
432
+ */
433
+ function validateOsGridRef(osGridRef) {
434
+ if (!osGridRef) {
435
+ return {
436
+ valid: false
437
+ };
438
+ }
439
+ const pattern = /^((([sS]|[nN])[a-hA-Hj-zJ-Z])|(([tT]|[oO])[abfglmqrvwABFGLMQRVW])|([hH][l-zL-Z])|([jJ][lmqrvwLMQRVW]))\s?(([0-9]{3})\s?([0-9]{3})|([0-9]{4})\s?([0-9]{4})|([0-9]{5})\s?([0-9]{5}))$/;
440
+ const match = pattern.exec(osGridRef);
441
+ if (match === null) {
442
+ return {
443
+ valid: false
444
+ };
445
+ }
446
+ return {
447
+ valid: true,
448
+ value: match[0]
449
+ };
450
+ }
451
+
452
+ /**
453
+ * Gets the inputs for a latlong location field
454
+ * @param {HTMLDivElement} locationField - the latlong location field element
455
+ */
456
+ function getLatLongInputs(locationField) {
457
+ const inputs = locationField.querySelectorAll(LOCATION_FIELD_SELECTOR);
458
+ if (inputs.length !== 2) {
459
+ throw new Error('Expected 2 inputs for lat and long');
460
+ }
461
+ const latInput = /** @type {HTMLInputElement} */inputs[0];
462
+ const longInput = /** @type {HTMLInputElement} */inputs[1];
463
+ return {
464
+ latInput,
465
+ longInput
466
+ };
467
+ }
468
+
469
+ /**
470
+ * Gets the inputs for a easting/northing location field
471
+ * @param {HTMLDivElement} locationField - the eastingnorthing location field element
472
+ */
473
+ function getEastingNorthingInputs(locationField) {
474
+ const inputs = locationField.querySelectorAll(LOCATION_FIELD_SELECTOR);
475
+ if (inputs.length !== 2) {
476
+ throw new Error('Expected 2 inputs for easting and northing');
477
+ }
478
+ const eastingInput = /** @type {HTMLInputElement} */inputs[0];
479
+ const northingInput = /** @type {HTMLInputElement} */inputs[1];
480
+ return {
481
+ eastingInput,
482
+ northingInput
483
+ };
484
+ }
485
+
486
+ /**
487
+ * Gets the input for a OS grid reference location field
488
+ * @param {HTMLDivElement} locationField - the osgridref location field element
489
+ */
490
+ function getOsGridRefInput(locationField) {
491
+ const input = locationField.querySelector(LOCATION_FIELD_SELECTOR);
492
+ if (input === null) {
493
+ throw new Error('Expected 1 input for osgridref');
494
+ }
495
+ return /** @type {HTMLInputElement} */input;
496
+ }
497
+
498
+ /**
499
+ * Get the initial map config for a center point
500
+ * @param {MapCenter} center - the point
501
+ */
502
+ function getInitMapCenterConfig(center) {
503
+ return {
504
+ zoom: '16',
505
+ center,
506
+ markers: [{
507
+ id: 'location',
508
+ coords: center
509
+ }]
510
+ };
511
+ }
512
+
513
+ /**
514
+ * Gets initial map config for a latlong location field
515
+ * @param {HTMLDivElement} locationField - the latlong location field element
516
+ * @returns {InteractiveMapInitConfig | undefined}
517
+ */
518
+ function getInitLatLongMapConfig(locationField) {
519
+ const {
520
+ latInput,
521
+ longInput
522
+ } = getLatLongInputs(locationField);
523
+ const result = validateLatLong(latInput.value, longInput.value);
524
+ if (!result.valid) {
525
+ return undefined;
526
+ }
527
+
528
+ /** @type {MapCenter} */
529
+ const center = [result.value.long, result.value.lat];
530
+ return getInitMapCenterConfig(center);
531
+ }
532
+
533
+ /**
534
+ * Gets initial map config for a easting/northing location field
535
+ * @param {HTMLDivElement} locationField - the eastingnorthing location field element
536
+ * @returns {InteractiveMapInitConfig | undefined}
537
+ */
538
+ function getInitEastingNorthingMapConfig(locationField) {
539
+ const {
540
+ eastingInput,
541
+ northingInput
542
+ } = getEastingNorthingInputs(locationField);
543
+ const result = validateEastingNorthing(eastingInput.value, northingInput.value);
544
+ if (!result.valid) {
545
+ return undefined;
546
+ }
547
+ const latlong = eastingNorthingToLatLong(result.value);
548
+
549
+ /** @type {MapCenter} */
550
+ const center = [latlong.long, latlong.lat];
551
+ return getInitMapCenterConfig(center);
552
+ }
553
+
554
+ /**
555
+ * Gets initial map config for an OS grid reference location field
556
+ * @param {HTMLDivElement} locationField - the osgridref location field element
557
+ * @returns {InteractiveMapInitConfig | undefined}
558
+ */
559
+ function getInitOsGridRefMapConfig(locationField) {
560
+ const osGridRefInput = getOsGridRefInput(locationField);
561
+ const result = validateOsGridRef(osGridRefInput.value);
562
+ if (!result.valid) {
563
+ return undefined;
564
+ }
565
+ const latlong = osGridRefToLatLong(result.value);
566
+
567
+ /** @type {MapCenter} */
568
+ const center = [latlong.long, latlong.lat];
569
+ return getInitMapCenterConfig(center);
570
+ }
571
+
572
+ /**
573
+ * Bind a latlong field to the map
574
+ * @param {HTMLDivElement} locationField - the latlong location field
575
+ * @param {InteractiveMap} map - the map component instance (of InteractiveMap)
576
+ * @param {MapLibreMap} mapProvider - the map provider instance (of MapLibreMap)
577
+ */
578
+ function bindLatLongField(locationField, map, mapProvider) {
579
+ const {
580
+ latInput,
581
+ longInput
582
+ } = getLatLongInputs(locationField);
583
+ map.on(EVENTS.interactMarkerChange,
584
+ /**
585
+ * Callback function which fires when the map marker changes
586
+ * @param {object} e - the event
587
+ * @param {[number, number]} e.coords - the map marker coordinates
588
+ */
589
+ function onInteractMarkerChange(e) {
590
+ const maxPrecision = 7;
591
+ latInput.value = e.coords[1].toFixed(maxPrecision);
592
+ longInput.value = e.coords[0].toFixed(maxPrecision);
593
+ });
594
+
595
+ /**
596
+ * Lat & long input change event listener
597
+ * Update the map view location when the inputs are changed
598
+ */
599
+ function onUpdateInputs() {
600
+ const result = validateLatLong(latInput.value, longInput.value);
601
+ if (result.valid) {
602
+ /** @type {MapCenter} */
603
+ const center = [result.value.long, result.value.lat];
604
+ centerMap(map, mapProvider, center);
605
+ }
606
+ }
607
+ latInput.addEventListener('change', onUpdateInputs, false);
608
+ longInput.addEventListener('change', onUpdateInputs, false);
609
+ }
610
+
611
+ /**
612
+ * Bind an eastingnorthing field to the map
613
+ * @param {HTMLDivElement} locationField - the eastingnorthing location field
614
+ * @param {InteractiveMap} map - the map component instance (of InteractiveMap)
615
+ * @param {MapLibreMap} mapProvider - the map provider instance (of MapLibreMap)
616
+ */
617
+ function bindEastingNorthingField(locationField, map, mapProvider) {
618
+ const {
619
+ eastingInput,
620
+ northingInput
621
+ } = getEastingNorthingInputs(locationField);
622
+ map.on(EVENTS.interactMarkerChange,
623
+ /**
624
+ * Callback function which fires when the map marker changes
625
+ * @param {object} e - the event
626
+ * @param {[number, number]} e.coords - the map marker coordinates
627
+ */
628
+ function onInteractMarkerChange(e) {
629
+ const maxPrecision = 0;
630
+ const point = latLongToEastingNorthing({
631
+ lat: e.coords[1],
632
+ long: e.coords[0]
633
+ });
634
+ eastingInput.value = point.easting.toFixed(maxPrecision);
635
+ northingInput.value = point.northing.toFixed(maxPrecision);
636
+ });
637
+
638
+ /**
639
+ * Easting & northing input change event listener
640
+ * Update the map view location when the inputs are changed
641
+ */
642
+ function onUpdateInputs() {
643
+ const result = validateEastingNorthing(eastingInput.value, northingInput.value);
644
+ if (result.valid) {
645
+ const latlong = eastingNorthingToLatLong(result.value);
646
+
647
+ /** @type {MapCenter} */
648
+ const center = [latlong.long, latlong.lat];
649
+ centerMap(map, mapProvider, center);
650
+ }
651
+ }
652
+ eastingInput.addEventListener('change', onUpdateInputs, false);
653
+ northingInput.addEventListener('change', onUpdateInputs, false);
654
+ }
655
+
656
+ /**
657
+ * Bind an OS grid reference field to the map
658
+ * @param {HTMLDivElement} locationField - the osgridref location field
659
+ * @param {InteractiveMap} map - the map component instance (of InteractiveMap)
660
+ * @param {MapLibreMap} mapProvider - the map provider instance (of MapLibreMap)
661
+ */
662
+ function bindOsGridRefField(locationField, map, mapProvider) {
663
+ const osGridRefInput = getOsGridRefInput(locationField);
664
+ map.on(EVENTS.interactMarkerChange,
665
+ /**
666
+ * Callback function which fires when the map marker changes
667
+ * @param {object} e - the event
668
+ * @param {[number, number]} e.coords - the map marker coordinates
669
+ */
670
+ function onInteractMarkerChange(e) {
671
+ const point = latLongToOsGridRef({
672
+ lat: e.coords[1],
673
+ long: e.coords[0]
674
+ });
675
+ osGridRefInput.value = point;
676
+ });
677
+
678
+ /**
679
+ * OS grid reference input change event listener
680
+ * Update the map view location when the input is changed
681
+ */
682
+ function onUpdateInput() {
683
+ const result = validateOsGridRef(osGridRefInput.value);
684
+ if (result.valid) {
685
+ const latlong = osGridRefToLatLong(result.value);
686
+
687
+ /** @type {MapCenter} */
688
+ const center = [latlong.long, latlong.lat];
689
+ centerMap(map, mapProvider, center);
690
+ }
691
+ }
692
+ osGridRefInput.addEventListener('change', onUpdateInput, false);
693
+ }
694
+
695
+ /**
696
+ * Updates the marker position and moves the map view port the new location
697
+ * @param {InteractiveMap} map - the map component instance (of InteractiveMap)
698
+ * @param {MapLibreMap} mapProvider - the map provider instance (of MapLibreMap)
699
+ * @param {MapCenter} center - the point
700
+ */
701
+ function centerMap(map, mapProvider, center) {
702
+ // Move the 'location' marker to the new point
703
+ map.addMarker('location', center);
704
+
705
+ // Pan & zoom the map to the new valid location
706
+ mapProvider.flyTo({
707
+ center,
708
+ zoom: 14,
709
+ essential: true
710
+ });
711
+ }
712
+
713
+ /**
714
+ * @typedef {object} InteractiveMap - an instance of a InteractiveMap
715
+ * @property {Function} on - register callback listeners to map events
716
+ * @property {Function} addPanel - adds a new panel to the map
717
+ * @property {Function} addMarker - adds/updates a marker
718
+ */
719
+
720
+ /**
721
+ * @typedef {object} MapLibreMap
722
+ * @property {Function} flyTo - pans/zooms to a new location
723
+ */
724
+
725
+ /**
726
+ * @typedef {[number, number]} MapCenter - Map center point as [long, lat]
727
+ */
728
+
729
+ /**
730
+ * @typedef {object} InteractiveMapInitConfig - additional config that can be provided to InteractiveMap
731
+ * @property {string} zoom - the zoom level of the map
732
+ * @property {MapCenter} center - the center point of the map
733
+ * @property {{ id: string, coords: MapCenter}[]} [markers] - the markers to add to the map
734
+ */
735
+
736
+ /**
737
+ * @typedef {object} TileData
738
+ * @property {string} VTS_OUTDOOR_URL - the outdoor tile URL
739
+ * @property {string} VTS_DARK_URL - the dark tile URL
740
+ * @property {string} VTS_BLACK_AND_WHITE_URL - the black and white tile URL
741
+ */
742
+
743
+ /**
744
+ * @typedef {object} MapsEnvironmentConfig
745
+ * @property {string} assetPath - the root asset path
746
+ * @property {string} apiPath - the root API path
747
+ * @property {TileData} data - the tile data config
748
+ */
749
+ //# sourceMappingURL=location-map.js.map