@defra/forms-engine-plugin 5.0.0-alpha.28 → 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.
- package/.public/37c964ca545c240f010a.mjs +2 -0
- package/.public/37c964ca545c240f010a.mjs.LICENSE.txt +4 -0
- package/.public/assets-manifest.json +3 -0
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.LICENSE.txt +2 -0
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.public/javascripts/vendor/@defra.min.js +1 -1
- package/.public/javascripts/vendor/@defra.min.js.map +1 -1
- package/.public/javascripts/vendor/maplibre-gl.min.js +1 -1
- package/.public/javascripts/vendor/maplibre-gl.min.js.LICENSE.txt +3 -3
- package/.public/javascripts/vendor/maplibre-gl.min.js.map +1 -1
- package/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/javascripts/geospatial-map.js +3 -3
- package/.server/client/javascripts/geospatial-map.js.map +1 -1
- package/.server/client/javascripts/location-map.js +2 -2
- package/.server/client/javascripts/location-map.js.map +1 -1
- package/.server/client/javascripts/map.d.ts +5 -2
- package/.server/client/javascripts/map.js +17 -5
- package/.server/client/javascripts/map.js.map +1 -1
- package/.server/server/plugins/engine/components/AutocompleteField.d.ts +1 -2
- package/.server/server/plugins/engine/components/AutocompleteField.js +9 -4
- package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldBase.js +11 -6
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.js +14 -8
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/YesNoField.d.ts +1 -3
- package/.server/server/plugins/engine/components/YesNoField.js +9 -4
- package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/index.d.ts +14 -1
- package/.server/server/plugins/engine/components/helpers/index.js +32 -0
- package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
- package/package.json +4 -4
- package/src/client/javascripts/geospatial-map.js +3 -3
- package/src/client/javascripts/location-map.js +2 -2
- package/src/client/javascripts/map.js +19 -8
- package/src/server/plugins/engine/components/AutocompleteField.ts +12 -5
- package/src/server/plugins/engine/components/LocationFieldBase.ts +18 -8
- package/src/server/plugins/engine/components/NumberField.ts +17 -13
- package/src/server/plugins/engine/components/YesNoField.ts +14 -4
- package/src/server/plugins/engine/components/helpers/index.ts +45 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["joi","lowerFirst","escapeMarkdown","answer","punctuation","character","replaceAll","addClassOptionIfNone","options","className","classes","lowerFirstPreserveProperNouns","text","result","replace","lowerFirstExpressionOptions","functions","createLowerFirstExpression","template","expression","getTranslatedLabel","component","translator","tComponent","translatedLabel"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/index.ts"],"sourcesContent":["import { type ComponentDef } from '@defra/forms-model'\nimport joi, {
|
|
1
|
+
{"version":3,"file":"index.js","names":["joi","lowerFirst","escapeMarkdown","answer","punctuation","character","replaceAll","addClassOptionIfNone","options","className","classes","lowerFirstPreserveProperNouns","text","result","replace","lowerFirstExpressionOptions","functions","createLowerFirstExpression","template","expression","withCustomValidationOverrides","defaults","customValidationMessage","customValidationMessages","messages","key","Object","keys","authorKeys","message","entries","includes","getTranslatedLabel","component","translator","tComponent","translatedLabel"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/index.ts"],"sourcesContent":["import { type ComponentDef } from '@defra/forms-model'\nimport joi, {\n type JoiExpression,\n type LanguageMessages,\n type ReferenceOptions\n} from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\n\n/**\n * Prevent Markdown formatting\n * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}\n */\nexport function escapeMarkdown(answer: string) {\n const punctuation = [\n '`',\n \"'\",\n '*',\n '_',\n '{',\n '}',\n '[',\n ']',\n '(',\n ')',\n '#',\n '+',\n '-',\n '.',\n '!'\n ]\n\n for (const character of punctuation) {\n answer = answer.replaceAll(character, `\\\\${character}`)\n }\n\n return answer\n}\n\nexport const addClassOptionIfNone = (\n options: Extract<ComponentDef, { options: { classes?: string } }>['options'],\n className: string\n) => {\n options.classes ??= className\n}\n\n/**\n * Applies lowerFirst but preserves capitalisation of proper nouns\n * like \"National Grid\", \"Ordnance Survey\" and \"OS\".\n */\nexport function lowerFirstPreserveProperNouns(text: string): string {\n const result = lowerFirst(text)\n return result\n .replace(/\\bnational [Gg]rid\\b/g, 'National Grid')\n .replace(/\\bordnance [Ss]urvey\\b/g, 'Ordnance Survey')\n .replace(/\\b[oO][sS]\\b/g, 'OS')\n}\n\n/**\n * Configuration for Joi expressions that use lowerFirst function\n */\nexport const lowerFirstExpressionOptions = {\n functions: {\n lowerFirst: lowerFirstPreserveProperNouns\n }\n} as ReferenceOptions\n\n/**\n * Creates a Joi expression with lowerFirst function support\n * Used for error messages in location field components\n */\nexport const createLowerFirstExpression = (template: string): JoiExpression =>\n joi.expression(template, lowerFirstExpressionOptions) as JoiExpression\n\n/**\n * Joi's own LanguageMessages type only allows string values, but\n * buildValidationMessages() returns JoiExpression for templated messages,\n * which Joi accepts at runtime. This type covers both.\n */\ntype TranslatedValidationMessages = Record<string, string | JoiExpression>\n\n/**\n * Keep the form author's custom validation message for any key they set,\n * so translated defaults only fill in the keys the author left blank.\n * A single customValidationMessage covers every key in the schema.\n */\nexport function withCustomValidationOverrides(\n defaults: TranslatedValidationMessages,\n customValidationMessage?: string,\n customValidationMessages?: LanguageMessages\n): LanguageMessages {\n const messages: TranslatedValidationMessages = {}\n\n // A global override for all messages is the value for all scenarios\n if (customValidationMessage) {\n for (const key of Object.keys(defaults)) {\n messages[key] = customValidationMessage\n }\n\n return messages as LanguageMessages\n }\n\n // A default only fills a key that the user has not configured\n const authorKeys = Object.keys(customValidationMessages ?? {})\n\n for (const [key, message] of Object.entries(defaults)) {\n if (!authorKeys.includes(key)) {\n messages[key] = message\n }\n }\n\n return messages as LanguageMessages\n}\n\n/**\n * Translate a component label, in precedence order of errorDescription, then shortDesciption, then title\n * @param {ComponentDef} component\n * @param {Translator} translator\n */\nexport function getTranslatedLabel(\n component: ComponentDef,\n translator: Translator\n) {\n const { tComponent } = translator\n const translatedLabel =\n tComponent(component, 'errorDescription') ||\n tComponent(component, 'shortDescription') ||\n tComponent(component, 'title')\n return translatedLabel\n}\n"],"mappings":"AACA,OAAOA,GAAG,MAIH,KAAK;AACZ,OAAOC,UAAU,MAAM,sBAAsB;AAI7C;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,MAAc,EAAE;EAC7C,MAAMC,WAAW,GAAG,CAClB,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACJ;EAED,KAAK,MAAMC,SAAS,IAAID,WAAW,EAAE;IACnCD,MAAM,GAAGA,MAAM,CAACG,UAAU,CAACD,SAAS,EAAE,KAAKA,SAAS,EAAE,CAAC;EACzD;EAEA,OAAOF,MAAM;AACf;AAEA,OAAO,MAAMI,oBAAoB,GAAGA,CAClCC,OAA4E,EAC5EC,SAAiB,KACd;EACHD,OAAO,CAACE,OAAO,KAAKD,SAAS;AAC/B,CAAC;;AAED;AACA;AACA;AACA;AACA,OAAO,SAASE,6BAA6BA,CAACC,IAAY,EAAU;EAClE,MAAMC,MAAM,GAAGZ,UAAU,CAACW,IAAI,CAAC;EAC/B,OAAOC,MAAM,CACVC,OAAO,CAAC,uBAAuB,EAAE,eAAe,CAAC,CACjDA,OAAO,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CACrDA,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC;AACnC;;AAEA;AACA;AACA;AACA,OAAO,MAAMC,2BAA2B,GAAG;EACzCC,SAAS,EAAE;IACTf,UAAU,EAAEU;EACd;AACF,CAAqB;;AAErB;AACA;AACA;AACA;AACA,OAAO,MAAMM,0BAA0B,GAAIC,QAAgB,IACzDlB,GAAG,CAACmB,UAAU,CAACD,QAAQ,EAAEH,2BAA2B,CAAkB;;AAExE;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASK,6BAA6BA,CAC3CC,QAAsC,EACtCC,uBAAgC,EAChCC,wBAA2C,EACzB;EAClB,MAAMC,QAAsC,GAAG,CAAC,CAAC;;EAEjD;EACA,IAAIF,uBAAuB,EAAE;IAC3B,KAAK,MAAMG,GAAG,IAAIC,MAAM,CAACC,IAAI,CAACN,QAAQ,CAAC,EAAE;MACvCG,QAAQ,CAACC,GAAG,CAAC,GAAGH,uBAAuB;IACzC;IAEA,OAAOE,QAAQ;EACjB;;EAEA;EACA,MAAMI,UAAU,GAAGF,MAAM,CAACC,IAAI,CAACJ,wBAAwB,IAAI,CAAC,CAAC,CAAC;EAE9D,KAAK,MAAM,CAACE,GAAG,EAAEI,OAAO,CAAC,IAAIH,MAAM,CAACI,OAAO,CAACT,QAAQ,CAAC,EAAE;IACrD,IAAI,CAACO,UAAU,CAACG,QAAQ,CAACN,GAAG,CAAC,EAAE;MAC7BD,QAAQ,CAACC,GAAG,CAAC,GAAGI,OAAO;IACzB;EACF;EAEA,OAAOL,QAAQ;AACjB;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASQ,kBAAkBA,CAChCC,SAAuB,EACvBC,UAAsB,EACtB;EACA,MAAM;IAAEC;EAAW,CAAC,GAAGD,UAAU;EACjC,MAAME,eAAe,GACnBD,UAAU,CAACF,SAAS,EAAE,kBAAkB,CAAC,IACzCE,UAAU,CAACF,SAAS,EAAE,kBAAkB,CAAC,IACzCE,UAAU,CAACF,SAAS,EAAE,OAAO,CAAC;EAChC,OAAOG,eAAe;AACxB","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.29",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"dependencies": {
|
|
95
95
|
"@defra/forms-model": "^4.0.0",
|
|
96
96
|
"@defra/hapi-tracing": "^1.29.0",
|
|
97
|
-
"@defra/interactive-map": "0.0.
|
|
97
|
+
"@defra/interactive-map": "^0.0.56-alpha",
|
|
98
98
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
99
99
|
"@hapi/boom": "^10.0.1",
|
|
100
100
|
"@hapi/bourne": "^3.0.0",
|
|
@@ -138,7 +138,6 @@
|
|
|
138
138
|
"liquidjs": "^10.24.0",
|
|
139
139
|
"lodash": "^4.17.21",
|
|
140
140
|
"lru-cache": "^11.5.1",
|
|
141
|
-
"maplibre-gl": "^5.24.0",
|
|
142
141
|
"marked": "^15.0.12",
|
|
143
142
|
"nunjucks": "^3.2.4",
|
|
144
143
|
"obscenity": "^0.4.5",
|
|
@@ -155,7 +154,7 @@
|
|
|
155
154
|
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
|
156
155
|
"@babel/preset-env": "^7.28.5",
|
|
157
156
|
"@babel/preset-typescript": "^7.28.5",
|
|
158
|
-
"@defra/docusaurus-theme-govuk": "^0.0
|
|
157
|
+
"@defra/docusaurus-theme-govuk": "^1.0.0",
|
|
159
158
|
"@docusaurus/core": "^3.10.1",
|
|
160
159
|
"@docusaurus/plugin-content-docs": "^3.10.1",
|
|
161
160
|
"@easyops-cn/docusaurus-search-local": "^0.55.0",
|
|
@@ -219,6 +218,7 @@
|
|
|
219
218
|
"prettier": "^3.6.2",
|
|
220
219
|
"react": "^19.2.4",
|
|
221
220
|
"react-dom": "^19.2.4",
|
|
221
|
+
"react-router-dom": "^5.3.4",
|
|
222
222
|
"sass-embedded": "^1.93.3",
|
|
223
223
|
"sass-loader": "^16.0.6",
|
|
224
224
|
"source-map-loader": "^5.0.0",
|
|
@@ -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
|
|
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
|
-
...
|
|
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) ??
|
|
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
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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(
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
[this.name]: withCustomValidationOverrides(
|
|
62
|
+
defaults,
|
|
63
|
+
undefined,
|
|
64
|
+
customValidationMessages
|
|
65
|
+
)
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
}
|
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
FormComponent,
|
|
10
10
|
isFormValue
|
|
11
11
|
} from './FormComponent.js'
|
|
12
|
-
import {
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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 {
|
|
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
|
-
|
|
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, {
|
|
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
|