@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.
Files changed (112) hide show
  1. package/.public/stylesheets/application.min.css +1 -1
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/_location-input.scss +60 -0
  4. package/.server/client/stylesheets/application.scss +1 -0
  5. package/.server/client/stylesheets/shared.scss +1 -0
  6. package/.server/server/forms/components.json +7 -0
  7. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
  8. package/.server/server/plugins/engine/components/ComponentBase.d.ts +2 -2
  9. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  10. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  11. package/.server/server/plugins/engine/components/DeclarationField.d.ts +81 -0
  12. package/.server/server/plugins/engine/components/DeclarationField.js +123 -0
  13. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -0
  14. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
  15. package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
  16. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
  17. package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
  18. package/.server/server/plugins/engine/components/LatLongField.js +164 -0
  19. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
  20. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
  21. package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
  22. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
  23. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
  24. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
  25. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
  26. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
  27. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
  28. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
  29. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
  30. package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
  31. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
  32. package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
  33. package/.server/server/plugins/engine/components/helpers/components.js +24 -29
  34. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  35. package/.server/server/plugins/engine/components/index.d.ts +5 -0
  36. package/.server/server/plugins/engine/components/index.js +5 -0
  37. package/.server/server/plugins/engine/components/index.js.map +1 -1
  38. package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
  39. package/.server/server/plugins/engine/components/markdownParser.js +28 -0
  40. package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
  41. package/.server/server/plugins/engine/components/types.d.ts +10 -0
  42. package/.server/server/plugins/engine/components/types.js.map +1 -1
  43. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
  44. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  45. package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -0
  46. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  47. package/.server/server/plugins/engine/types/index.d.ts +1 -1
  48. package/.server/server/plugins/engine/types/index.js.map +1 -1
  49. package/.server/server/plugins/engine/types.d.ts +2 -2
  50. package/.server/server/plugins/engine/types.js.map +1 -1
  51. package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
  52. package/.server/server/plugins/engine/views/components/declarationfield.html +14 -0
  53. package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  54. package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
  55. package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  56. package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
  57. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  58. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  59. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  60. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  61. package/.server/server/plugins/nunjucks/filters/merge.d.ts +7 -0
  62. package/.server/server/plugins/nunjucks/filters/merge.js +16 -0
  63. package/.server/server/plugins/nunjucks/filters/merge.js.map +1 -0
  64. package/.server/server/plugins/nunjucks/filters/merge.test.js +19 -0
  65. package/.server/server/plugins/nunjucks/filters/merge.test.js.map +1 -0
  66. package/package.json +3 -3
  67. package/src/client/stylesheets/_location-input.scss +60 -0
  68. package/src/client/stylesheets/application.scss +1 -0
  69. package/src/client/stylesheets/shared.scss +1 -0
  70. package/src/server/forms/components.json +7 -0
  71. package/src/server/forms/page-events.yaml +1 -1
  72. package/src/server/forms/register-as-a-unicorn-breeder.yaml +40 -1
  73. package/src/server/index.test.ts +1 -0
  74. package/src/server/plugins/engine/components/ComponentBase.ts +2 -1
  75. package/src/server/plugins/engine/components/ComponentCollection.ts +1 -0
  76. package/src/server/plugins/engine/components/DeclarationField.test.ts +426 -0
  77. package/src/server/plugins/engine/components/DeclarationField.ts +167 -0
  78. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
  79. package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
  80. package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
  81. package/src/server/plugins/engine/components/LatLongField.ts +213 -0
  82. package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
  83. package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
  84. package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
  85. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
  86. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
  87. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
  88. package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
  89. package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
  90. package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
  91. package/src/server/plugins/engine/components/helpers/components.ts +44 -47
  92. package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
  93. package/src/server/plugins/engine/components/index.ts +5 -0
  94. package/src/server/plugins/engine/components/markdownParser.ts +40 -0
  95. package/src/server/plugins/engine/components/types.ts +14 -0
  96. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +76 -3
  97. package/src/server/plugins/engine/models/SummaryViewModel.ts +5 -1
  98. package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
  99. package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
  100. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
  101. package/src/server/plugins/engine/pageControllers/validationOptions.ts +4 -0
  102. package/src/server/plugins/engine/types/index.ts +2 -0
  103. package/src/server/plugins/engine/types.ts +4 -0
  104. package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
  105. package/src/server/plugins/engine/views/components/declarationfield.html +14 -0
  106. package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  107. package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
  108. package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  109. package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
  110. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  111. package/src/server/plugins/nunjucks/filters/merge.js +16 -0
  112. 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 declare const markdown: Marked;
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
- export const markdown = new Marked({
18
- breaks: true,
19
- gfm: true,
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
  }