@defra/forms-engine-plugin 4.0.6 → 4.0.8
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/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/_location-input.scss +60 -0
- package/.server/client/stylesheets/application.scss +1 -0
- package/.server/client/stylesheets/shared.scss +1 -0
- package/.server/server/forms/components.json +7 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
- package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -2
- package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DeclarationField.d.ts +81 -0
- package/.server/server/plugins/engine/components/DeclarationField.js +123 -0
- package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
- package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
- package/.server/server/plugins/engine/components/LatLongField.js +164 -0
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
- package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
- package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
- package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
- package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
- package/.server/server/plugins/engine/components/helpers/components.js +24 -29
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/index.d.ts +5 -0
- package/.server/server/plugins/engine/components/index.js +5 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
- package/.server/server/plugins/engine/components/markdownParser.js +28 -0
- package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
- package/.server/server/plugins/engine/components/types.d.ts +10 -0
- package/.server/server/plugins/engine/components/types.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
- package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -0
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/types/index.d.ts +1 -1
- package/.server/server/plugins/engine/types/index.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +2 -2
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
- package/.server/server/plugins/engine/views/components/declarationfield.html +14 -0
- package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
- package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
- package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
- package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
- package/.server/server/plugins/nunjucks/filters/index.js +1 -0
- package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/merge.d.ts +7 -0
- package/.server/server/plugins/nunjucks/filters/merge.js +16 -0
- package/.server/server/plugins/nunjucks/filters/merge.js.map +1 -0
- package/.server/server/plugins/nunjucks/filters/merge.test.js +19 -0
- package/.server/server/plugins/nunjucks/filters/merge.test.js.map +1 -0
- package/package.json +3 -3
- package/src/client/stylesheets/_location-input.scss +60 -0
- package/src/client/stylesheets/application.scss +1 -0
- package/src/client/stylesheets/shared.scss +1 -0
- package/src/server/forms/components.json +7 -0
- package/src/server/forms/page-events.yaml +1 -1
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
- package/src/server/index.test.ts +1 -0
- package/src/server/plugins/engine/components/ComponentBase.ts +2 -1
- package/src/server/plugins/engine/components/ComponentCollection.ts +1 -0
- package/src/server/plugins/engine/components/DeclarationField.test.ts +426 -0
- package/src/server/plugins/engine/components/DeclarationField.ts +167 -0
- package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
- package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
- package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
- package/src/server/plugins/engine/components/LatLongField.ts +213 -0
- package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
- package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
- package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
- package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
- package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
- package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
- package/src/server/plugins/engine/components/helpers/components.ts +44 -47
- package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
- package/src/server/plugins/engine/components/index.ts +5 -0
- package/src/server/plugins/engine/components/markdownParser.ts +40 -0
- package/src/server/plugins/engine/components/types.ts +14 -0
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +76 -3
- package/src/server/plugins/engine/models/SummaryViewModel.ts +5 -1
- package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
- package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
- package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +4 -0
- package/src/server/plugins/engine/types/index.ts +2 -0
- package/src/server/plugins/engine/types.ts +4 -0
- package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
- package/src/server/plugins/engine/views/components/declarationfield.html +14 -0
- package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
- package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
- package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
- package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
- package/src/server/plugins/nunjucks/filters/index.js +1 -0
- package/src/server/plugins/nunjucks/filters/merge.js +16 -0
- package/src/server/plugins/nunjucks/filters/merge.test.js +15 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import joi from 'joi';
|
|
2
|
+
import { FormComponent, isFormValue } from "./FormComponent.js";
|
|
3
|
+
import { addClassOptionIfNone } from "./helpers/index.js";
|
|
4
|
+
import { markdown } from "./markdownParser.js";
|
|
5
|
+
import { messageTemplate } from "../pageControllers/validationOptions.js";
|
|
6
|
+
/**
|
|
7
|
+
* Abstract base class for location-based field components
|
|
8
|
+
*/
|
|
9
|
+
export class LocationFieldBase extends FormComponent {
|
|
10
|
+
instructionText;
|
|
11
|
+
constructor(def, props) {
|
|
12
|
+
super(def, props);
|
|
13
|
+
const {
|
|
14
|
+
options
|
|
15
|
+
} = def;
|
|
16
|
+
const locationOptions = options;
|
|
17
|
+
this.instructionText = locationOptions.instructionText;
|
|
18
|
+
addClassOptionIfNone(locationOptions, 'govuk-input--width-10');
|
|
19
|
+
const config = this.getValidationConfig();
|
|
20
|
+
let formSchema = joi.string().trim().label(this.label).required().pattern(config.pattern).messages({
|
|
21
|
+
'string.pattern.base': config.patternErrorMessage,
|
|
22
|
+
...config.additionalMessages
|
|
23
|
+
});
|
|
24
|
+
if (config.customValidation) {
|
|
25
|
+
formSchema = formSchema.custom(config.customValidation);
|
|
26
|
+
}
|
|
27
|
+
if (locationOptions.required === false) {
|
|
28
|
+
formSchema = formSchema.allow('');
|
|
29
|
+
}
|
|
30
|
+
if (locationOptions.customValidationMessage) {
|
|
31
|
+
const message = locationOptions.customValidationMessage;
|
|
32
|
+
const messageKeys = ['any.required', 'string.empty', 'string.pattern.base'];
|
|
33
|
+
if (config.additionalMessages) {
|
|
34
|
+
messageKeys.push(...Object.keys(config.additionalMessages));
|
|
35
|
+
}
|
|
36
|
+
const messages = messageKeys.reduce((acc, key) => {
|
|
37
|
+
acc[key] = message;
|
|
38
|
+
return acc;
|
|
39
|
+
}, {});
|
|
40
|
+
formSchema = formSchema.messages(messages);
|
|
41
|
+
} else if (locationOptions.customValidationMessages) {
|
|
42
|
+
formSchema = formSchema.messages(locationOptions.customValidationMessages);
|
|
43
|
+
}
|
|
44
|
+
this.formSchema = formSchema.default('');
|
|
45
|
+
this.stateSchema = formSchema.default(null).allow(null);
|
|
46
|
+
this.options = locationOptions;
|
|
47
|
+
}
|
|
48
|
+
getFormValueFromState(state) {
|
|
49
|
+
const {
|
|
50
|
+
name
|
|
51
|
+
} = this;
|
|
52
|
+
return this.getFormValue(state[name]);
|
|
53
|
+
}
|
|
54
|
+
getFormValue(value) {
|
|
55
|
+
return this.isValue(value) ? value : undefined;
|
|
56
|
+
}
|
|
57
|
+
isValue(value) {
|
|
58
|
+
return LocationFieldBase.isText(value);
|
|
59
|
+
}
|
|
60
|
+
getViewModel(payload, errors) {
|
|
61
|
+
const viewModel = super.getViewModel(payload, errors);
|
|
62
|
+
if (this.instructionText) {
|
|
63
|
+
return {
|
|
64
|
+
...viewModel,
|
|
65
|
+
instructionText: markdown.parse(this.instructionText, {
|
|
66
|
+
async: false
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return viewModel;
|
|
71
|
+
}
|
|
72
|
+
getAllPossibleErrors() {
|
|
73
|
+
return {
|
|
74
|
+
baseErrors: [{
|
|
75
|
+
type: 'required',
|
|
76
|
+
template: messageTemplate.required
|
|
77
|
+
}, ...this.getErrorTemplates()],
|
|
78
|
+
advancedSettingsErrors: []
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
static isText(value) {
|
|
82
|
+
return isFormValue(value) && typeof value === 'string';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=LocationFieldBase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocationFieldBase.js","names":["joi","FormComponent","isFormValue","addClassOptionIfNone","markdown","messageTemplate","LocationFieldBase","instructionText","constructor","def","props","options","locationOptions","config","getValidationConfig","formSchema","string","trim","label","required","pattern","messages","patternErrorMessage","additionalMessages","customValidation","custom","allow","customValidationMessage","message","messageKeys","push","Object","keys","reduce","acc","key","customValidationMessages","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","isText","getViewModel","payload","errors","viewModel","parse","async","getAllPossibleErrors","baseErrors","type","template","getErrorTemplates","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldBase.ts"],"sourcesContent":["import { type FormComponentsDef } from '@defra/forms-model'\nimport joi, { type LanguageMessages, type StringSchema } from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { addClassOptionIfNone } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\ninterface LocationFieldOptions {\n instructionText?: string\n required?: boolean\n customValidationMessage?: string\n customValidationMessages?: LanguageMessages\n classes?: string\n}\n\ninterface ValidationConfig {\n pattern: RegExp\n patternErrorMessage: string\n customValidation?: (\n value: string,\n helpers: joi.CustomHelpers\n ) => string | joi.ErrorReport\n additionalMessages?: LanguageMessages\n}\n\n/**\n * Abstract base class for location-based field components\n */\nexport abstract class LocationFieldBase extends FormComponent {\n declare options: LocationFieldOptions\n declare formSchema: StringSchema\n declare stateSchema: StringSchema\n instructionText?: string\n\n protected abstract getValidationConfig(): ValidationConfig\n protected abstract getErrorTemplates(): {\n type: string\n template: string\n }[]\n\n constructor(\n def: FormComponentsDef,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options } = def\n const locationOptions = options as LocationFieldOptions\n this.instructionText = locationOptions.instructionText\n\n addClassOptionIfNone(locationOptions, 'govuk-input--width-10')\n\n const config = this.getValidationConfig()\n\n let formSchema = joi\n .string()\n .trim()\n .label(this.label)\n .required()\n .pattern(config.pattern)\n .messages({\n 'string.pattern.base': config.patternErrorMessage,\n ...config.additionalMessages\n })\n\n if (config.customValidation) {\n formSchema = formSchema.custom(config.customValidation)\n }\n\n if (locationOptions.required === false) {\n formSchema = formSchema.allow('')\n }\n\n if (locationOptions.customValidationMessage) {\n const message = locationOptions.customValidationMessage\n const messageKeys = [\n 'any.required',\n 'string.empty',\n 'string.pattern.base'\n ]\n\n if (config.additionalMessages) {\n messageKeys.push(...Object.keys(config.additionalMessages))\n }\n\n const messages = messageKeys.reduce<LanguageMessages>((acc, key) => {\n acc[key] = message\n return acc\n }, {})\n\n formSchema = formSchema.messages(messages)\n } else if (locationOptions.customValidationMessages) {\n formSchema = formSchema.messages(locationOptions.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = locationOptions\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { name } = this\n return this.getFormValue(state[name])\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n isValue(value?: FormStateValue | FormState): value is string {\n return LocationFieldBase.isText(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n\n if (this.instructionText) {\n return {\n ...viewModel,\n instructionText: markdown.parse(this.instructionText, { async: false })\n }\n }\n\n return viewModel\n }\n\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n ...this.getErrorTemplates()\n ],\n advancedSettingsErrors: []\n }\n }\n\n static isText(value?: FormStateValue | FormState): value is string {\n return isFormValue(value) && typeof value === 'string'\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type CustomValidator } from 'joi';
|
|
2
|
+
import { type EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js';
|
|
3
|
+
import { type LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js';
|
|
4
|
+
import { type DateInputItem, type Label, type ViewModel } from '~/src/server/plugins/engine/components/types.js';
|
|
5
|
+
import { type FormPayload, type FormSubmissionError, type FormValue } from '~/src/server/plugins/engine/types.js';
|
|
6
|
+
export type LocationField = InstanceType<typeof EastingNorthingField> | InstanceType<typeof LatLongField>;
|
|
7
|
+
export declare function getLocationFieldViewModel(component: LocationField, viewModel: ViewModel & {
|
|
8
|
+
label: Label;
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
value: FormValue;
|
|
12
|
+
}, payload: FormPayload, errors?: FormSubmissionError[]): {
|
|
13
|
+
fieldset: {
|
|
14
|
+
attributes?: string | Record<string, string>;
|
|
15
|
+
legend?: Label;
|
|
16
|
+
};
|
|
17
|
+
items: DateInputItem[];
|
|
18
|
+
label: Label;
|
|
19
|
+
type?: string;
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
value: FormValue;
|
|
23
|
+
hint?: {
|
|
24
|
+
id?: string;
|
|
25
|
+
text: string;
|
|
26
|
+
};
|
|
27
|
+
prefix?: import("~/src/server/plugins/engine/components/types.js").ComponentText;
|
|
28
|
+
suffix?: import("~/src/server/plugins/engine/components/types.js").ComponentText;
|
|
29
|
+
classes?: string;
|
|
30
|
+
condition?: string;
|
|
31
|
+
errors?: FormSubmissionError[];
|
|
32
|
+
errorMessage?: {
|
|
33
|
+
text: string;
|
|
34
|
+
};
|
|
35
|
+
summaryHtml?: string;
|
|
36
|
+
html?: string;
|
|
37
|
+
attributes: {
|
|
38
|
+
autocomplete?: string;
|
|
39
|
+
maxlength?: number;
|
|
40
|
+
multiple?: string;
|
|
41
|
+
accept?: string;
|
|
42
|
+
inputmode?: string;
|
|
43
|
+
};
|
|
44
|
+
content?: import("~/src/server/plugins/engine/components/types.js").Content | import("~/src/server/plugins/engine/components/types.js").Content[] | string;
|
|
45
|
+
maxlength?: number;
|
|
46
|
+
maxwords?: number;
|
|
47
|
+
rows?: number;
|
|
48
|
+
formGroup?: {
|
|
49
|
+
classes?: string;
|
|
50
|
+
attributes?: string | Record<string, string>;
|
|
51
|
+
};
|
|
52
|
+
components?: import("~/src/server/plugins/engine/components/types.js").ComponentViewModel[];
|
|
53
|
+
upload?: {
|
|
54
|
+
count: number;
|
|
55
|
+
summaryList: import("~/src/server/plugins/engine/types.js").SummaryList;
|
|
56
|
+
};
|
|
57
|
+
} | {
|
|
58
|
+
instructionText: string;
|
|
59
|
+
fieldset: {
|
|
60
|
+
attributes?: string | Record<string, string>;
|
|
61
|
+
legend?: Label;
|
|
62
|
+
};
|
|
63
|
+
items: DateInputItem[];
|
|
64
|
+
label: Label;
|
|
65
|
+
type?: string;
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
value: FormValue;
|
|
69
|
+
hint?: {
|
|
70
|
+
id?: string;
|
|
71
|
+
text: string;
|
|
72
|
+
};
|
|
73
|
+
prefix?: import("~/src/server/plugins/engine/components/types.js").ComponentText;
|
|
74
|
+
suffix?: import("~/src/server/plugins/engine/components/types.js").ComponentText;
|
|
75
|
+
classes?: string;
|
|
76
|
+
condition?: string;
|
|
77
|
+
errors?: FormSubmissionError[];
|
|
78
|
+
errorMessage?: {
|
|
79
|
+
text: string;
|
|
80
|
+
};
|
|
81
|
+
summaryHtml?: string;
|
|
82
|
+
html?: string;
|
|
83
|
+
attributes: {
|
|
84
|
+
autocomplete?: string;
|
|
85
|
+
maxlength?: number;
|
|
86
|
+
multiple?: string;
|
|
87
|
+
accept?: string;
|
|
88
|
+
inputmode?: string;
|
|
89
|
+
};
|
|
90
|
+
content?: import("~/src/server/plugins/engine/components/types.js").Content | import("~/src/server/plugins/engine/components/types.js").Content[] | string;
|
|
91
|
+
maxlength?: number;
|
|
92
|
+
maxwords?: number;
|
|
93
|
+
rows?: number;
|
|
94
|
+
formGroup?: {
|
|
95
|
+
classes?: string;
|
|
96
|
+
attributes?: string | Record<string, string>;
|
|
97
|
+
};
|
|
98
|
+
components?: import("~/src/server/plugins/engine/components/types.js").ComponentViewModel[];
|
|
99
|
+
upload?: {
|
|
100
|
+
count: number;
|
|
101
|
+
summaryList: import("~/src/server/plugins/engine/types.js").SummaryList;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Validator factory for location-based fields.
|
|
106
|
+
* This creates a validator that ensures all required fields are present.
|
|
107
|
+
*/
|
|
108
|
+
export declare function createLocationFieldValidator(component: LocationField): CustomValidator;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isFormValue } from "./FormComponent.js";
|
|
2
|
+
import { markdown } from "./markdownParser.js";
|
|
3
|
+
export function getLocationFieldViewModel(component, viewModel, payload, errors) {
|
|
4
|
+
const {
|
|
5
|
+
collection,
|
|
6
|
+
name
|
|
7
|
+
} = component;
|
|
8
|
+
const {
|
|
9
|
+
fieldset: existingFieldset,
|
|
10
|
+
label
|
|
11
|
+
} = viewModel;
|
|
12
|
+
|
|
13
|
+
// Check for component errors only
|
|
14
|
+
const hasError = errors?.some(error => error.name === name);
|
|
15
|
+
|
|
16
|
+
// Use the component collection to generate the subitems
|
|
17
|
+
const items = collection.getViewModel(payload, errors).map(({
|
|
18
|
+
model
|
|
19
|
+
}) => {
|
|
20
|
+
let {
|
|
21
|
+
label,
|
|
22
|
+
type,
|
|
23
|
+
value,
|
|
24
|
+
classes,
|
|
25
|
+
prefix,
|
|
26
|
+
suffix,
|
|
27
|
+
errorMessage
|
|
28
|
+
} = model;
|
|
29
|
+
if (label) {
|
|
30
|
+
label.toString = () => label.text; // Use string labels
|
|
31
|
+
}
|
|
32
|
+
if (hasError || errorMessage) {
|
|
33
|
+
classes = `${classes ?? ''} govuk-input--error`.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Allow any `toString()`-able value so non-numeric
|
|
37
|
+
// values are shown alongside their error messages
|
|
38
|
+
if (!isFormValue(value)) {
|
|
39
|
+
value = undefined;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
label,
|
|
43
|
+
id: model.id,
|
|
44
|
+
name: model.name,
|
|
45
|
+
type,
|
|
46
|
+
value,
|
|
47
|
+
classes,
|
|
48
|
+
prefix,
|
|
49
|
+
suffix
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
const fieldset = existingFieldset ?? {
|
|
53
|
+
legend: {
|
|
54
|
+
text: label.text,
|
|
55
|
+
classes: 'govuk-fieldset__legend--m'
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const result = {
|
|
59
|
+
...viewModel,
|
|
60
|
+
fieldset,
|
|
61
|
+
items
|
|
62
|
+
};
|
|
63
|
+
if (component.options.instructionText) {
|
|
64
|
+
return {
|
|
65
|
+
...result,
|
|
66
|
+
instructionText: markdown.parse(component.options.instructionText, {
|
|
67
|
+
async: false
|
|
68
|
+
})
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validator factory for location-based fields.
|
|
76
|
+
* This creates a validator that ensures all required fields are present.
|
|
77
|
+
*/
|
|
78
|
+
export function createLocationFieldValidator(component) {
|
|
79
|
+
return (payload, helpers) => {
|
|
80
|
+
const {
|
|
81
|
+
collection,
|
|
82
|
+
name,
|
|
83
|
+
options
|
|
84
|
+
} = component;
|
|
85
|
+
const values = component.getFormValueFromState(component.getStateFromValidForm(payload));
|
|
86
|
+
const context = {
|
|
87
|
+
missing: collection.keys,
|
|
88
|
+
key: name
|
|
89
|
+
};
|
|
90
|
+
if (!component.isState(values)) {
|
|
91
|
+
return options.required !== false ? helpers.error('object.required', context) : payload;
|
|
92
|
+
}
|
|
93
|
+
return payload;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=LocationFieldHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocationFieldHelpers.js","names":["isFormValue","markdown","getLocationFieldViewModel","component","viewModel","payload","errors","collection","name","fieldset","existingFieldset","label","hasError","some","error","items","getViewModel","map","model","type","value","classes","prefix","suffix","errorMessage","toString","text","trim","undefined","id","legend","result","options","instructionText","parse","async","createLocationFieldValidator","helpers","values","getFormValueFromState","getStateFromValidForm","context","missing","keys","key","isState","required"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldHelpers.ts"],"sourcesContent":["import { type Context, type CustomValidator } from 'joi'\n\nimport { type EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nimport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\nimport {\n type DateInputItem,\n type Label,\n type ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue\n} from '~/src/server/plugins/engine/types.js'\n\nexport type LocationField =\n | InstanceType<typeof EastingNorthingField>\n | InstanceType<typeof LatLongField>\n\nexport function getLocationFieldViewModel(\n component: LocationField,\n viewModel: ViewModel & {\n label: Label\n id: string\n name: string\n value: FormValue\n },\n payload: FormPayload,\n errors?: FormSubmissionError[]\n) {\n const { collection, name } = component\n const { fieldset: existingFieldset, label } = viewModel\n\n // Check for component errors only\n const hasError = errors?.some((error) => error.name === name)\n\n // Use the component collection to generate the subitems\n const items: DateInputItem[] = collection\n .getViewModel(payload, errors)\n .map(({ model }): DateInputItem => {\n let { label, type, value, classes, prefix, suffix, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text // Use string labels\n }\n\n if (hasError || errorMessage) {\n classes = `${classes ?? ''} govuk-input--error`.trim()\n }\n\n // Allow any `toString()`-able value so non-numeric\n // values are shown alongside their error messages\n if (!isFormValue(value)) {\n value = undefined\n }\n\n return {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes,\n prefix,\n suffix\n }\n })\n\n const fieldset = existingFieldset ?? {\n legend: {\n text: label.text,\n classes: 'govuk-fieldset__legend--m'\n }\n }\n\n const result = {\n ...viewModel,\n fieldset,\n items\n }\n\n if (component.options.instructionText) {\n return {\n ...result,\n instructionText: markdown.parse(component.options.instructionText, {\n async: false\n })\n }\n }\n\n return result\n}\n\n/**\n * Validator factory for location-based fields.\n * This creates a validator that ensures all required fields are present.\n */\nexport function createLocationFieldValidator(\n component: LocationField\n): CustomValidator {\n return (payload: FormPayload, helpers) => {\n const { collection, name, options } = component\n\n const values = component.getFormValueFromState(\n component.getStateFromValidForm(payload)\n )\n\n const context: Context = {\n missing: collection.keys,\n key: name\n }\n\n if (!component.isState(values)) {\n return options.required !== false\n ? helpers.error('object.required', context)\n : payload\n }\n\n return payload\n }\n}\n"],"mappings":"AAGA,SAASA,WAAW;AAEpB,SAASC,QAAQ;AAgBjB,OAAO,SAASC,yBAAyBA,CACvCC,SAAwB,EACxBC,SAKC,EACDC,OAAoB,EACpBC,MAA8B,EAC9B;EACA,MAAM;IAAEC,UAAU;IAAEC;EAAK,CAAC,GAAGL,SAAS;EACtC,MAAM;IAAEM,QAAQ,EAAEC,gBAAgB;IAAEC;EAAM,CAAC,GAAGP,SAAS;;EAEvD;EACA,MAAMQ,QAAQ,GAAGN,MAAM,EAAEO,IAAI,CAAEC,KAAK,IAAKA,KAAK,CAACN,IAAI,KAAKA,IAAI,CAAC;;EAE7D;EACA,MAAMO,KAAsB,GAAGR,UAAU,CACtCS,YAAY,CAACX,OAAO,EAAEC,MAAM,CAAC,CAC7BW,GAAG,CAAC,CAAC;IAAEC;EAAM,CAAC,KAAoB;IACjC,IAAI;MAAEP,KAAK;MAAEQ,IAAI;MAAEC,KAAK;MAAEC,OAAO;MAAEC,MAAM;MAAEC,MAAM;MAAEC;IAAa,CAAC,GAAGN,KAAK;IAEzE,IAAIP,KAAK,EAAE;MACTA,KAAK,CAACc,QAAQ,GAAG,MAAMd,KAAK,CAACe,IAAI,EAAC;IACpC;IAEA,IAAId,QAAQ,IAAIY,YAAY,EAAE;MAC5BH,OAAO,GAAG,GAAGA,OAAO,IAAI,EAAE,qBAAqB,CAACM,IAAI,CAAC,CAAC;IACxD;;IAEA;IACA;IACA,IAAI,CAAC3B,WAAW,CAACoB,KAAK,CAAC,EAAE;MACvBA,KAAK,GAAGQ,SAAS;IACnB;IAEA,OAAO;MACLjB,KAAK;MACLkB,EAAE,EAAEX,KAAK,CAACW,EAAE;MACZrB,IAAI,EAAEU,KAAK,CAACV,IAAI;MAChBW,IAAI;MACJC,KAAK;MACLC,OAAO;MACPC,MAAM;MACNC;IACF,CAAC;EACH,CAAC,CAAC;EAEJ,MAAMd,QAAQ,GAAGC,gBAAgB,IAAI;IACnCoB,MAAM,EAAE;MACNJ,IAAI,EAAEf,KAAK,CAACe,IAAI;MAChBL,OAAO,EAAE;IACX;EACF,CAAC;EAED,MAAMU,MAAM,GAAG;IACb,GAAG3B,SAAS;IACZK,QAAQ;IACRM;EACF,CAAC;EAED,IAAIZ,SAAS,CAAC6B,OAAO,CAACC,eAAe,EAAE;IACrC,OAAO;MACL,GAAGF,MAAM;MACTE,eAAe,EAAEhC,QAAQ,CAACiC,KAAK,CAAC/B,SAAS,CAAC6B,OAAO,CAACC,eAAe,EAAE;QACjEE,KAAK,EAAE;MACT,CAAC;IACH,CAAC;EACH;EAEA,OAAOJ,MAAM;AACf;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASK,4BAA4BA,CAC1CjC,SAAwB,EACP;EACjB,OAAO,CAACE,OAAoB,EAAEgC,OAAO,KAAK;IACxC,MAAM;MAAE9B,UAAU;MAAEC,IAAI;MAAEwB;IAAQ,CAAC,GAAG7B,SAAS;IAE/C,MAAMmC,MAAM,GAAGnC,SAAS,CAACoC,qBAAqB,CAC5CpC,SAAS,CAACqC,qBAAqB,CAACnC,OAAO,CACzC,CAAC;IAED,MAAMoC,OAAgB,GAAG;MACvBC,OAAO,EAAEnC,UAAU,CAACoC,IAAI;MACxBC,GAAG,EAAEpC;IACP,CAAC;IAED,IAAI,CAACL,SAAS,CAAC0C,OAAO,CAACP,MAAM,CAAC,EAAE;MAC9B,OAAON,OAAO,CAACc,QAAQ,KAAK,KAAK,GAC7BT,OAAO,CAACvB,KAAK,CAAC,iBAAiB,EAAE2B,OAAO,CAAC,GACzCpC,OAAO;IACb;IAEA,OAAOA,OAAO;EAChB,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model';
|
|
2
|
+
import type joi from 'joi';
|
|
3
|
+
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js';
|
|
4
|
+
export declare class NationalGridFieldNumberField extends LocationFieldBase {
|
|
5
|
+
options: NationalGridFieldNumberFieldComponent['options'];
|
|
6
|
+
protected getValidationConfig(): {
|
|
7
|
+
pattern: RegExp;
|
|
8
|
+
patternErrorMessage: string;
|
|
9
|
+
customValidation: (value: string, helpers: joi.CustomHelpers) => string | joi.ErrorReport;
|
|
10
|
+
};
|
|
11
|
+
protected getErrorTemplates(): {
|
|
12
|
+
type: string;
|
|
13
|
+
template: string;
|
|
14
|
+
}[];
|
|
15
|
+
/**
|
|
16
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
17
|
+
*/
|
|
18
|
+
static getAllPossibleErrors(): import("../types.js").ErrorMessageTemplateList;
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { LocationFieldBase } from "./LocationFieldBase.js";
|
|
2
|
+
export class NationalGridFieldNumberField extends LocationFieldBase {
|
|
3
|
+
getValidationConfig() {
|
|
4
|
+
return {
|
|
5
|
+
// Pattern allows spaces and commas in the input since custom validation will clean them
|
|
6
|
+
pattern: /^[A-Z]{2}[\d\s,]*$/i,
|
|
7
|
+
patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`,
|
|
8
|
+
customValidation: (value, helpers) => {
|
|
9
|
+
// Strip spaces and commas for validation
|
|
10
|
+
const cleanValue = value.replace(/[\s,]/g, '');
|
|
11
|
+
|
|
12
|
+
// Check if it matches the exact pattern after cleaning
|
|
13
|
+
if (!/^[A-Z]{2}\d{8}$/i.test(cleanValue)) {
|
|
14
|
+
return helpers.error('string.pattern.base');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Format with spaces per GDS guidance: NG 1234 5678
|
|
18
|
+
const letters = cleanValue.substring(0, 2);
|
|
19
|
+
const numbers = cleanValue.substring(2);
|
|
20
|
+
const formattedValue = `${letters} ${numbers.substring(0, 4)} ${numbers.substring(4)}`;
|
|
21
|
+
return formattedValue;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
getErrorTemplates() {
|
|
26
|
+
return [{
|
|
27
|
+
type: 'pattern',
|
|
28
|
+
template: 'Enter a valid National Grid field number for [short description] like NG 1234 5678'
|
|
29
|
+
}];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
34
|
+
*/
|
|
35
|
+
static getAllPossibleErrors() {
|
|
36
|
+
const instance = Object.create(NationalGridFieldNumberField.prototype);
|
|
37
|
+
return instance.getAllPossibleErrors();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=NationalGridFieldNumberField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NationalGridFieldNumberField.js","names":["LocationFieldBase","NationalGridFieldNumberField","getValidationConfig","pattern","patternErrorMessage","title","customValidation","value","helpers","cleanValue","replace","test","error","letters","substring","numbers","formattedValue","getErrorTemplates","type","template","getAllPossibleErrors","instance","Object","create","prototype"],"sources":["../../../../../src/server/plugins/engine/components/NationalGridFieldNumberField.ts"],"sourcesContent":["import { type NationalGridFieldNumberFieldComponent } from '@defra/forms-model'\nimport type joi from 'joi'\n\nimport { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'\n\nexport class NationalGridFieldNumberField extends LocationFieldBase {\n declare options: NationalGridFieldNumberFieldComponent['options']\n\n protected getValidationConfig() {\n return {\n // Pattern allows spaces and commas in the input since custom validation will clean them\n pattern: /^[A-Z]{2}[\\d\\s,]*$/i,\n patternErrorMessage: `Enter a valid National Grid field number for ${this.title} like NG 1234 5678`,\n customValidation: (value: string, helpers: joi.CustomHelpers) => {\n // Strip spaces and commas for validation\n const cleanValue = value.replace(/[\\s,]/g, '')\n\n // Check if it matches the exact pattern after cleaning\n if (!/^[A-Z]{2}\\d{8}$/i.test(cleanValue)) {\n return helpers.error('string.pattern.base')\n }\n\n // Format with spaces per GDS guidance: NG 1234 5678\n const letters = cleanValue.substring(0, 2)\n const numbers = cleanValue.substring(2)\n const formattedValue = `${letters} ${numbers.substring(0, 4)} ${numbers.substring(4)}`\n\n return formattedValue\n }\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template:\n 'Enter a valid National Grid field number for [short description] like NG 1234 5678'\n }\n ]\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors() {\n const instance = Object.create(\n NationalGridFieldNumberField.prototype\n ) as NationalGridFieldNumberField\n return instance.getAllPossibleErrors()\n }\n}\n"],"mappings":"AAGA,SAASA,iBAAiB;AAE1B,OAAO,MAAMC,4BAA4B,SAASD,iBAAiB,CAAC;EAGxDE,mBAAmBA,CAAA,EAAG;IAC9B,OAAO;MACL;MACAC,OAAO,EAAE,qBAAqB;MAC9BC,mBAAmB,EAAE,gDAAgD,IAAI,CAACC,KAAK,oBAAoB;MACnGC,gBAAgB,EAAEA,CAACC,KAAa,EAAEC,OAA0B,KAAK;QAC/D;QACA,MAAMC,UAAU,GAAGF,KAAK,CAACG,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;;QAE9C;QACA,IAAI,CAAC,kBAAkB,CAACC,IAAI,CAACF,UAAU,CAAC,EAAE;UACxC,OAAOD,OAAO,CAACI,KAAK,CAAC,qBAAqB,CAAC;QAC7C;;QAEA;QACA,MAAMC,OAAO,GAAGJ,UAAU,CAACK,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAMC,OAAO,GAAGN,UAAU,CAACK,SAAS,CAAC,CAAC,CAAC;QACvC,MAAME,cAAc,GAAG,GAAGH,OAAO,IAAIE,OAAO,CAACD,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAIC,OAAO,CAACD,SAAS,CAAC,CAAC,CAAC,EAAE;QAEtF,OAAOE,cAAc;MACvB;IACF,CAAC;EACH;EAEUC,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EACN;IACJ,CAAC,CACF;EACH;;EAEA;AACF;AACA;EACE,OAAOC,oBAAoBA,CAAA,EAAG;IAC5B,MAAMC,QAAQ,GAAGC,MAAM,CAACC,MAAM,CAC5BtB,4BAA4B,CAACuB,SAC/B,CAAiC;IACjC,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type OsGridRefFieldComponent } from '@defra/forms-model';
|
|
2
|
+
import type joi from 'joi';
|
|
3
|
+
import { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js';
|
|
4
|
+
export declare class OsGridRefField extends LocationFieldBase {
|
|
5
|
+
options: OsGridRefFieldComponent['options'];
|
|
6
|
+
protected getValidationConfig(): {
|
|
7
|
+
pattern: RegExp;
|
|
8
|
+
patternErrorMessage: string;
|
|
9
|
+
customValidation: (value: string, helpers: joi.CustomHelpers) => string | joi.ErrorReport;
|
|
10
|
+
};
|
|
11
|
+
protected getErrorTemplates(): {
|
|
12
|
+
type: string;
|
|
13
|
+
template: string;
|
|
14
|
+
}[];
|
|
15
|
+
/**
|
|
16
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
17
|
+
*/
|
|
18
|
+
static getAllPossibleErrors(): import("../types.js").ErrorMessageTemplateList;
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LocationFieldBase } from "./LocationFieldBase.js";
|
|
2
|
+
export class OsGridRefField extends LocationFieldBase {
|
|
3
|
+
getValidationConfig() {
|
|
4
|
+
// Regex for OS grid references and parcel IDs
|
|
5
|
+
// Validates specific valid OS grid letter combinations with:
|
|
6
|
+
// - 6 digits (e.g., SD865005 or SD 865 005)
|
|
7
|
+
// - 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789
|
|
8
|
+
// - 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345
|
|
9
|
+
const osGridPattern = /^(?:[sn][a-hj-z]|[to][abfglmqrvw]|h[l-z]|j[lmqrvw])\s?(?:\d{3}\s?\d{3}|\d{4}\s?\d{4}|\d{5}\s?\d{5})$/i;
|
|
10
|
+
|
|
11
|
+
// More permissive pattern for initial validation (allows spaces to be cleaned)
|
|
12
|
+
const initialPattern = /^[A-Za-z]{2}[\d\s]*$/;
|
|
13
|
+
return {
|
|
14
|
+
pattern: initialPattern,
|
|
15
|
+
patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`,
|
|
16
|
+
customValidation: (value, helpers) => {
|
|
17
|
+
// Strip spaces from the input for processing
|
|
18
|
+
const cleanValue = value.replace(/\s/g, '');
|
|
19
|
+
const letters = cleanValue.substring(0, 2);
|
|
20
|
+
const numbers = cleanValue.substring(2);
|
|
21
|
+
|
|
22
|
+
// Validate number length
|
|
23
|
+
if (numbers.length !== 6 && numbers.length !== 8 && numbers.length !== 10) {
|
|
24
|
+
return helpers.error('string.pattern.base');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Format with spaces: XX 123 456, XX 1234 5678, or XX 12345 67890
|
|
28
|
+
const halfLength = numbers.length / 2;
|
|
29
|
+
const formattedValue = `${letters} ${numbers.substring(0, halfLength)} ${numbers.substring(halfLength)}`;
|
|
30
|
+
|
|
31
|
+
// Validate the formatted value against the OS grid pattern
|
|
32
|
+
if (!osGridPattern.test(formattedValue)) {
|
|
33
|
+
return helpers.error('string.pattern.base');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Return formatted value with spaces per GDS guidance
|
|
37
|
+
return formattedValue;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
getErrorTemplates() {
|
|
42
|
+
return [{
|
|
43
|
+
type: 'pattern',
|
|
44
|
+
template: 'Enter a valid OS grid reference for [short description] like TQ123456'
|
|
45
|
+
}];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Static version of getAllPossibleErrors that doesn't require a component instance.
|
|
50
|
+
*/
|
|
51
|
+
static getAllPossibleErrors() {
|
|
52
|
+
const instance = Object.create(OsGridRefField.prototype);
|
|
53
|
+
return instance.getAllPossibleErrors();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=OsGridRefField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OsGridRefField.js","names":["LocationFieldBase","OsGridRefField","getValidationConfig","osGridPattern","initialPattern","pattern","patternErrorMessage","title","customValidation","value","helpers","cleanValue","replace","letters","substring","numbers","length","error","halfLength","formattedValue","test","getErrorTemplates","type","template","getAllPossibleErrors","instance","Object","create","prototype"],"sources":["../../../../../src/server/plugins/engine/components/OsGridRefField.ts"],"sourcesContent":["import { type OsGridRefFieldComponent } from '@defra/forms-model'\nimport type joi from 'joi'\n\nimport { LocationFieldBase } from '~/src/server/plugins/engine/components/LocationFieldBase.js'\n\nexport class OsGridRefField extends LocationFieldBase {\n declare options: OsGridRefFieldComponent['options']\n\n protected getValidationConfig() {\n // Regex for OS grid references and parcel IDs\n // Validates specific valid OS grid letter combinations with:\n // - 6 digits (e.g., SD865005 or SD 865 005)\n // - 8 digits in 2 blocks of 4 (parcel ID) e.g., ST 6789 6789\n // - 10 digits in 2 blocks of 5 (OS grid reference) e.g., SO 12345 12345\n const osGridPattern =\n /^(?:[sn][a-hj-z]|[to][abfglmqrvw]|h[l-z]|j[lmqrvw])\\s?(?:\\d{3}\\s?\\d{3}|\\d{4}\\s?\\d{4}|\\d{5}\\s?\\d{5})$/i\n\n // More permissive pattern for initial validation (allows spaces to be cleaned)\n const initialPattern = /^[A-Za-z]{2}[\\d\\s]*$/\n\n return {\n pattern: initialPattern,\n patternErrorMessage: `Enter a valid OS grid reference for ${this.title} like TQ123456`,\n customValidation: (value: string, helpers: joi.CustomHelpers) => {\n // Strip spaces from the input for processing\n const cleanValue = value.replace(/\\s/g, '')\n const letters = cleanValue.substring(0, 2)\n const numbers = cleanValue.substring(2)\n\n // Validate number length\n if (\n numbers.length !== 6 &&\n numbers.length !== 8 &&\n numbers.length !== 10\n ) {\n return helpers.error('string.pattern.base')\n }\n\n // Format with spaces: XX 123 456, XX 1234 5678, or XX 12345 67890\n const halfLength = numbers.length / 2\n const formattedValue = `${letters} ${numbers.substring(0, halfLength)} ${numbers.substring(halfLength)}`\n\n // Validate the formatted value against the OS grid pattern\n if (!osGridPattern.test(formattedValue)) {\n return helpers.error('string.pattern.base')\n }\n\n // Return formatted value with spaces per GDS guidance\n return formattedValue\n }\n }\n }\n\n protected getErrorTemplates() {\n return [\n {\n type: 'pattern',\n template:\n 'Enter a valid OS grid reference for [short description] like TQ123456'\n }\n ]\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors() {\n const instance = Object.create(OsGridRefField.prototype) as OsGridRefField\n return instance.getAllPossibleErrors()\n }\n}\n"],"mappings":"AAGA,SAASA,iBAAiB;AAE1B,OAAO,MAAMC,cAAc,SAASD,iBAAiB,CAAC;EAG1CE,mBAAmBA,CAAA,EAAG;IAC9B;IACA;IACA;IACA;IACA;IACA,MAAMC,aAAa,GACjB,uGAAuG;;IAEzG;IACA,MAAMC,cAAc,GAAG,sBAAsB;IAE7C,OAAO;MACLC,OAAO,EAAED,cAAc;MACvBE,mBAAmB,EAAE,uCAAuC,IAAI,CAACC,KAAK,gBAAgB;MACtFC,gBAAgB,EAAEA,CAACC,KAAa,EAAEC,OAA0B,KAAK;QAC/D;QACA,MAAMC,UAAU,GAAGF,KAAK,CAACG,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3C,MAAMC,OAAO,GAAGF,UAAU,CAACG,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAMC,OAAO,GAAGJ,UAAU,CAACG,SAAS,CAAC,CAAC,CAAC;;QAEvC;QACA,IACEC,OAAO,CAACC,MAAM,KAAK,CAAC,IACpBD,OAAO,CAACC,MAAM,KAAK,CAAC,IACpBD,OAAO,CAACC,MAAM,KAAK,EAAE,EACrB;UACA,OAAON,OAAO,CAACO,KAAK,CAAC,qBAAqB,CAAC;QAC7C;;QAEA;QACA,MAAMC,UAAU,GAAGH,OAAO,CAACC,MAAM,GAAG,CAAC;QACrC,MAAMG,cAAc,GAAG,GAAGN,OAAO,IAAIE,OAAO,CAACD,SAAS,CAAC,CAAC,EAAEI,UAAU,CAAC,IAAIH,OAAO,CAACD,SAAS,CAACI,UAAU,CAAC,EAAE;;QAExG;QACA,IAAI,CAACf,aAAa,CAACiB,IAAI,CAACD,cAAc,CAAC,EAAE;UACvC,OAAOT,OAAO,CAACO,KAAK,CAAC,qBAAqB,CAAC;QAC7C;;QAEA;QACA,OAAOE,cAAc;MACvB;IACF,CAAC;EACH;EAEUE,iBAAiBA,CAAA,EAAG;IAC5B,OAAO,CACL;MACEC,IAAI,EAAE,SAAS;MACfC,QAAQ,EACN;IACJ,CAAC,CACF;EACH;;EAEA;AACF;AACA;EACE,OAAOC,oBAAoBA,CAAA,EAAG;IAC5B,MAAMC,QAAQ,GAAGC,MAAM,CAACC,MAAM,CAAC1B,cAAc,CAAC2B,SAAS,CAAmB;IAC1E,OAAOH,QAAQ,CAACD,oBAAoB,CAAC,CAAC;EACxC;AACF","ignoreList":[]}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { ComponentType, type ComponentDef } from '@defra/forms-model';
|
|
2
|
-
import { Marked } from 'marked';
|
|
3
2
|
import { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js';
|
|
4
3
|
import { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js';
|
|
5
4
|
import * as Components from '~/src/server/plugins/engine/components/index.js';
|
|
6
5
|
import { type FormState } from '~/src/server/plugins/engine/types.js';
|
|
7
6
|
export type Component = InstanceType<(typeof Components)[keyof typeof Components]>;
|
|
8
|
-
export type Field = InstanceType<typeof Components.AutocompleteField | typeof Components.RadiosField | typeof Components.YesNoField | typeof Components.CheckboxesField | typeof Components.DatePartsField | typeof Components.EmailAddressField | typeof Components.MonthYearField | typeof Components.MultilineTextField | typeof Components.NumberField | typeof Components.SelectField | typeof Components.TelephoneNumberField | typeof Components.TextField | typeof Components.UkAddressField | typeof Components.FileUploadField>;
|
|
9
|
-
export type Guidance = InstanceType<typeof Components.Details | typeof Components.Html | typeof Components.Markdown | typeof Components.InsetText | typeof Components.List>;
|
|
7
|
+
export type Field = InstanceType<typeof Components.AutocompleteField | typeof Components.RadiosField | typeof Components.YesNoField | typeof Components.CheckboxesField | typeof Components.DatePartsField | typeof Components.DeclarationField | typeof Components.EastingNorthingField | typeof Components.EmailAddressField | typeof Components.LatLongField | typeof Components.MonthYearField | typeof Components.MultilineTextField | typeof Components.NationalGridFieldNumberField | typeof Components.NumberField | typeof Components.OsGridRefField | typeof Components.SelectField | typeof Components.TelephoneNumberField | typeof Components.TextField | typeof Components.UkAddressField | typeof Components.FileUploadField>;
|
|
8
|
+
export type Guidance = InstanceType<typeof Components.Details> | InstanceType<typeof Components.Html> | InstanceType<typeof Components.Markdown> | InstanceType<typeof Components.InsetText> | InstanceType<typeof Components.List>;
|
|
10
9
|
export type ListField = InstanceType<typeof Components.AutocompleteField | typeof Components.CheckboxesField | typeof Components.RadiosField | typeof Components.SelectField | typeof Components.YesNoField>;
|
|
11
10
|
export declare const designerUrl: string;
|
|
12
|
-
export
|
|
11
|
+
export { markdown } from '~/src/server/plugins/engine/components/markdownParser.js';
|
|
13
12
|
/**
|
|
14
13
|
* Filter known components with lists
|
|
15
14
|
*/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ComponentType } from '@defra/forms-model';
|
|
2
|
-
import { Marked } from 'marked';
|
|
3
2
|
import { config } from "../../../../../config/index.js";
|
|
4
3
|
import { ListFormComponent } from "../ListFormComponent.js";
|
|
5
4
|
import { escapeMarkdown } from "./index.js";
|
|
6
5
|
import * as Components from "../index.js";
|
|
6
|
+
import { markdown } from "../markdownParser.js";
|
|
7
7
|
|
|
8
8
|
// All component instances
|
|
9
9
|
|
|
@@ -14,38 +14,15 @@ import * as Components from "../index.js";
|
|
|
14
14
|
// List component instances only
|
|
15
15
|
|
|
16
16
|
export const designerUrl = config.get('designerUrl');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Render paragraphs without `<p>` wrappers
|
|
22
|
-
* for check answers summary list `<dd>`
|
|
23
|
-
*/
|
|
24
|
-
extensions: [{
|
|
25
|
-
name: 'paragraph',
|
|
26
|
-
renderer({
|
|
27
|
-
tokens = []
|
|
28
|
-
}) {
|
|
29
|
-
const text = this.parser.parseInline(tokens);
|
|
30
|
-
return tokens.length > 1 ? `${text}<br>` : text;
|
|
31
|
-
}
|
|
32
|
-
}],
|
|
33
|
-
/**
|
|
34
|
-
* Restrict allowed Markdown tokens
|
|
35
|
-
*/
|
|
36
|
-
walkTokens(token) {
|
|
37
|
-
const tokens = ['br', 'escape', 'list', 'list_item', 'paragraph', 'space', 'text'];
|
|
38
|
-
if (!tokens.includes(token.type)) {
|
|
39
|
-
token.type = 'text';
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
17
|
+
|
|
18
|
+
// Re-export markdown from its own module to avoid circular dependencies
|
|
19
|
+
export { markdown } from "../markdownParser.js";
|
|
43
20
|
|
|
44
21
|
/**
|
|
45
22
|
* Filter known components with lists
|
|
46
23
|
*/
|
|
47
24
|
export function hasListFormField(field) {
|
|
48
|
-
return !!field && isListFieldType(field.type);
|
|
25
|
+
return !!field && field.type !== undefined && isListFieldType(field.type);
|
|
49
26
|
}
|
|
50
27
|
export function isListFieldType(type) {
|
|
51
28
|
const allowedTypes = [ComponentType.AutocompleteField, ComponentType.CheckboxesField, ComponentType.RadiosField, ComponentType.SelectField, ComponentType.YesNoField];
|
|
@@ -67,6 +44,9 @@ export function createComponent(def, options) {
|
|
|
67
44
|
case ComponentType.DatePartsField:
|
|
68
45
|
component = new Components.DatePartsField(def, options);
|
|
69
46
|
break;
|
|
47
|
+
case ComponentType.DeclarationField:
|
|
48
|
+
component = new Components.DeclarationField(def, options);
|
|
49
|
+
break;
|
|
70
50
|
case ComponentType.Details:
|
|
71
51
|
component = new Components.Details(def, options);
|
|
72
52
|
break;
|
|
@@ -115,6 +95,18 @@ export function createComponent(def, options) {
|
|
|
115
95
|
case ComponentType.FileUploadField:
|
|
116
96
|
component = new Components.FileUploadField(def, options);
|
|
117
97
|
break;
|
|
98
|
+
case ComponentType.EastingNorthingField:
|
|
99
|
+
component = new Components.EastingNorthingField(def, options);
|
|
100
|
+
break;
|
|
101
|
+
case ComponentType.OsGridRefField:
|
|
102
|
+
component = new Components.OsGridRefField(def, options);
|
|
103
|
+
break;
|
|
104
|
+
case ComponentType.NationalGridFieldNumberField:
|
|
105
|
+
component = new Components.NationalGridFieldNumberField(def, options);
|
|
106
|
+
break;
|
|
107
|
+
case ComponentType.LatLongField:
|
|
108
|
+
component = new Components.LatLongField(def, options);
|
|
109
|
+
break;
|
|
118
110
|
}
|
|
119
111
|
if (typeof component === 'undefined') {
|
|
120
112
|
throw new Error(`Component type ${def.type} does not exist`);
|
|
@@ -142,7 +134,7 @@ export function getAnswer(field, state, options = {
|
|
|
142
134
|
}
|
|
143
135
|
|
|
144
136
|
// Use display HTML for check answers summary (multi line)
|
|
145
|
-
if (field instanceof ListFormComponent || field instanceof Components.MultilineTextField || field instanceof Components.UkAddressField) {
|
|
137
|
+
if (field instanceof ListFormComponent || field instanceof Components.MultilineTextField || field instanceof Components.UkAddressField || field instanceof Components.EastingNorthingField || field instanceof Components.LatLongField) {
|
|
146
138
|
return markdown.parse(getAnswerMarkdown(field, state), {
|
|
147
139
|
async: false
|
|
148
140
|
}).trim();
|
|
@@ -214,6 +206,9 @@ export function getAnswerMarkdown(field, state, options = {
|
|
|
214
206
|
} else if (field instanceof Components.UkAddressField) {
|
|
215
207
|
// Format UK addresses into new lines
|
|
216
208
|
answerEscaped = (field.getContextValueFromState(state) ?? []).map(escapeMarkdown).join('\n').concat('\n');
|
|
209
|
+
} else if (field instanceof Components.EastingNorthingField || field instanceof Components.LatLongField) {
|
|
210
|
+
const contextValue = field.getContextValueFromState(state);
|
|
211
|
+
answerEscaped = contextValue ? `${contextValue}\n` : '';
|
|
217
212
|
}
|
|
218
213
|
return answerEscaped;
|
|
219
214
|
}
|