@defra/forms-engine-plugin 4.0.9 → 4.0.10

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.
@@ -8,9 +8,21 @@ import { convertToLanguageMessages } from "../../../utils/type-utils.js";
8
8
 
9
9
  // British National Grid coordinate limits
10
10
  const DEFAULT_EASTING_MIN = 0;
11
- const DEFAULT_EASTING_MAX = 70000;
11
+ const DEFAULT_EASTING_MAX = 700000;
12
12
  const DEFAULT_NORTHING_MIN = 0;
13
13
  const DEFAULT_NORTHING_MAX = 1300000;
14
+
15
+ // Easting length constraints (integer values only, no decimals)
16
+ // Min: 1 char for values like "0" or single digit values
17
+ // Max: 6 chars for values up to 700000 (British National Grid easting limit)
18
+ const EASTING_MIN_LENGTH = 1;
19
+ const EASTING_MAX_LENGTH = 6;
20
+
21
+ // Northing length constraints (integer values only, no decimals)
22
+ // Min: 1 char for values like "0" or single digit values
23
+ // Max: 7 chars for values up to 1300000 (British National Grid northing limit)
24
+ const NORTHING_MIN_LENGTH = 1;
25
+ const NORTHING_MAX_LENGTH = 7;
14
26
  export class EastingNorthingField extends FormComponent {
15
27
  constructor(def, props) {
16
28
  super(def, props);
@@ -29,9 +41,11 @@ export class EastingNorthingField extends FormComponent {
29
41
  'number.base': messageTemplate.objectMissing,
30
42
  'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${eastingMax}`,
31
43
  'number.max': `{{#label}} for ${this.title} must be between ${eastingMin} and {{#limit}}`,
32
- 'number.precision': `{{#label}} for ${this.title} must be between 1 and 5 digits`,
33
- 'number.integer': `{{#label}} for ${this.title} must be between 1 and 5 digits`,
34
- 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 5 digits`
44
+ 'number.precision': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
45
+ 'number.integer': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
46
+ 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
47
+ 'number.minLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`,
48
+ 'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`
35
49
  });
36
50
  const northingValidationMessages = convertToLanguageMessages({
37
51
  'any.required': messageTemplate.objectMissing,
@@ -40,7 +54,9 @@ export class EastingNorthingField extends FormComponent {
40
54
  'number.max': `{{#label}} for ${this.title} must be between ${northingMin} and {{#limit}}`,
41
55
  'number.precision': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
42
56
  'number.integer': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
43
- 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`
57
+ 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
58
+ 'number.minLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`,
59
+ 'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`
44
60
  });
45
61
  this.collection = new ComponentCollection([{
46
62
  type: ComponentType.NumberField,
@@ -49,7 +65,9 @@ export class EastingNorthingField extends FormComponent {
49
65
  schema: {
50
66
  min: eastingMin,
51
67
  max: eastingMax,
52
- precision: 0
68
+ precision: 0,
69
+ minLength: EASTING_MIN_LENGTH,
70
+ maxLength: EASTING_MAX_LENGTH
53
71
  },
54
72
  options: {
55
73
  required: isRequired,
@@ -64,7 +82,9 @@ export class EastingNorthingField extends FormComponent {
64
82
  schema: {
65
83
  min: northingMin,
66
84
  max: northingMax,
67
- precision: 0
85
+ precision: 0,
86
+ minLength: NORTHING_MIN_LENGTH,
87
+ maxLength: NORTHING_MAX_LENGTH
68
88
  },
69
89
  options: {
70
90
  required: isRequired,
@@ -136,17 +156,17 @@ export class EastingNorthingField extends FormComponent {
136
156
  template: messageTemplate.required
137
157
  }, {
138
158
  type: 'eastingFormat',
139
- template: 'Easting for [short description] must be between 1 and 5 digits'
159
+ template: 'Easting for [short description] must be between 1 and 6 digits'
140
160
  }, {
141
161
  type: 'northingFormat',
142
162
  template: 'Northing for [short description] must be between 1 and 7 digits'
143
163
  }],
144
164
  advancedSettingsErrors: [{
145
165
  type: 'eastingMin',
146
- template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
166
+ template: `Easting for [short description] must be between 0 and 700000`
147
167
  }, {
148
168
  type: 'eastingMax',
149
- template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
169
+ template: `Easting for [short description] must be between 0 and 700000`
150
170
  }, {
151
171
  type: 'northingMin',
152
172
  template: `Northing for [short description] must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
@@ -1 +1 @@
1
- {"version":3,"file":"EastingNorthingField.js","names":["ComponentType","ComponentCollection","FormComponent","isFormState","createLocationFieldValidator","getLocationFieldViewModel","NumberField","messageTemplate","convertToLanguageMessages","DEFAULT_EASTING_MIN","DEFAULT_EASTING_MAX","DEFAULT_NORTHING_MIN","DEFAULT_NORTHING_MAX","EastingNorthingField","constructor","def","props","name","options","schema","isRequired","required","eastingMin","easting","min","eastingMax","max","northingMin","northing","northingMax","customValidationMessages","objectMissing","title","northingValidationMessages","collection","type","precision","optionalText","classes","parent","custom","getValidatorEastingNorthing","peers","formSchema","stateSchema","getFormValueFromState","state","value","isEastingNorthing","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber","component"],"sources":["../../../../../src/server/plugins/engine/components/EastingNorthingField.ts"],"sourcesContent":["import {\n ComponentType,\n type EastingNorthingFieldComponent\n} from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n createLocationFieldValidator,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// British National Grid coordinate limits\nconst DEFAULT_EASTING_MIN = 0\nconst DEFAULT_EASTING_MAX = 70000\nconst DEFAULT_NORTHING_MIN = 0\nconst DEFAULT_NORTHING_MAX = 1300000\n\nexport class EastingNorthingField extends FormComponent {\n declare options: EastingNorthingFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: EastingNorthingFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n const eastingMin = schema?.easting?.min ?? DEFAULT_EASTING_MIN\n const eastingMax = schema?.easting?.max ?? DEFAULT_EASTING_MAX\n const northingMin = schema?.northing?.min ?? DEFAULT_NORTHING_MIN\n const northingMax = schema?.northing?.max ?? DEFAULT_NORTHING_MAX\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${eastingMax}`,\n 'number.max': `{{#label}} for ${this.title} must be between ${eastingMin} and {{#limit}}`,\n 'number.precision': `{{#label}} for ${this.title} must be between 1 and 5 digits`,\n 'number.integer': `{{#label}} for ${this.title} must be between 1 and 5 digits`,\n 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 5 digits`\n })\n\n const northingValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${northingMax}`,\n 'number.max': `{{#label}} for ${this.title} must be between ${northingMin} and {{#limit}}`,\n 'number.precision': `{{#label}} for ${this.title} must be between 1 and 7 digits`,\n 'number.integer': `{{#label}} for ${this.title} must be between 1 and 7 digits`,\n 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__easting`,\n title: 'Easting',\n schema: { min: eastingMin, max: eastingMax, precision: 0 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__northing`,\n title: 'Northing',\n schema: { min: northingMin, max: northingMax, precision: 0 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages: northingValidationMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n custom: getValidatorEastingNorthing(this),\n peers: [`${name}__easting`, `${name}__northing`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return EastingNorthingField.isEastingNorthing(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: EastingNorthingState | undefined\n ): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<northingvalue, eastingvalue>>\n return `${value.northing}, ${value.easting}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(\n value: EastingNorthingState | undefined\n ): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Northing: <<entry>>\\nEasting: <<entry>>\n return `Northing: ${value.northing}\\nEasting: ${value.easting}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return EastingNorthingField.isEastingNorthing(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return EastingNorthingField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'eastingFormat',\n template:\n 'Easting for [short description] must be between 1 and 5 digits'\n },\n {\n type: 'northingFormat',\n template:\n 'Northing for [short description] must be between 1 and 7 digits'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'eastingMin',\n template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`\n },\n {\n type: 'eastingMax',\n template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`\n },\n {\n type: 'northingMin',\n template: `Northing for [short description] must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n },\n {\n type: 'northingMax',\n template: `Northing for [short description] must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n }\n ]\n }\n }\n\n static isEastingNorthing(\n value?: FormStateValue | FormState\n ): value is EastingNorthingState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.easting) &&\n NumberField.isNumber(value.northing)\n )\n }\n}\n\nexport function getValidatorEastingNorthing(component: EastingNorthingField) {\n return createLocationFieldValidator(component)\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"EastingNorthingField.js","names":["ComponentType","ComponentCollection","FormComponent","isFormState","createLocationFieldValidator","getLocationFieldViewModel","NumberField","messageTemplate","convertToLanguageMessages","DEFAULT_EASTING_MIN","DEFAULT_EASTING_MAX","DEFAULT_NORTHING_MIN","DEFAULT_NORTHING_MAX","EASTING_MIN_LENGTH","EASTING_MAX_LENGTH","NORTHING_MIN_LENGTH","NORTHING_MAX_LENGTH","EastingNorthingField","constructor","def","props","name","options","schema","isRequired","required","eastingMin","easting","min","eastingMax","max","northingMin","northing","northingMax","customValidationMessages","objectMissing","title","northingValidationMessages","collection","type","precision","minLength","maxLength","optionalText","classes","parent","custom","getValidatorEastingNorthing","peers","formSchema","stateSchema","getFormValueFromState","state","value","isEastingNorthing","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber","component"],"sources":["../../../../../src/server/plugins/engine/components/EastingNorthingField.ts"],"sourcesContent":["import {\n ComponentType,\n type EastingNorthingFieldComponent\n} from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n createLocationFieldValidator,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// British National Grid coordinate limits\nconst DEFAULT_EASTING_MIN = 0\nconst DEFAULT_EASTING_MAX = 700000\nconst DEFAULT_NORTHING_MIN = 0\nconst DEFAULT_NORTHING_MAX = 1300000\n\n// Easting length constraints (integer values only, no decimals)\n// Min: 1 char for values like \"0\" or single digit values\n// Max: 6 chars for values up to 700000 (British National Grid easting limit)\nconst EASTING_MIN_LENGTH = 1\nconst EASTING_MAX_LENGTH = 6\n\n// Northing length constraints (integer values only, no decimals)\n// Min: 1 char for values like \"0\" or single digit values\n// Max: 7 chars for values up to 1300000 (British National Grid northing limit)\nconst NORTHING_MIN_LENGTH = 1\nconst NORTHING_MAX_LENGTH = 7\n\nexport class EastingNorthingField extends FormComponent {\n declare options: EastingNorthingFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: EastingNorthingFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n const eastingMin = schema?.easting?.min ?? DEFAULT_EASTING_MIN\n const eastingMax = schema?.easting?.max ?? DEFAULT_EASTING_MAX\n const northingMin = schema?.northing?.min ?? DEFAULT_NORTHING_MIN\n const northingMax = schema?.northing?.max ?? DEFAULT_NORTHING_MAX\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${eastingMax}`,\n 'number.max': `{{#label}} for ${this.title} must be between ${eastingMin} and {{#limit}}`,\n 'number.precision': `{{#label}} for ${this.title} must be between 1 and 6 digits`,\n 'number.integer': `{{#label}} for ${this.title} must be between 1 and 6 digits`,\n 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 6 digits`,\n 'number.minLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`,\n 'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 6 digits`\n })\n\n const northingValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.min': `{{#label}} for ${this.title} must be between {{#limit}} and ${northingMax}`,\n 'number.max': `{{#label}} for ${this.title} must be between ${northingMin} and {{#limit}}`,\n 'number.precision': `{{#label}} for ${this.title} must be between 1 and 7 digits`,\n 'number.integer': `{{#label}} for ${this.title} must be between 1 and 7 digits`,\n 'number.unsafe': `{{#label}} for ${this.title} must be between 1 and 7 digits`,\n 'number.minLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`,\n 'number.maxLength': `{{#label}} for ${this.title} must be between 1 and 7 digits`\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__easting`,\n title: 'Easting',\n schema: {\n min: eastingMin,\n max: eastingMax,\n precision: 0,\n minLength: EASTING_MIN_LENGTH,\n maxLength: EASTING_MAX_LENGTH\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__northing`,\n title: 'Northing',\n schema: {\n min: northingMin,\n max: northingMax,\n precision: 0,\n minLength: NORTHING_MIN_LENGTH,\n maxLength: NORTHING_MAX_LENGTH\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n customValidationMessages: northingValidationMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n custom: getValidatorEastingNorthing(this),\n peers: [`${name}__easting`, `${name}__northing`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return EastingNorthingField.isEastingNorthing(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(\n value: EastingNorthingState | undefined\n ): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<northingvalue, eastingvalue>>\n return `${value.northing}, ${value.easting}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(\n value: EastingNorthingState | undefined\n ): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Northing: <<entry>>\\nEasting: <<entry>>\n return `Northing: ${value.northing}\\nEasting: ${value.easting}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return EastingNorthingField.isEastingNorthing(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return EastingNorthingField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'eastingFormat',\n template:\n 'Easting for [short description] must be between 1 and 6 digits'\n },\n {\n type: 'northingFormat',\n template:\n 'Northing for [short description] must be between 1 and 7 digits'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'eastingMin',\n template: `Easting for [short description] must be between 0 and 700000`\n },\n {\n type: 'eastingMax',\n template: `Easting for [short description] must be between 0 and 700000`\n },\n {\n type: 'northingMin',\n template: `Northing for [short description] must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n },\n {\n type: 'northingMax',\n template: `Northing for [short description] must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`\n }\n ]\n }\n }\n\n static isEastingNorthing(\n value?: FormStateValue | FormState\n ): value is EastingNorthingState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.easting) &&\n NumberField.isNumber(value.northing)\n )\n }\n}\n\nexport function getValidatorEastingNorthing(component: EastingNorthingField) {\n return createLocationFieldValidator(component)\n}\n"],"mappings":"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","ignoreList":[]}
@@ -5,6 +5,23 @@ import { createLocationFieldValidator, getLocationFieldViewModel } from "./Locat
5
5
  import { NumberField } from "./NumberField.js";
6
6
  import { messageTemplate } from "../pageControllers/validationOptions.js";
7
7
  import { convertToLanguageMessages } from "../../../utils/type-utils.js";
8
+
9
+ // Precision constants
10
+ // UK latitude/longitude requires high precision for accurate location (within ~11mm)
11
+ const DECIMAL_PRECISION = 7; // 7 decimal places
12
+ const MIN_DECIMAL_PLACES = 1; // At least 1 decimal place required
13
+
14
+ // Latitude length constraints
15
+ // Min: 3 chars for values like "52.1" (2 digits + decimal + 1 decimal place)
16
+ // Max: 10 chars for values like "59.1234567" (2 digits + decimal + 7 decimal places)
17
+ const LATITUDE_MIN_LENGTH = 3;
18
+ const LATITUDE_MAX_LENGTH = 10;
19
+
20
+ // Longitude length constraints
21
+ // Min: 2 chars for values like "-1" or single digit with decimal (needs min decimal places)
22
+ // Max: 10 chars for values like "-1.1234567" (minus + 1 digit + decimal + 7 decimal places)
23
+ const LONGITUDE_MIN_LENGTH = 2;
24
+ const LONGITUDE_MAX_LENGTH = 10;
8
25
  export class LatLongField extends FormComponent {
9
26
  constructor(def, props) {
10
27
  super(def, props);
@@ -24,19 +41,24 @@ export class LatLongField extends FormComponent {
24
41
  'any.required': messageTemplate.objectMissing,
25
42
  'number.base': messageTemplate.objectMissing,
26
43
  'number.precision': '{{#label}} must have no more than 7 decimal places',
44
+ 'number.minPrecision': '{{#label}} must have at least {{#minPrecision}} decimal place',
27
45
  'number.unsafe': '{{#label}} must be a valid number'
28
46
  });
29
47
  const latitudeMessages = convertToLanguageMessages({
30
48
  ...customValidationMessages,
31
49
  'number.base': `Enter a valid latitude for ${this.title} like 51.519450`,
32
50
  'number.min': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,
33
- 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`
51
+ 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,
52
+ 'number.minLength': `Latitude for ${this.title} must be between 3 and 10 characters`,
53
+ 'number.maxLength': `Latitude for ${this.title} must be between 3 and 10 characters`
34
54
  });
35
55
  const longitudeMessages = convertToLanguageMessages({
36
56
  ...customValidationMessages,
37
57
  'number.base': `Enter a valid longitude for ${this.title} like -0.127758`,
38
58
  'number.min': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,
39
- 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`
59
+ 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,
60
+ 'number.minLength': `Longitude for ${this.title} must be between 2 and 10 characters`,
61
+ 'number.maxLength': `Longitude for ${this.title} must be between 2 and 10 characters`
40
62
  });
41
63
  this.collection = new ComponentCollection([{
42
64
  type: ComponentType.NumberField,
@@ -45,7 +67,10 @@ export class LatLongField extends FormComponent {
45
67
  schema: {
46
68
  min: latitudeMin,
47
69
  max: latitudeMax,
48
- precision: 7
70
+ precision: DECIMAL_PRECISION,
71
+ minPrecision: MIN_DECIMAL_PLACES,
72
+ minLength: LATITUDE_MIN_LENGTH,
73
+ maxLength: LATITUDE_MAX_LENGTH
49
74
  },
50
75
  options: {
51
76
  required: isRequired,
@@ -61,7 +86,10 @@ export class LatLongField extends FormComponent {
61
86
  schema: {
62
87
  min: longitudeMin,
63
88
  max: longitudeMax,
64
- precision: 7
89
+ precision: DECIMAL_PRECISION,
90
+ minPrecision: MIN_DECIMAL_PLACES,
91
+ minLength: LONGITUDE_MIN_LENGTH,
92
+ maxLength: LONGITUDE_MAX_LENGTH
65
93
  },
66
94
  options: {
67
95
  required: isRequired,
@@ -1 +1 @@
1
- {"version":3,"file":"LatLongField.js","names":["ComponentType","ComponentCollection","FormComponent","isFormState","createLocationFieldValidator","getLocationFieldViewModel","NumberField","messageTemplate","convertToLanguageMessages","LatLongField","constructor","def","props","name","options","schema","isRequired","required","latitudeMin","latitude","min","latitudeMax","max","longitudeMin","longitude","longitudeMax","customValidationMessages","objectMissing","latitudeMessages","title","longitudeMessages","collection","type","precision","optionalText","classes","suffix","parent","custom","getValidatorLatLong","peers","formSchema","stateSchema","getFormValueFromState","state","value","isLatLong","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber","component"],"sources":["../../../../../src/server/plugins/engine/components/LatLongField.ts"],"sourcesContent":["import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n createLocationFieldValidator,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { type LatLongState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\nexport class LatLongField extends FormComponent {\n declare options: LatLongFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: LatLongFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n // Read schema values from def.schema with fallback defaults\n const latitudeMin = schema?.latitude?.min ?? 49\n const latitudeMax = schema?.latitude?.max ?? 60\n const longitudeMin = schema?.longitude?.min ?? -9\n const longitudeMax = schema?.longitude?.max ?? 2\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.precision':\n '{{#label}} must have no more than 7 decimal places',\n 'number.unsafe': '{{#label}} must be a valid number'\n })\n\n const latitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'number.base': `Enter a valid latitude for ${this.title} like 51.519450`,\n 'number.min': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,\n 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`\n })\n\n const longitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'number.base': `Enter a valid longitude for ${this.title} like -0.127758`,\n 'number.min': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,\n 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__latitude`,\n title: 'Latitude',\n schema: { min: latitudeMin, max: latitudeMax, precision: 7 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: latitudeMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__longitude`,\n title: 'Longitude',\n schema: { min: longitudeMin, max: longitudeMax, precision: 7 },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: longitudeMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n custom: getValidatorLatLong(this),\n peers: [`${name}__latitude`, `${name}__longitude`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return LatLongField.isLatLong(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(value: LatLongState | undefined): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<latvalue, langvalue>>\n return `${value.latitude}, ${value.longitude}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(value: LatLongState | undefined): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Lat: <<entry>>\\nLong: <<entry>>\n return `Lat: ${value.latitude}\\nLong: ${value.longitude}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return LatLongField.isLatLong(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return LatLongField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'latitudeFormat',\n template:\n 'Enter a valid latitude for [short description] like 51.519450'\n },\n {\n type: 'longitudeFormat',\n template:\n 'Enter a valid longitude for [short description] like -0.127758'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'latitudeMin',\n template: 'Latitude for [short description] must be between 49 and 60'\n },\n {\n type: 'latitudeMax',\n template: 'Latitude for [short description] must be between 49 and 60'\n },\n {\n type: 'longitudeMin',\n template: 'Longitude for [short description] must be between -9 and 2'\n },\n {\n type: 'longitudeMax',\n template: 'Longitude for [short description] must be between -9 and 2'\n }\n ]\n }\n }\n\n static isLatLong(value?: FormStateValue | FormState): value is LatLongState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.latitude) &&\n NumberField.isNumber(value.longitude)\n )\n }\n}\n\nexport function getValidatorLatLong(component: LatLongField) {\n return createLocationFieldValidator(component)\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"LatLongField.js","names":["ComponentType","ComponentCollection","FormComponent","isFormState","createLocationFieldValidator","getLocationFieldViewModel","NumberField","messageTemplate","convertToLanguageMessages","DECIMAL_PRECISION","MIN_DECIMAL_PLACES","LATITUDE_MIN_LENGTH","LATITUDE_MAX_LENGTH","LONGITUDE_MIN_LENGTH","LONGITUDE_MAX_LENGTH","LatLongField","constructor","def","props","name","options","schema","isRequired","required","latitudeMin","latitude","min","latitudeMax","max","longitudeMin","longitude","longitudeMax","customValidationMessages","objectMissing","latitudeMessages","title","longitudeMessages","collection","type","precision","minPrecision","minLength","maxLength","optionalText","classes","suffix","parent","custom","getValidatorLatLong","peers","formSchema","stateSchema","getFormValueFromState","state","value","isLatLong","undefined","getDisplayStringFromFormValue","getDisplayStringFromState","getContextValueFromFormValue","getContextValueFromState","getViewModel","payload","errors","viewModel","isState","getAllPossibleErrors","baseErrors","template","advancedSettingsErrors","isNumber","component"],"sources":["../../../../../src/server/plugins/engine/components/LatLongField.ts"],"sourcesContent":["import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'\nimport { type LanguageMessages, type ObjectSchema } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n createLocationFieldValidator,\n getLocationFieldViewModel\n} from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nimport { type LatLongState } from '~/src/server/plugins/engine/components/types.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'\nimport { convertToLanguageMessages } from '~/src/server/utils/type-utils.js'\n\n// Precision constants\n// UK latitude/longitude requires high precision for accurate location (within ~11mm)\nconst DECIMAL_PRECISION = 7 // 7 decimal places\nconst MIN_DECIMAL_PLACES = 1 // At least 1 decimal place required\n\n// Latitude length constraints\n// Min: 3 chars for values like \"52.1\" (2 digits + decimal + 1 decimal place)\n// Max: 10 chars for values like \"59.1234567\" (2 digits + decimal + 7 decimal places)\nconst LATITUDE_MIN_LENGTH = 3\nconst LATITUDE_MAX_LENGTH = 10\n\n// Longitude length constraints\n// Min: 2 chars for values like \"-1\" or single digit with decimal (needs min decimal places)\n// Max: 10 chars for values like \"-1.1234567\" (minus + 1 digit + decimal + 7 decimal places)\nconst LONGITUDE_MIN_LENGTH = 2\nconst LONGITUDE_MAX_LENGTH = 10\n\nexport class LatLongField extends FormComponent {\n declare options: LatLongFieldComponent['options']\n declare formSchema: ObjectSchema<FormPayload>\n declare stateSchema: ObjectSchema<FormState>\n declare collection: ComponentCollection\n\n constructor(\n def: LatLongFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { name, options, schema } = def\n\n const isRequired = options.required !== false\n\n // Read schema values from def.schema with fallback defaults\n const latitudeMin = schema?.latitude?.min ?? 49\n const latitudeMax = schema?.latitude?.max ?? 60\n const longitudeMin = schema?.longitude?.min ?? -9\n const longitudeMax = schema?.longitude?.max ?? 2\n\n const customValidationMessages: LanguageMessages =\n convertToLanguageMessages({\n 'any.required': messageTemplate.objectMissing,\n 'number.base': messageTemplate.objectMissing,\n 'number.precision':\n '{{#label}} must have no more than 7 decimal places',\n 'number.minPrecision':\n '{{#label}} must have at least {{#minPrecision}} decimal place',\n 'number.unsafe': '{{#label}} must be a valid number'\n })\n\n const latitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'number.base': `Enter a valid latitude for ${this.title} like 51.519450`,\n 'number.min': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,\n 'number.max': `Latitude for ${this.title} must be between ${latitudeMin} and ${latitudeMax}`,\n 'number.minLength': `Latitude for ${this.title} must be between 3 and 10 characters`,\n 'number.maxLength': `Latitude for ${this.title} must be between 3 and 10 characters`\n })\n\n const longitudeMessages: LanguageMessages = convertToLanguageMessages({\n ...customValidationMessages,\n 'number.base': `Enter a valid longitude for ${this.title} like -0.127758`,\n 'number.min': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,\n 'number.max': `Longitude for ${this.title} must be between ${longitudeMin} and ${longitudeMax}`,\n 'number.minLength': `Longitude for ${this.title} must be between 2 and 10 characters`,\n 'number.maxLength': `Longitude for ${this.title} must be between 2 and 10 characters`\n })\n\n this.collection = new ComponentCollection(\n [\n {\n type: ComponentType.NumberField,\n name: `${name}__latitude`,\n title: 'Latitude',\n schema: {\n min: latitudeMin,\n max: latitudeMax,\n precision: DECIMAL_PRECISION,\n minPrecision: MIN_DECIMAL_PLACES,\n minLength: LATITUDE_MIN_LENGTH,\n maxLength: LATITUDE_MAX_LENGTH\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: latitudeMessages\n }\n },\n {\n type: ComponentType.NumberField,\n name: `${name}__longitude`,\n title: 'Longitude',\n schema: {\n min: longitudeMin,\n max: longitudeMax,\n precision: DECIMAL_PRECISION,\n minPrecision: MIN_DECIMAL_PLACES,\n minLength: LONGITUDE_MIN_LENGTH,\n maxLength: LONGITUDE_MAX_LENGTH\n },\n options: {\n required: isRequired,\n optionalText: true,\n classes: 'govuk-input--width-10',\n suffix: '°',\n customValidationMessages: longitudeMessages\n }\n }\n ],\n { ...props, parent: this },\n {\n custom: getValidatorLatLong(this),\n peers: [`${name}__latitude`, `${name}__longitude`]\n }\n )\n\n this.options = options\n this.formSchema = this.collection.formSchema\n this.stateSchema = this.collection.stateSchema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const value = super.getFormValueFromState(state)\n return LatLongField.isLatLong(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(value: LatLongState | undefined): string {\n if (!value) {\n return ''\n }\n\n // CYA page format: <<latvalue, langvalue>>\n return `${value.latitude}, ${value.longitude}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(value)\n }\n\n getContextValueFromFormValue(value: LatLongState | undefined): string | null {\n if (!value) {\n return null\n }\n\n // Output format: Lat: <<entry>>\\nLong: <<entry>>\n return `Lat: ${value.latitude}\\nLong: ${value.longitude}`\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const value = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(value)\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n return getLocationFieldViewModel(this, viewModel, payload, errors)\n }\n\n isState(value?: FormStateValue | FormState) {\n return LatLongField.isLatLong(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return LatLongField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n {\n type: 'latitudeFormat',\n template:\n 'Enter a valid latitude for [short description] like 51.519450'\n },\n {\n type: 'longitudeFormat',\n template:\n 'Enter a valid longitude for [short description] like -0.127758'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'latitudeMin',\n template: 'Latitude for [short description] must be between 49 and 60'\n },\n {\n type: 'latitudeMax',\n template: 'Latitude for [short description] must be between 49 and 60'\n },\n {\n type: 'longitudeMin',\n template: 'Longitude for [short description] must be between -9 and 2'\n },\n {\n type: 'longitudeMax',\n template: 'Longitude for [short description] must be between -9 and 2'\n }\n ]\n }\n }\n\n static isLatLong(value?: FormStateValue | FormState): value is LatLongState {\n return (\n isFormState(value) &&\n NumberField.isNumber(value.latitude) &&\n NumberField.isNumber(value.longitude)\n )\n }\n}\n\nexport function getValidatorLatLong(component: LatLongField) {\n return createLocationFieldValidator(component)\n}\n"],"mappings":"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","ignoreList":[]}
@@ -69,4 +69,22 @@ export declare class NumberField extends FormComponent {
69
69
  static getAllPossibleErrors(): ErrorMessageTemplateList;
70
70
  static isNumber(value?: FormStateValue | FormState): value is number;
71
71
  }
72
+ /**
73
+ * Validates string length of a numeric value
74
+ * @param value - The numeric value to validate
75
+ * @param minLength - Minimum required string length
76
+ * @param maxLength - Maximum allowed string length
77
+ * @returns Object with validation result
78
+ */
79
+ export declare function validateStringLength(value: number, minLength?: number, maxLength?: number): {
80
+ isValid: boolean;
81
+ error?: 'minLength' | 'maxLength';
82
+ };
83
+ /**
84
+ * Validates minimum decimal precision
85
+ * @param value - The numeric value to validate
86
+ * @param minPrecision - Minimum required decimal places
87
+ * @returns true if valid, false if invalid
88
+ */
89
+ export declare function validateMinimumPrecision(value: number, minPrecision: number): boolean;
72
90
  export declare function getValidatorPrecision(component: NumberField): joi.CustomValidator;
@@ -134,6 +134,84 @@ export class NumberField extends FormComponent {
134
134
  return typeof value === 'number';
135
135
  }
136
136
  }
137
+
138
+ /**
139
+ * Validates string length of a numeric value
140
+ * @param value - The numeric value to validate
141
+ * @param minLength - Minimum required string length
142
+ * @param maxLength - Maximum allowed string length
143
+ * @returns Object with validation result
144
+ */
145
+ export function validateStringLength(value, minLength, maxLength) {
146
+ if (typeof minLength !== 'number' && typeof maxLength !== 'number') {
147
+ return {
148
+ isValid: true
149
+ };
150
+ }
151
+ const valueStr = String(value);
152
+ if (typeof minLength === 'number' && valueStr.length < minLength) {
153
+ return {
154
+ isValid: false,
155
+ error: 'minLength'
156
+ };
157
+ }
158
+ if (typeof maxLength === 'number' && valueStr.length > maxLength) {
159
+ return {
160
+ isValid: false,
161
+ error: 'maxLength'
162
+ };
163
+ }
164
+ return {
165
+ isValid: true
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Validates minimum decimal precision
171
+ * @param value - The numeric value to validate
172
+ * @param minPrecision - Minimum required decimal places
173
+ * @returns true if valid, false if invalid
174
+ */
175
+ export function validateMinimumPrecision(value, minPrecision) {
176
+ if (Number.isInteger(value)) {
177
+ return false;
178
+ }
179
+ const valueStr = String(value);
180
+ const decimalIndex = valueStr.indexOf('.');
181
+ if (decimalIndex !== -1) {
182
+ const decimalPlaces = valueStr.length - decimalIndex - 1;
183
+ return decimalPlaces >= minPrecision;
184
+ }
185
+ return false;
186
+ }
187
+
188
+ /**
189
+ * Helper function to handle length validation errors
190
+ * Returns the appropriate error response based on the validation result
191
+ */
192
+ function handleLengthValidationError(lengthCheck, helpers, custom, minLength, maxLength) {
193
+ if (!lengthCheck.isValid && lengthCheck.error) {
194
+ const errorType = `number.${lengthCheck.error}`;
195
+ if (custom) {
196
+ // Only pass the relevant length value in context
197
+ const contextData = lengthCheck.error === 'minLength' ? {
198
+ minLength: minLength ?? 0
199
+ } : {
200
+ maxLength: maxLength ?? 0
201
+ };
202
+ return helpers.message({
203
+ custom
204
+ }, contextData);
205
+ }
206
+ const context = lengthCheck.error === 'minLength' ? {
207
+ minLength: minLength ?? 0
208
+ } : {
209
+ maxLength: maxLength ?? 0
210
+ };
211
+ return helpers.error(errorType, context);
212
+ }
213
+ return null;
214
+ }
137
215
  export function getValidatorPrecision(component) {
138
216
  const validator = (value, helpers) => {
139
217
  const {
@@ -144,16 +222,24 @@ export function getValidatorPrecision(component) {
144
222
  customValidationMessage: custom
145
223
  } = options;
146
224
  const {
147
- precision: limit
225
+ precision: limit,
226
+ minPrecision,
227
+ minLength,
228
+ maxLength
148
229
  } = schema;
149
230
  if (!limit || limit <= 0) {
231
+ const lengthCheck = validateStringLength(value, minLength, maxLength);
232
+ const error = handleLengthValidationError(lengthCheck, helpers, custom, minLength, maxLength);
233
+ if (error) return error;
150
234
  return value;
151
235
  }
236
+
237
+ // Validate precision (max decimal places)
152
238
  const validationSchema = joi.number().precision(limit).prefs({
153
239
  convert: false
154
240
  });
155
241
  try {
156
- return joi.attempt(value, validationSchema);
242
+ joi.attempt(value, validationSchema);
157
243
  } catch {
158
244
  return custom ? helpers.message({
159
245
  custom
@@ -163,6 +249,21 @@ export function getValidatorPrecision(component) {
163
249
  limit
164
250
  });
165
251
  }
252
+
253
+ // Validate minimum precision (min decimal places)
254
+ if (typeof minPrecision === 'number' && minPrecision > 0) {
255
+ if (!validateMinimumPrecision(value, minPrecision)) {
256
+ return helpers.error('number.minPrecision', {
257
+ minPrecision
258
+ });
259
+ }
260
+ }
261
+
262
+ // Check string length validation after precision checks
263
+ const lengthCheck = validateStringLength(value, minLength, maxLength);
264
+ const error = handleLengthValidationError(lengthCheck, helpers, custom, minLength, maxLength);
265
+ if (error) return error;
266
+ return value;
166
267
  };
167
268
  return validator;
168
269
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField.js","names":["joi","FormComponent","isFormValue","messageTemplate","NumberField","constructor","def","props","options","schema","formSchema","number","custom","getValidatorPrecision","label","required","allow","messages","customValidationMessages","empty","min","max","precision","integer","customValidationMessage","message","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getViewModel","payload","errors","viewModel","attributes","prefix","suffix","inputmode","text","isNumber","getAllPossibleErrors","baseErrors","type","template","numberInteger","advancedSettingsErrors","numberMin","numberMax","numberPrecision","component","validator","helpers","limit","validationSchema","prefs","convert","attempt","error"],"sources":["../../../../../src/server/plugins/engine/components/NumberField.ts"],"sourcesContent":["import { type NumberFieldComponent } from '@defra/forms-model'\nimport joi, { type CustomValidator, type NumberSchema } from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.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\nexport class NumberField extends FormComponent {\n declare options: NumberFieldComponent['options']\n declare schema: NumberFieldComponent['schema']\n declare formSchema: NumberSchema\n declare stateSchema: NumberSchema\n\n constructor(\n def: NumberFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options, schema } = def\n\n let formSchema = joi\n .number()\n .custom(getValidatorPrecision(this))\n .label(this.label)\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.allow('')\n } else {\n const messages = options.customValidationMessages\n\n formSchema = formSchema.empty('').messages({\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n 'any.required':\n messages?.['any.required'] ?? (messageTemplate.required as string)\n })\n }\n\n if (typeof schema.min === 'number') {\n formSchema = formSchema.min(schema.min)\n }\n\n if (typeof schema.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n\n if (typeof schema.precision === 'number' && schema.precision <= 0) {\n formSchema = formSchema.integer()\n }\n\n if (options.customValidationMessage) {\n const message = options.customValidationMessage\n\n formSchema = formSchema.messages({\n 'any.required': message,\n 'number.base': message,\n 'number.precision': message,\n 'number.integer': message,\n 'number.min': message,\n 'number.max': message\n })\n } else if (options.customValidationMessages) {\n formSchema = formSchema.messages(options.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = options\n this.schema = schema\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 getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const { options, schema } = this\n\n const viewModel = super.getViewModel(payload, errors)\n let { attributes, prefix, suffix, value } = viewModel\n\n if (typeof schema.precision === 'undefined' || schema.precision <= 0) {\n // If precision isn't provided or provided and\n // less than or equal to 0, use numeric inputmode\n attributes.inputmode = 'numeric'\n }\n\n if (options.prefix) {\n prefix = {\n text: options.prefix\n }\n }\n\n if (options.suffix) {\n suffix = {\n text: options.suffix\n }\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 ...viewModel,\n attributes,\n prefix,\n suffix,\n value\n }\n }\n\n isValue(value?: FormStateValue | FormState) {\n return NumberField.isNumber(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return NumberField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n { type: 'numberInteger', template: messageTemplate.numberInteger }\n ],\n advancedSettingsErrors: [\n { type: 'numberMin', template: messageTemplate.numberMin },\n { type: 'numberMax', template: messageTemplate.numberMax },\n { type: 'numberPrecision', template: messageTemplate.numberPrecision }\n ]\n }\n }\n\n static isNumber(value?: FormStateValue | FormState): value is number {\n return typeof value === 'number'\n }\n}\n\nexport function getValidatorPrecision(component: NumberField) {\n const validator: CustomValidator = (value: number, helpers) => {\n const { options, schema } = component\n\n const { customValidationMessage: custom } = options\n const { precision: limit } = schema\n\n if (!limit || limit <= 0) {\n return value\n }\n\n const validationSchema = joi\n .number()\n .precision(limit)\n .prefs({ convert: false })\n\n try {\n return joi.attempt(value, validationSchema)\n } catch {\n return custom\n ? helpers.message({ custom }, { limit })\n : helpers.error('number.precision', { limit })\n }\n }\n\n return validator\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"NumberField.js","names":["joi","FormComponent","isFormValue","messageTemplate","NumberField","constructor","def","props","options","schema","formSchema","number","custom","getValidatorPrecision","label","required","allow","messages","customValidationMessages","empty","min","max","precision","integer","customValidationMessage","message","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getViewModel","payload","errors","viewModel","attributes","prefix","suffix","inputmode","text","isNumber","getAllPossibleErrors","baseErrors","type","template","numberInteger","advancedSettingsErrors","numberMin","numberMax","numberPrecision","validateStringLength","minLength","maxLength","isValid","valueStr","String","length","error","validateMinimumPrecision","minPrecision","Number","isInteger","decimalIndex","indexOf","decimalPlaces","handleLengthValidationError","lengthCheck","helpers","errorType","contextData","context","component","validator","limit","validationSchema","prefs","convert","attempt"],"sources":["../../../../../src/server/plugins/engine/components/NumberField.ts"],"sourcesContent":["import { type NumberFieldComponent } from '@defra/forms-model'\nimport joi, {\n type CustomHelpers,\n type CustomValidator,\n type NumberSchema\n} from 'joi'\n\nimport {\n FormComponent,\n isFormValue\n} from '~/src/server/plugins/engine/components/FormComponent.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\nexport class NumberField extends FormComponent {\n declare options: NumberFieldComponent['options']\n declare schema: NumberFieldComponent['schema']\n declare formSchema: NumberSchema\n declare stateSchema: NumberSchema\n\n constructor(\n def: NumberFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options, schema } = def\n\n let formSchema = joi\n .number()\n .custom(getValidatorPrecision(this))\n .label(this.label)\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.allow('')\n } else {\n const messages = options.customValidationMessages\n\n formSchema = formSchema.empty('').messages({\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n 'any.required':\n messages?.['any.required'] ?? (messageTemplate.required as string)\n })\n }\n\n if (typeof schema.min === 'number') {\n formSchema = formSchema.min(schema.min)\n }\n\n if (typeof schema.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n\n if (typeof schema.precision === 'number' && schema.precision <= 0) {\n formSchema = formSchema.integer()\n }\n\n if (options.customValidationMessage) {\n const message = options.customValidationMessage\n\n formSchema = formSchema.messages({\n 'any.required': message,\n 'number.base': message,\n 'number.precision': message,\n 'number.integer': message,\n 'number.min': message,\n 'number.max': message\n })\n } else if (options.customValidationMessages) {\n formSchema = formSchema.messages(options.customValidationMessages)\n }\n\n this.formSchema = formSchema.default('')\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = options\n this.schema = schema\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 getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const { options, schema } = this\n\n const viewModel = super.getViewModel(payload, errors)\n let { attributes, prefix, suffix, value } = viewModel\n\n if (typeof schema.precision === 'undefined' || schema.precision <= 0) {\n // If precision isn't provided or provided and\n // less than or equal to 0, use numeric inputmode\n attributes.inputmode = 'numeric'\n }\n\n if (options.prefix) {\n prefix = {\n text: options.prefix\n }\n }\n\n if (options.suffix) {\n suffix = {\n text: options.suffix\n }\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 ...viewModel,\n attributes,\n prefix,\n suffix,\n value\n }\n }\n\n isValue(value?: FormStateValue | FormState) {\n return NumberField.isNumber(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return NumberField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'required', template: messageTemplate.required },\n { type: 'numberInteger', template: messageTemplate.numberInteger }\n ],\n advancedSettingsErrors: [\n { type: 'numberMin', template: messageTemplate.numberMin },\n { type: 'numberMax', template: messageTemplate.numberMax },\n { type: 'numberPrecision', template: messageTemplate.numberPrecision }\n ]\n }\n }\n\n static isNumber(value?: FormStateValue | FormState): value is number {\n return typeof value === 'number'\n }\n}\n\n/**\n * Validates string length of a numeric value\n * @param value - The numeric value to validate\n * @param minLength - Minimum required string length\n * @param maxLength - Maximum allowed string length\n * @returns Object with validation result\n */\nexport function validateStringLength(\n value: number,\n minLength?: number,\n maxLength?: number\n): { isValid: boolean; error?: 'minLength' | 'maxLength' } {\n if (typeof minLength !== 'number' && typeof maxLength !== 'number') {\n return { isValid: true }\n }\n\n const valueStr = String(value)\n\n if (typeof minLength === 'number' && valueStr.length < minLength) {\n return { isValid: false, error: 'minLength' }\n }\n\n if (typeof maxLength === 'number' && valueStr.length > maxLength) {\n return { isValid: false, error: 'maxLength' }\n }\n\n return { isValid: true }\n}\n\n/**\n * Validates minimum decimal precision\n * @param value - The numeric value to validate\n * @param minPrecision - Minimum required decimal places\n * @returns true if valid, false if invalid\n */\nexport function validateMinimumPrecision(\n value: number,\n minPrecision: number\n): boolean {\n if (Number.isInteger(value)) {\n return false\n }\n\n const valueStr = String(value)\n const decimalIndex = valueStr.indexOf('.')\n\n if (decimalIndex !== -1) {\n const decimalPlaces = valueStr.length - decimalIndex - 1\n return decimalPlaces >= minPrecision\n }\n\n return false\n}\n\n/**\n * Helper function to handle length validation errors\n * Returns the appropriate error response based on the validation result\n */\nfunction handleLengthValidationError(\n lengthCheck: ReturnType<typeof validateStringLength>,\n helpers: CustomHelpers,\n custom: string | undefined,\n minLength: number | undefined,\n maxLength: number | undefined\n) {\n if (!lengthCheck.isValid && lengthCheck.error) {\n const errorType = `number.${lengthCheck.error}`\n\n if (custom) {\n // Only pass the relevant length value in context\n const contextData =\n lengthCheck.error === 'minLength'\n ? { minLength: minLength ?? 0 }\n : { maxLength: maxLength ?? 0 }\n return helpers.message({ custom }, contextData)\n }\n\n const context =\n lengthCheck.error === 'minLength'\n ? { minLength: minLength ?? 0 }\n : { maxLength: maxLength ?? 0 }\n return helpers.error(errorType, context)\n }\n return null\n}\n\nexport function getValidatorPrecision(component: NumberField) {\n const validator: CustomValidator = (value: number, helpers) => {\n const { options, schema } = component\n const { customValidationMessage: custom } = options\n const {\n precision: limit,\n minPrecision,\n minLength,\n maxLength\n } = schema as {\n precision?: number\n minPrecision?: number\n minLength?: number\n maxLength?: number\n }\n\n if (!limit || limit <= 0) {\n const lengthCheck = validateStringLength(value, minLength, maxLength)\n const error = handleLengthValidationError(\n lengthCheck,\n helpers,\n custom,\n minLength,\n maxLength\n )\n if (error) return error\n return value\n }\n\n // Validate precision (max decimal places)\n const validationSchema = joi\n .number()\n .precision(limit)\n .prefs({ convert: false })\n\n try {\n joi.attempt(value, validationSchema)\n } catch {\n return custom\n ? helpers.message({ custom }, { limit })\n : helpers.error('number.precision', { limit })\n }\n\n // Validate minimum precision (min decimal places)\n if (typeof minPrecision === 'number' && minPrecision > 0) {\n if (!validateMinimumPrecision(value, minPrecision)) {\n return helpers.error('number.minPrecision', { minPrecision })\n }\n }\n\n // Check string length validation after precision checks\n const lengthCheck = validateStringLength(value, minLength, maxLength)\n const error = handleLengthValidationError(\n lengthCheck,\n helpers,\n custom,\n minLength,\n maxLength\n )\n if (error) return error\n\n return value\n }\n\n return validator\n}\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.9",
3
+ "version": "4.0.10",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "license": "SEE LICENSE IN LICENSE",
72
72
  "dependencies": {
73
- "@defra/forms-model": "^3.0.574",
73
+ "@defra/forms-model": "^3.0.575",
74
74
  "@defra/hapi-tracing": "^1.26.0",
75
75
  "@elastic/ecs-pino-format": "^1.5.0",
76
76
  "@hapi/boom": "^10.0.1",
@@ -556,10 +556,17 @@ describe('EastingNorthingField', () => {
556
556
  easting: 12345.5,
557
557
  northing: 1234567
558
558
  }),
559
+ // Two errors expected: decimal input triggers both integer validation
560
+ // and length validation ('12345.5' is 7 chars, max is 6)
559
561
  errors: [
560
562
  expect.objectContaining({
561
563
  text: expect.stringMatching(
562
- /Easting for .* must be between 1 and 5 digits/
564
+ /Easting for .* must be between 1 and 6 digits/
565
+ )
566
+ }),
567
+ expect.objectContaining({
568
+ text: expect.stringMatching(
569
+ /Easting for .* must be between 1 and 6 digits/
563
570
  )
564
571
  })
565
572
  ]
@@ -575,7 +582,14 @@ describe('EastingNorthingField', () => {
575
582
  easting: 12345,
576
583
  northing: 1234567.5
577
584
  }),
585
+ // Two errors expected: decimal input triggers both integer validation
586
+ // and length validation ('1234567.5' is 9 chars, max is 7)
578
587
  errors: [
588
+ expect.objectContaining({
589
+ text: expect.stringMatching(
590
+ /Northing for .* must be between 1 and 7 digits/
591
+ )
592
+ }),
579
593
  expect.objectContaining({
580
594
  text: expect.stringMatching(
581
595
  /Northing for .* must be between 1 and 7 digits/