@defra/forms-engine-plugin 4.13.0 → 4.14.1

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 (32) hide show
  1. package/.public/javascripts/shared.min.js +1 -1
  2. package/.public/javascripts/shared.min.js.map +1 -1
  3. package/.server/client/javascripts/geospatial-map.d.ts +32 -0
  4. package/.server/client/javascripts/geospatial-map.js +161 -70
  5. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  6. package/.server/client/javascripts/map.d.ts +6 -0
  7. package/.server/client/javascripts/map.js +5 -0
  8. package/.server/client/javascripts/map.js.map +1 -1
  9. package/.server/client/javascripts/utils.d.ts +7 -0
  10. package/.server/client/javascripts/utils.js +21 -0
  11. package/.server/client/javascripts/utils.js.map +1 -0
  12. package/.server/server/forms/simple-form.yaml +9 -0
  13. package/.server/server/plugins/engine/components/GeospatialField.d.ts +1 -0
  14. package/.server/server/plugins/engine/components/GeospatialField.js +9 -5
  15. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  16. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +2 -2
  17. package/.server/server/plugins/engine/components/helpers/geospatial.js +32 -5
  18. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  19. package/.server/server/plugins/engine/components/helpers/geospatial.test.js +37 -6
  20. package/.server/server/plugins/engine/components/helpers/geospatial.test.js.map +1 -1
  21. package/.server/server/plugins/engine/pageControllers/validationOptions.js +4 -1
  22. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  23. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -1
  24. package/package.json +2 -2
  25. package/src/client/javascripts/geospatial-map.js +168 -53
  26. package/src/client/javascripts/map.js +5 -0
  27. package/src/client/javascripts/utils.js +23 -0
  28. package/src/server/forms/simple-form.yaml +9 -0
  29. package/src/server/plugins/engine/components/GeospatialField.ts +11 -7
  30. package/src/server/plugins/engine/components/helpers/geospatial.ts +49 -11
  31. package/src/server/plugins/engine/pageControllers/validationOptions.ts +4 -1
  32. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -1
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @param {boolean} allowPoint
3
+ * @param {boolean} allowLine
4
+ * @param {boolean} allowShape
5
+ */
6
+ export function getHelpPanelHtml(allowPoint: boolean, allowLine: boolean, allowShape: boolean): string;
1
7
  /**
2
8
  * Extract and parses the GeoJSON from the textarea
3
9
  * @param {HTMLTextAreaElement} geospatialInput - the textarea containing the geojson
@@ -45,6 +51,23 @@ export function focusFeature(feature: Feature, mapProvider: MapLibreMap): void;
45
51
  * @param {boolean} [readonly] - render the list item in readonly mode
46
52
  */
47
53
  export function createFeatureHTML(feature: Feature, index: number, mapId: string, disabled?: boolean, readonly?: boolean): string;
54
+ /**
55
+ * Factory closure to manage the UI
56
+ * @param {GeoJSON} geojson - the features
57
+ * @param {InteractiveMap} map - the map
58
+ * @param {string} mapId - the ID of the map
59
+ * @param {HTMLDivElement} listEl - where to render the feature list
60
+ * @param {HTMLTextAreaElement} geospatialInput - the geospatial textarea
61
+ * @param { UIManagerOptions | undefined } options - extra options such as allowable geometry types
62
+ */
63
+ export function getUIManager(geojson: GeoJSON, map: InteractiveMap, mapId: string, listEl: HTMLDivElement, geospatialInput: HTMLTextAreaElement, options: UIManagerOptions | undefined): {
64
+ renderList: RenderList;
65
+ renderValue: RenderValue;
66
+ listEl: HTMLDivElement;
67
+ toggleActionButtons: (hidden: boolean) => void;
68
+ focusDescriptionInput: () => void;
69
+ getAllowableGeometryTypes: () => string[];
70
+ };
48
71
  export type GeoJSON = {
49
72
  /**
50
73
  * - the GeoJSON type string
@@ -99,6 +122,10 @@ export type RenderValue = () => void;
99
122
  * Toggles the action button hidden state
100
123
  */
101
124
  export type ToggleActionButtons = (hidden: boolean) => void;
125
+ /**
126
+ * Returns the list of geometry types a user can create
127
+ */
128
+ export type GetAllowableGeometryTypes = () => string[];
102
129
  /**
103
130
  * Set focus to the last description input
104
131
  */
@@ -160,6 +187,10 @@ export type UIManager = {
160
187
  * - function that sets focus to a description input element
161
188
  */
162
189
  focusDescriptionInput: FocusDescriptionInput;
190
+ /**
191
+ * - function that returns the array of geometry types a user can create
192
+ */
193
+ getAllowableGeometryTypes: GetAllowableGeometryTypes;
163
194
  };
164
195
  export type Context = {
165
196
  /**
@@ -196,4 +227,5 @@ import type { Feature } from '../../server/plugins/engine/types.js';
196
227
  import type { InteractiveMap } from '../../client/javascripts/map.js';
197
228
  import type { FeatureCollection } from '../../server/plugins/engine/types.js';
198
229
  import type { MapLibreMap } from '../../client/javascripts/map.js';
230
+ import type { UIManagerOptions } from '../../client/javascripts/map.js';
199
231
  import type { Geometry } from '../../server/plugins/engine/types.js';
@@ -1,5 +1,6 @@
1
1
  import { bbox } from '@turf/bbox';
2
2
  import { EVENTS, createMap, defaultConfig, getCentroidGridRef, getCoordinateGridRef } from "./map.js";
3
+ import { formatDelimtedList } from "./utils.js";
3
4
  const helpPanelConfig = {
4
5
  showLabel: true,
5
6
  label: 'How to use this map',
@@ -20,9 +21,56 @@ const helpPanelConfig = {
20
21
  open: true,
21
22
  dismissible: true,
22
23
  modal: false
23
- },
24
- html: '<p class="govuk-body-s govuk-!-margin-bottom-2">You can add points, shapes or lines to the map.</p><ul class="govuk-list govuk-list--number govuk-body-s"><li>Search for a county, place or postcode</li><li>Use the + and - icons to zoom in and out</li><li>Double‑click, or select \'Done\', when you have finished drawing a line or shape</li><li>Give the location a name</li></ul>'
24
+ }
25
25
  };
26
+
27
+ /**
28
+ * @param {boolean} allowLine
29
+ * @param {boolean} allowShape
30
+ */
31
+ function getLineOrShapeText(allowLine, allowShape) {
32
+ if (allowLine && allowShape) {
33
+ return 'a line or shape';
34
+ }
35
+ if (allowLine) {
36
+ return 'a line';
37
+ }
38
+ if (allowShape) {
39
+ return 'a shape';
40
+ }
41
+ return '';
42
+ }
43
+
44
+ /**
45
+ * @param {boolean} allowPoint
46
+ * @param {boolean} allowLine
47
+ * @param {boolean} allowShape
48
+ */
49
+ function getAllowedTypesPhrase(allowPoint, allowLine, allowShape) {
50
+ const items = [];
51
+ if (allowPoint) {
52
+ items.push('points');
53
+ }
54
+ if (allowLine) {
55
+ items.push('lines');
56
+ }
57
+ if (allowShape) {
58
+ items.push('shapes');
59
+ }
60
+ return formatDelimtedList(items, ',', 'or');
61
+ }
62
+
63
+ /**
64
+ * @param {boolean} allowPoint
65
+ * @param {boolean} allowLine
66
+ * @param {boolean} allowShape
67
+ */
68
+ export function getHelpPanelHtml(allowPoint, allowLine, allowShape) {
69
+ const lineOrShapeText = getLineOrShapeText(allowLine, allowShape);
70
+ const doneExtra = lineOrShapeText ? `<li>Double‑click, or select 'Done', when you have finished drawing ${lineOrShapeText}</li>` : '';
71
+ const allowedTypesText = getAllowedTypesPhrase(allowPoint, allowLine, allowShape);
72
+ return `<p class="govuk-body-s govuk-!-margin-bottom-2">You can add ${allowedTypesText} to the map.</p><ul class="govuk-list govuk-list--number govuk-body-s"><li>Search for a county, place or postcode</li><li>Use the + and - icons to zoom in and out</li>${doneExtra}<li>Give the location a name</li></ul>`;
73
+ }
26
74
  const lineFeatureProperties = {
27
75
  stroke: 'rgba(0, 11, 112, 1)',
28
76
  fill: 'rgba(0, 11, 112, 0.2)',
@@ -138,7 +186,11 @@ export function processGeospatial(config, geospatial, index) {
138
186
  } = createMap(mapId, initConfig, config);
139
187
  const featuresManager = getFeaturesManager(geojson);
140
188
  const activeFeatureManager = getActiveFeatureManager();
141
- const uiManager = getUIManager(geojson, map, mapId, listEl, geospatialInput);
189
+ const geometryTypes = geospatial.dataset.geometrytypes;
190
+ const options = {
191
+ geometryTypes
192
+ };
193
+ const uiManager = getUIManager(geojson, map, mapId, listEl, geospatialInput, options);
142
194
 
143
195
  /**
144
196
  * @type {Context}
@@ -454,16 +506,32 @@ function getValueRenderer(geojson, geospatialInput) {
454
506
  * @param {string} mapId - the ID of the map
455
507
  * @param {HTMLDivElement} listEl - where to render the feature list
456
508
  * @param {HTMLTextAreaElement} geospatialInput - the geospatial textarea
509
+ * @param { UIManagerOptions | undefined } options - extra options such as allowable geometry types
457
510
  */
458
- function getUIManager(geojson, map, mapId, listEl, geospatialInput) {
511
+ export function getUIManager(geojson, map, mapId, listEl, geospatialInput, options) {
512
+ /**
513
+ * Get a CSV list of geometry types the user can create
514
+ * @returns {string[]}
515
+ */
516
+ function getAllowableGeometryTypes() {
517
+ return options?.geometryTypes ? options.geometryTypes.split(',') : ['point', 'line', 'shape'];
518
+ }
519
+
459
520
  /**
460
521
  * Toggle the hidden state of the action buttons
461
522
  * @type {ToggleActionButtons}
462
523
  */
463
524
  function toggleActionButtons(hidden) {
464
- map.toggleButtonState('btnAddPoint', 'hidden', hidden);
465
- map.toggleButtonState('btnAddPolygon', 'hidden', hidden);
466
- map.toggleButtonState('btnAddLine', 'hidden', hidden);
525
+ const types = getAllowableGeometryTypes();
526
+ if (types.includes('point')) {
527
+ map.toggleButtonState('btnAddPoint', 'hidden', hidden);
528
+ }
529
+ if (types.includes('shape')) {
530
+ map.toggleButtonState('btnAddPolygon', 'hidden', hidden);
531
+ }
532
+ if (types.includes('line')) {
533
+ map.toggleButtonState('btnAddLine', 'hidden', hidden);
534
+ }
467
535
  }
468
536
 
469
537
  /**
@@ -487,7 +555,8 @@ function getUIManager(geojson, map, mapId, listEl, geospatialInput) {
487
555
  renderValue,
488
556
  listEl,
489
557
  toggleActionButtons,
490
- focusDescriptionInput
558
+ focusDescriptionInput,
559
+ getAllowableGeometryTypes
491
560
  };
492
561
  }
493
562
 
@@ -539,7 +608,8 @@ function onMapReadyFactory(context) {
539
608
  } = context;
540
609
  const {
541
610
  toggleActionButtons,
542
- renderList
611
+ renderList,
612
+ getAllowableGeometryTypes
543
613
  } = uiManager;
544
614
  const {
545
615
  resetActiveFeature
@@ -551,68 +621,82 @@ function onMapReadyFactory(context) {
551
621
  * @param {MapLibreMap} e.map - the map provider instance
552
622
  */
553
623
  return function onMapReady(e) {
624
+ const types = getAllowableGeometryTypes();
625
+ const allowPoint = types.includes('point');
626
+ const allowLine = types.includes('line');
627
+ const allowShape = types.includes('shape');
628
+
554
629
  // Add info panel
555
- map.addPanel('info', helpPanelConfig);
556
- map.addButton('btnAddPoint', {
557
- variant: 'tertiary',
558
- label: 'Add point',
559
- iconSvgContent: POINT_SVG,
560
- onClick: () => {
561
- resetActiveFeature();
562
- toggleActionButtons(true);
563
- renderList(true);
564
- interactPlugin.enable();
565
- },
566
- mobile: {
567
- slot: 'actions'
568
- },
569
- tablet: {
570
- slot: 'actions'
571
- },
572
- desktop: {
573
- slot: 'actions'
574
- }
575
- });
576
- map.addButton('btnAddPolygon', {
577
- variant: 'tertiary',
578
- label: 'Add shape',
579
- iconSvgContent: POLYGON_SVG,
580
- onClick: () => {
581
- resetActiveFeature();
582
- toggleActionButtons(true);
583
- renderList(true);
584
- drawPlugin.newPolygon(generateID(), polygonFeatureProperties);
585
- },
586
- mobile: {
587
- slot: 'actions'
588
- },
589
- tablet: {
590
- slot: 'actions'
591
- },
592
- desktop: {
593
- slot: 'actions'
594
- }
595
- });
596
- map.addButton('btnAddLine', {
597
- variant: 'tertiary',
598
- label: 'Add line',
599
- iconSvgContent: LINE_SVG,
600
- onClick: () => {
601
- resetActiveFeature();
602
- toggleActionButtons(true);
603
- renderList(true);
604
- drawPlugin.newLine(generateID(), lineFeatureProperties);
605
- },
606
- mobile: {
607
- slot: 'actions'
608
- },
609
- tablet: {
610
- slot: 'actions'
611
- },
612
- desktop: {
613
- slot: 'actions'
614
- }
630
+ map.addPanel('info', {
631
+ ...helpPanelConfig,
632
+ html: getHelpPanelHtml(allowPoint, allowLine, allowShape)
615
633
  });
634
+ if (allowPoint) {
635
+ map.addButton('btnAddPoint', {
636
+ variant: 'tertiary',
637
+ label: 'Add point',
638
+ iconSvgContent: POINT_SVG,
639
+ onClick: () => {
640
+ resetActiveFeature();
641
+ toggleActionButtons(true);
642
+ renderList(true);
643
+ interactPlugin.enable();
644
+ },
645
+ mobile: {
646
+ slot: 'actions'
647
+ },
648
+ tablet: {
649
+ slot: 'actions'
650
+ },
651
+ desktop: {
652
+ slot: 'actions'
653
+ }
654
+ });
655
+ }
656
+ if (allowShape) {
657
+ map.addButton('btnAddPolygon', {
658
+ variant: 'tertiary',
659
+ label: 'Add shape',
660
+ iconSvgContent: POLYGON_SVG,
661
+ onClick: () => {
662
+ resetActiveFeature();
663
+ toggleActionButtons(true);
664
+ renderList(true);
665
+ drawPlugin.newPolygon(generateID(), polygonFeatureProperties);
666
+ },
667
+ mobile: {
668
+ slot: 'actions'
669
+ },
670
+ tablet: {
671
+ slot: 'actions'
672
+ },
673
+ desktop: {
674
+ slot: 'actions'
675
+ }
676
+ });
677
+ }
678
+ if (allowLine) {
679
+ map.addButton('btnAddLine', {
680
+ variant: 'tertiary',
681
+ label: 'Add line',
682
+ iconSvgContent: LINE_SVG,
683
+ onClick: () => {
684
+ resetActiveFeature();
685
+ toggleActionButtons(true);
686
+ renderList(true);
687
+ drawPlugin.newLine(generateID(), lineFeatureProperties);
688
+ },
689
+ mobile: {
690
+ slot: 'actions'
691
+ },
692
+ tablet: {
693
+ slot: 'actions'
694
+ },
695
+ desktop: {
696
+ slot: 'actions'
697
+ }
698
+ });
699
+ }
616
700
 
617
701
  // Set the map provider on the context
618
702
  context.mapProvider = e.map;
@@ -1083,6 +1167,12 @@ function onListElKeydownFactory() {
1083
1167
  * @returns {void}
1084
1168
  */
1085
1169
 
1170
+ /**
1171
+ * Returns the list of geometry types a user can create
1172
+ * @callback GetAllowableGeometryTypes
1173
+ * @returns {string[]}
1174
+ */
1175
+
1086
1176
  /**
1087
1177
  * Set focus to the last description input
1088
1178
  * @callback FocusDescriptionInput
@@ -1112,6 +1202,7 @@ function onListElKeydownFactory() {
1112
1202
  * @property {HTMLDivElement} listEl - the summary list of features
1113
1203
  * @property {ToggleActionButtons} toggleActionButtons - function that toggles the action buttons
1114
1204
  * @property {FocusDescriptionInput} focusDescriptionInput - function that sets focus to a description input element
1205
+ * @property {GetAllowableGeometryTypes} getAllowableGeometryTypes - function that returns the array of geometry types a user can create
1115
1206
  */
1116
1207
 
1117
1208
  /**
@@ -1126,6 +1217,6 @@ function onListElKeydownFactory() {
1126
1217
  */
1127
1218
 
1128
1219
  /**
1129
- * @import { MapLibreMap } from '../../client/javascripts/map.js'
1220
+ * @import { MapLibreMap, UIManagerOptions } from '../../client/javascripts/map.js'
1130
1221
  */
1131
1222
  //# sourceMappingURL=geospatial-map.js.map