@defra/forms-engine-plugin 5.0.0-alpha.27 → 5.0.0-alpha.29

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 (57) hide show
  1. package/.public/37c964ca545c240f010a.mjs +2 -0
  2. package/.public/37c964ca545c240f010a.mjs.LICENSE.txt +4 -0
  3. package/.public/assets-manifest.json +3 -0
  4. package/.public/javascripts/shared.min.js +1 -1
  5. package/.public/javascripts/shared.min.js.LICENSE.txt +2 -0
  6. package/.public/javascripts/shared.min.js.map +1 -1
  7. package/.public/javascripts/vendor/@defra.min.js +1 -1
  8. package/.public/javascripts/vendor/@defra.min.js.map +1 -1
  9. package/.public/javascripts/vendor/maplibre-gl.min.js +1 -1
  10. package/.public/javascripts/vendor/maplibre-gl.min.js.LICENSE.txt +3 -3
  11. package/.public/javascripts/vendor/maplibre-gl.min.js.map +1 -1
  12. package/.public/stylesheets/application.min.css +1 -1
  13. package/.public/stylesheets/application.min.css.map +1 -1
  14. package/.server/client/javascripts/geospatial-map.js +3 -3
  15. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  16. package/.server/client/javascripts/location-map.js +2 -2
  17. package/.server/client/javascripts/location-map.js.map +1 -1
  18. package/.server/client/javascripts/map.d.ts +5 -2
  19. package/.server/client/javascripts/map.js +17 -5
  20. package/.server/client/javascripts/map.js.map +1 -1
  21. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +1 -2
  22. package/.server/server/plugins/engine/components/AutocompleteField.js +9 -4
  23. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  24. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +2 -2
  25. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  26. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -1
  27. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  28. package/.server/server/plugins/engine/components/DeclarationField.d.ts +2 -2
  29. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  30. package/.server/server/plugins/engine/components/FileUploadField.d.ts +2 -2
  31. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  32. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +2 -2
  33. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
  34. package/.server/server/plugins/engine/components/LocationFieldBase.js +11 -6
  35. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  36. package/.server/server/plugins/engine/components/NumberField.js +14 -8
  37. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  38. package/.server/server/plugins/engine/components/YesNoField.d.ts +1 -3
  39. package/.server/server/plugins/engine/components/YesNoField.js +9 -4
  40. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  41. package/.server/server/plugins/engine/components/helpers/index.d.ts +14 -1
  42. package/.server/server/plugins/engine/components/helpers/index.js +32 -0
  43. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  44. package/package.json +6 -6
  45. package/src/client/javascripts/geospatial-map.js +3 -3
  46. package/src/client/javascripts/location-map.js +2 -2
  47. package/src/client/javascripts/map.js +19 -8
  48. package/src/server/plugins/engine/components/AutocompleteField.ts +12 -5
  49. package/src/server/plugins/engine/components/CheckboxesField.ts +3 -3
  50. package/src/server/plugins/engine/components/ComponentBase.ts +4 -4
  51. package/src/server/plugins/engine/components/DeclarationField.ts +3 -4
  52. package/src/server/plugins/engine/components/FileUploadField.ts +2 -2
  53. package/src/server/plugins/engine/components/ListFormComponent.ts +4 -4
  54. package/src/server/plugins/engine/components/LocationFieldBase.ts +18 -8
  55. package/src/server/plugins/engine/components/NumberField.ts +17 -13
  56. package/src/server/plugins/engine/components/YesNoField.ts +14 -4
  57. package/src/server/plugins/engine/components/helpers/index.ts +45 -1
@@ -1,7 +1,7 @@
1
1
  // @ts-expect-error - no types
2
2
  import createDatasetsPlugin from '@defra/interactive-map/plugins/datasets'
3
3
  // @ts-expect-error - no types
4
- import createDrawPlugin from '@defra/interactive-map/plugins/draw-ml'
4
+ import createDrawPlugin from '@defra/interactive-map/plugins/draw'
5
5
  import { bbox } from '@turf/bbox'
6
6
 
7
7
  import {
@@ -12,9 +12,9 @@ import {
12
12
  import {
13
13
  EVENTS,
14
14
  createMap,
15
- defaultConfig,
16
15
  getCentroidGridRef,
17
16
  getCoordinateGridRef,
17
+ getDefaultConfig,
18
18
  getMapCountryLayers,
19
19
  getMapLayers
20
20
  } from './map.js'
@@ -223,7 +223,7 @@ export function processGeospatial(config, geospatial, index) {
223
223
  }
224
224
 
225
225
  const initConfig = {
226
- ...defaultConfig,
226
+ ...getDefaultConfig(),
227
227
  bounds,
228
228
  plugins
229
229
  }
@@ -10,8 +10,8 @@ import {
10
10
  EVENTS,
11
11
  centerMap,
12
12
  createMap,
13
- defaultConfig,
14
13
  eastingNorthingToLatLong,
14
+ getDefaultConfig,
15
15
  getMapCountryLayers,
16
16
  getMapLayers,
17
17
  latLongToEastingNorthing,
@@ -446,7 +446,7 @@ export function processLocation(config, location, index) {
446
446
  mapContainer.setAttribute('id', mapId)
447
447
  mapContainer.setAttribute('class', 'map-container')
448
448
 
449
- const initConfig = getInitMapConfig(location) ?? defaultConfig
449
+ const initConfig = getInitMapConfig(location) ?? getDefaultConfig()
450
450
 
451
451
  locationInputs.after(mapContainer)
452
452
 
@@ -122,10 +122,15 @@ export function getCentroidGridRef(feature) {
122
122
  }
123
123
  }
124
124
 
125
- /** @type {InteractiveMapInitConfig} */
126
- export const defaultConfig = {
127
- zoom: '6',
128
- center: [DEFAULT_LONG, DEFAULT_LAT]
125
+ /**
126
+ * Get the default config for a map
127
+ * @returns {InteractiveMapInitConfig}
128
+ */
129
+ export const getDefaultConfig = () => {
130
+ return {
131
+ zoom: '6',
132
+ center: [DEFAULT_LONG, DEFAULT_LAT]
133
+ }
129
134
  }
130
135
 
131
136
  export const EVENTS = {
@@ -166,6 +171,7 @@ export function initMaps(config = {}) {
166
171
  const {
167
172
  assetPath = '/assets',
168
173
  apiPath = '/form/api',
174
+ workerPath = '/assets/interactive-map/maplibre-gl-worker.mjs',
169
175
  data = defaultData
170
176
  } = config
171
177
  const locations = document.querySelectorAll('.app-location-field')
@@ -197,11 +203,15 @@ export function initMaps(config = {}) {
197
203
  }
198
204
 
199
205
  locations.forEach((location, index) => {
200
- processLocation({ assetPath, apiPath, data }, location, index)
206
+ processLocation({ assetPath, apiPath, workerPath, data }, location, index)
201
207
  })
202
208
 
203
209
  geospatials.forEach((geospatial, index) => {
204
- processGeospatial({ assetPath, apiPath, data }, geospatial, index)
210
+ processGeospatial(
211
+ { assetPath, apiPath, workerPath, data },
212
+ geospatial,
213
+ index
214
+ )
205
215
  })
206
216
  }
207
217
 
@@ -258,7 +268,7 @@ export function makeTileRequestTransformer(apiPath) {
258
268
  * @param {MapsEnvironmentConfig} mapsConfig - the map environment params
259
269
  */
260
270
  export function createMap(mapId, initConfig, mapsConfig) {
261
- const { assetPath, apiPath, data = defaultData } = mapsConfig
271
+ const { assetPath, apiPath, workerPath, data = defaultData } = mapsConfig
262
272
  const logoAltText = 'Ordnance survey logo'
263
273
  const interactPlugin = createInteractPlugin({
264
274
  markerColor: { outdoor: '#ff0000', dark: '#00ff00' },
@@ -270,7 +280,7 @@ export function createMap(mapId, initConfig, mapsConfig) {
270
280
  const map = new InteractiveMap(mapId, {
271
281
  enableFullscreen: true,
272
282
  autoColorScheme: false,
273
- mapProvider: maplibreProvider(),
283
+ mapProvider: maplibreProvider({ workerUrl: workerPath }),
274
284
  behaviour: 'inline',
275
285
  minZoom: 6,
276
286
  maxZoom: 18,
@@ -448,6 +458,7 @@ export function getMapCountryLayers(apiPath, country) {
448
458
  * @typedef {object} MapsEnvironmentConfig
449
459
  * @property {string} assetPath - the root asset path
450
460
  * @property {string} apiPath - the root API path
461
+ * @property {string} workerPath - the maplibre worker path
451
462
  * @property {TileData} data - the tile data config
452
463
  */
453
464
 
@@ -1,7 +1,7 @@
1
1
  import { type AutocompleteFieldComponent } from '@defra/forms-model'
2
- import { type LanguageMessages } from 'joi'
3
2
 
4
3
  import { SelectField } from './SelectField.js'
4
+ import { withCustomValidationOverrides } from './helpers/index.js'
5
5
  import { type RenderContext } from './types.js'
6
6
  import { buildValidationMessages } from '../i18n/buildValidationMessages.js'
7
7
  import { messageTemplate } from '../pageControllers/validationOptions.js'
@@ -50,12 +50,19 @@ export class AutocompleteField extends SelectField {
50
50
  }
51
51
 
52
52
  getValidationMessagesOverride(translator: Translator) {
53
+ const { customValidationMessages } = this.options
53
54
  const { t } = translator
55
+ const defaults = {
56
+ 'any.required': buildValidationMessages(t).objectRequired,
57
+ 'any.only': buildValidationMessages(t).objectRequired
58
+ }
59
+
54
60
  return {
55
- [this.name]: {
56
- 'any.required': buildValidationMessages(t).objectRequired,
57
- 'any.only': buildValidationMessages(t).objectRequired
58
- } as LanguageMessages
61
+ [this.name]: withCustomValidationOverrides(
62
+ defaults,
63
+ undefined,
64
+ customValidationMessages
65
+ )
59
66
  }
60
67
  }
61
68
  }
@@ -19,8 +19,8 @@ import {
19
19
  export class CheckboxesField extends SelectionControlField {
20
20
  declare options: CheckboxesFieldComponent['options']
21
21
  declare schema: CheckboxesFieldComponent['schema']
22
- declare formSchema: ArraySchema<string> | ArraySchema<number>
23
- declare stateSchema: ArraySchema<string> | ArraySchema<number>
22
+ declare formSchema: ArraySchema<string[]> | ArraySchema<number[]>
23
+ declare stateSchema: ArraySchema<string[]> | ArraySchema<number[]>
24
24
  declare limits: { min?: number; max?: number; length?: number }
25
25
 
26
26
  constructor(
@@ -33,7 +33,7 @@ export class CheckboxesField extends SelectionControlField {
33
33
  const { options, schema } = def
34
34
 
35
35
  let formSchema =
36
- type === 'string' ? joi.array<string>() : joi.array<number>()
36
+ type === 'string' ? joi.array<string[]>() : joi.array<number[]>()
37
37
 
38
38
  const itemsSchema = joi[type]()
39
39
  .valid(...this.values)
@@ -89,10 +89,10 @@ export class ComponentBase {
89
89
  }
90
90
 
91
91
  export type ComponentSchema =
92
- | ArraySchema<string>
93
- | ArraySchema<number>
94
- | ArraySchema<boolean>
95
- | ArraySchema<object>
92
+ | ArraySchema<string[]>
93
+ | ArraySchema<number[]>
94
+ | ArraySchema<boolean[]>
95
+ | ArraySchema<object[]>
96
96
  | BooleanSchema<string>
97
97
  | BooleanSchema
98
98
  | DateSchema
@@ -8,8 +8,7 @@ import {
8
8
  import joi, {
9
9
  type ArraySchema,
10
10
  type BooleanSchema,
11
- type LanguageMessages,
12
- type StringSchema
11
+ type LanguageMessages
13
12
  } from 'joi'
14
13
 
15
14
  import {
@@ -34,7 +33,7 @@ export class DeclarationField extends FormComponent {
34
33
 
35
34
  declare declarationConfirmationLabel: string | undefined
36
35
 
37
- declare formSchema: ArraySchema<StringSchema[]>
36
+ declare formSchema: ArraySchema<string[]>
38
37
  declare stateSchema: BooleanSchema
39
38
  declare content: string
40
39
  headerStartLevel: number
@@ -63,7 +62,7 @@ export class DeclarationField extends FormComponent {
63
62
  'any.unknown': messageTemplate.declarationRequired as string,
64
63
  'array.includesRequiredUnknowns':
65
64
  messageTemplate.declarationRequired as string
66
- }) as ArraySchema<StringSchema[]>
65
+ })
67
66
 
68
67
  this.formSchema = formSchema
69
68
  this.stateSchema = joi.boolean().cast('string').label(this.label).required()
@@ -111,8 +111,8 @@ export const formItemSchema = itemSchema.append({
111
111
  export class FileUploadField extends FormComponent {
112
112
  declare options: FileUploadFieldComponent['options']
113
113
  declare schema: FileUploadFieldComponent['schema']
114
- declare formSchema: ArraySchema<FileState>
115
- declare stateSchema: ArraySchema<FileState>
114
+ declare formSchema: ArraySchema<FileState[]>
115
+ declare stateSchema: ArraySchema<FileState[]>
116
116
  declare limits: { min?: number; max?: number; length?: number }
117
117
 
118
118
  constructor(
@@ -31,16 +31,16 @@ export class ListFormComponent extends FormComponent {
31
31
  >['options']
32
32
 
33
33
  declare formSchema:
34
- | ArraySchema<string>
35
- | ArraySchema<number>
34
+ | ArraySchema<string[]>
35
+ | ArraySchema<number[]>
36
36
  | BooleanSchema<string>
37
37
  | NumberSchema<string>
38
38
  | NumberSchema
39
39
  | StringSchema
40
40
 
41
41
  declare stateSchema:
42
- | ArraySchema<string>
43
- | ArraySchema<number>
42
+ | ArraySchema<string[]>
43
+ | ArraySchema<number[]>
44
44
  | BooleanSchema<string>
45
45
  | NumberSchema<string>
46
46
  | NumberSchema
@@ -9,7 +9,10 @@ import {
9
9
  FormComponent,
10
10
  isFormValue
11
11
  } from './FormComponent.js'
12
- import { addClassOptionIfNone } from './helpers/index.js'
12
+ import {
13
+ addClassOptionIfNone,
14
+ withCustomValidationOverrides
15
+ } from './helpers/index.js'
13
16
  import { type RenderContext } from './types.js'
14
17
  import { buildValidationMessages } from '../i18n/buildValidationMessages.js'
15
18
  import { messageTemplate } from '../pageControllers/validationOptions.js'
@@ -141,16 +144,23 @@ export abstract class LocationFieldBase extends FormComponent {
141
144
  }
142
145
 
143
146
  getValidationMessagesOverride(translator: Translator) {
147
+ const { customValidationMessage, customValidationMessages } = this.options
144
148
  const { t } = translator
145
149
  const config = this.getValidationConfig(translator)
150
+ const defaults = {
151
+ 'any.required':
152
+ config.requiredMessage ?? buildValidationMessages(t).objectRequired,
153
+ 'string.empty':
154
+ config.requiredMessage ?? buildValidationMessages(t).objectRequired,
155
+ 'string.pattern.base': config.patternErrorMessage
156
+ }
157
+
146
158
  return {
147
- [this.name]: {
148
- 'any.required':
149
- config.requiredMessage ?? buildValidationMessages(t).objectRequired,
150
- 'string.empty':
151
- config.requiredMessage ?? buildValidationMessages(t).objectRequired,
152
- 'string.pattern.base': config.patternErrorMessage
153
- } as LanguageMessages
159
+ [this.name]: withCustomValidationOverrides(
160
+ defaults,
161
+ customValidationMessage,
162
+ customValidationMessages
163
+ )
154
164
  }
155
165
  }
156
166
 
@@ -1,14 +1,11 @@
1
1
  import { type NumberFieldComponent } from '@defra/forms-model'
2
- import joi, {
3
- type CustomValidator,
4
- type LanguageMessages,
5
- type NumberSchema
6
- } from 'joi'
2
+ import joi, { type CustomValidator, type NumberSchema } from 'joi'
7
3
 
8
4
  import {
9
5
  FormComponent,
10
6
  isFormValue
11
7
  } from './FormComponent.js'
8
+ import { withCustomValidationOverrides } from './helpers/index.js'
12
9
  import { type RenderContext } from './types.js'
13
10
  import { buildValidationMessages } from '../i18n/buildValidationMessages.js'
14
11
  import { messageTemplate } from '../pageControllers/validationOptions.js'
@@ -137,16 +134,23 @@ export class NumberField extends FormComponent {
137
134
  }
138
135
 
139
136
  getValidationMessagesOverride(translator: Translator) {
137
+ const { customValidationMessage, customValidationMessages } = this.options
140
138
  const { t } = translator
139
+ const defaults = {
140
+ 'any.required': buildValidationMessages(t).objectRequired,
141
+ 'number.base': buildValidationMessages(t).objectMissing,
142
+ 'number.precision': buildValidationMessages(t).numberPrecision,
143
+ 'number.integer': buildValidationMessages(t).numberInteger,
144
+ 'number.min': buildValidationMessages(t).numberMin,
145
+ 'number.max': buildValidationMessages(t).numberMax
146
+ }
147
+
141
148
  return {
142
- [this.name]: {
143
- 'any.required': buildValidationMessages(t).objectRequired,
144
- 'number.base': buildValidationMessages(t).objectMissing,
145
- 'number.precision': buildValidationMessages(t).numberPrecision,
146
- 'number.integer': buildValidationMessages(t).numberInteger,
147
- 'number.min': buildValidationMessages(t).numberMin,
148
- 'number.max': buildValidationMessages(t).numberMax
149
- } as LanguageMessages
149
+ [this.name]: withCustomValidationOverrides(
150
+ defaults,
151
+ customValidationMessage,
152
+ customValidationMessages
153
+ )
150
154
  }
151
155
  }
152
156
 
@@ -6,7 +6,10 @@ import {
6
6
  } from '@defra/forms-model'
7
7
 
8
8
  import { SelectionControlField } from './SelectionControlField.js'
9
- import { addClassOptionIfNone } from './helpers/index.js'
9
+ import {
10
+ addClassOptionIfNone,
11
+ withCustomValidationOverrides
12
+ } from './helpers/index.js'
10
13
  import { buildValidationMessages } from '../i18n/buildValidationMessages.js'
11
14
  import { type Translator } from '../i18n/types.js'
12
15
  import { messageTemplate } from '../pageControllers/validationOptions.js'
@@ -62,11 +65,18 @@ export class YesNoField extends SelectionControlField {
62
65
  }
63
66
 
64
67
  getValidationMessagesOverride(translator: Translator) {
68
+ const { customValidationMessage, customValidationMessages } = this.options
65
69
  const { selectYesNoRequired } = buildValidationMessages(translator.t)
70
+ const defaults = {
71
+ 'any.required': selectYesNoRequired
72
+ }
73
+
66
74
  return {
67
- [this.name]: {
68
- 'any.required': selectYesNoRequired
69
- }
75
+ [this.name]: withCustomValidationOverrides(
76
+ defaults,
77
+ customValidationMessage,
78
+ customValidationMessages
79
+ )
70
80
  }
71
81
  }
72
82
 
@@ -1,5 +1,9 @@
1
1
  import { type ComponentDef } from '@defra/forms-model'
2
- import joi, { type JoiExpression, type ReferenceOptions } from 'joi'
2
+ import joi, {
3
+ type JoiExpression,
4
+ type LanguageMessages,
5
+ type ReferenceOptions
6
+ } from 'joi'
3
7
  import lowerFirst from 'lodash/lowerFirst.js'
4
8
 
5
9
  import { type Translator } from '../../i18n/types.js'
@@ -69,6 +73,46 @@ export const lowerFirstExpressionOptions = {
69
73
  export const createLowerFirstExpression = (template: string): JoiExpression =>
70
74
  joi.expression(template, lowerFirstExpressionOptions) as JoiExpression
71
75
 
76
+ /**
77
+ * Joi's own LanguageMessages type only allows string values, but
78
+ * buildValidationMessages() returns JoiExpression for templated messages,
79
+ * which Joi accepts at runtime. This type covers both.
80
+ */
81
+ type TranslatedValidationMessages = Record<string, string | JoiExpression>
82
+
83
+ /**
84
+ * Keep the form author's custom validation message for any key they set,
85
+ * so translated defaults only fill in the keys the author left blank.
86
+ * A single customValidationMessage covers every key in the schema.
87
+ */
88
+ export function withCustomValidationOverrides(
89
+ defaults: TranslatedValidationMessages,
90
+ customValidationMessage?: string,
91
+ customValidationMessages?: LanguageMessages
92
+ ): LanguageMessages {
93
+ const messages: TranslatedValidationMessages = {}
94
+
95
+ // A global override for all messages is the value for all scenarios
96
+ if (customValidationMessage) {
97
+ for (const key of Object.keys(defaults)) {
98
+ messages[key] = customValidationMessage
99
+ }
100
+
101
+ return messages as LanguageMessages
102
+ }
103
+
104
+ // A default only fills a key that the user has not configured
105
+ const authorKeys = Object.keys(customValidationMessages ?? {})
106
+
107
+ for (const [key, message] of Object.entries(defaults)) {
108
+ if (!authorKeys.includes(key)) {
109
+ messages[key] = message
110
+ }
111
+ }
112
+
113
+ return messages as LanguageMessages
114
+ }
115
+
72
116
  /**
73
117
  * Translate a component label, in precedence order of errorDescription, then shortDesciption, then title
74
118
  * @param {ComponentDef} component