@defra/forms-engine-plugin 5.0.0-alpha.11 → 5.0.0-alpha.12

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 (62) hide show
  1. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +20 -2
  2. package/.server/server/plugins/engine/components/CheckboxesField.js +24 -12
  3. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  4. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +2 -0
  5. package/.server/server/plugins/engine/components/EastingNorthingField.js +1 -1
  6. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  7. package/.server/server/plugins/engine/components/FileUploadField.d.ts +10 -1
  8. package/.server/server/plugins/engine/components/FileUploadField.js +18 -6
  9. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  10. package/.server/server/plugins/engine/components/GeospatialField.d.ts +21 -0
  11. package/.server/server/plugins/engine/components/GeospatialField.js +32 -9
  12. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  13. package/.server/server/plugins/engine/components/LatLongField.d.ts +2 -0
  14. package/.server/server/plugins/engine/components/LocationFieldBase.js +4 -1
  15. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  16. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +2 -0
  17. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +7 -1
  18. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  19. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +5 -0
  20. package/.server/server/plugins/engine/components/helpers/geospatial.js +25 -16
  21. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  22. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  23. package/.server/server/plugins/engine/form-availability.js +3 -2
  24. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  25. package/.server/server/plugins/engine/form-context.js +2 -2
  26. package/.server/server/plugins/engine/form-context.js.map +1 -1
  27. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +1 -1
  28. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +30 -9
  29. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  30. package/.server/server/plugins/engine/i18n/translations/cy.json +66 -10
  31. package/.server/server/plugins/engine/i18n/translations/en-GB.json +29 -10
  32. package/.server/server/plugins/engine/i18n/types.d.ts +1 -0
  33. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  34. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  35. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  36. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  37. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  38. package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -1
  39. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  40. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  41. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  42. package/.server/server/plugins/engine/views/components/_location-field-base.html +1 -1
  43. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  44. package/package.json +1 -1
  45. package/src/server/plugins/engine/components/CheckboxesField.ts +34 -13
  46. package/src/server/plugins/engine/components/EastingNorthingField.ts +1 -1
  47. package/src/server/plugins/engine/components/FileUploadField.ts +26 -6
  48. package/src/server/plugins/engine/components/GeospatialField.ts +43 -11
  49. package/src/server/plugins/engine/components/LocationFieldBase.ts +3 -1
  50. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +7 -1
  51. package/src/server/plugins/engine/components/helpers/geospatial.ts +32 -22
  52. package/src/server/plugins/engine/form-availability.ts +8 -2
  53. package/src/server/plugins/engine/form-context.ts +4 -2
  54. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +15 -10
  55. package/src/server/plugins/engine/i18n/translations/cy.json +66 -10
  56. package/src/server/plugins/engine/i18n/translations/en-GB.json +29 -10
  57. package/src/server/plugins/engine/i18n/types.ts +1 -0
  58. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  59. package/src/server/plugins/engine/pageControllers/validationOptions.ts +2 -2
  60. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  61. package/src/server/plugins/engine/views/components/_location-field-base.html +1 -1
  62. package/src/server/plugins/engine/views/unavailable.html +14 -11
@@ -1,6 +1,8 @@
1
+ import { EN_GB } from "../../../constants.js";
1
2
  import { FormComponent, isGeospatialState } from "./FormComponent.js";
2
3
  import { getMapLayers } from "./LocationFieldHelpers.js";
3
- import { getGeospatialSchema } from "./helpers/geospatial.js";
4
+ import { determineLimits, getGeospatialSchema } from "./helpers/geospatial.js";
5
+ import { t as tPlugin } from "../i18n/index.js";
4
6
  import { messageTemplate } from "../pageControllers/validationOptions.js";
5
7
  export class GeospatialField extends FormComponent {
6
8
  constructor(def, props) {
@@ -8,17 +10,17 @@ export class GeospatialField extends FormComponent {
8
10
  const {
9
11
  options
10
12
  } = def;
11
- const formSchema = getGeospatialSchema(def).label(this.label).messages({
12
- 'array.min': messageTemplate.featuresMin,
13
- 'array.max': messageTemplate.featuresMax,
14
- 'array.length': messageTemplate.featuresLength
15
- });
13
+ let formSchema = getGeospatialSchema(def).label(this.label);
14
+ const isOptional = options.required === false;
15
+ const limits = determineLimits(def, isOptional);
16
+ formSchema = formSchema.messages(GeospatialField.buildErrorMessages(EN_GB, limits));
16
17
  this.formSchema = formSchema;
17
18
  this.stateSchema = formSchema.default(null);
18
- if (options.required === false) {
19
+ if (isOptional) {
19
20
  this.stateSchema = this.stateSchema.allow(null);
20
21
  }
21
22
  this.options = options;
23
+ this.limits = limits;
22
24
  }
23
25
  getFormValueFromState(state) {
24
26
  const {
@@ -73,7 +75,7 @@ export class GeospatialField extends FormComponent {
73
75
  err.href = `#description_${err.path[1]}`;
74
76
  err.text = translator.t('components.geospatialField.validation.wrongCountry', {
75
77
  count: Number(err.path[1]) + 1,
76
- country: translator.t(`common.${err.context.country.toLowerCase()}`)
78
+ country: translator.t(`common.${err.context.country}`)
77
79
  });
78
80
  }
79
81
  });
@@ -85,6 +87,14 @@ export class GeospatialField extends FormComponent {
85
87
  isValue(value) {
86
88
  return isGeospatialState(value);
87
89
  }
90
+ getValidationMessagesOverride(translator) {
91
+ const {
92
+ language
93
+ } = translator;
94
+ return {
95
+ [this.name]: GeospatialField.buildErrorMessages(language, this.limits)
96
+ };
97
+ }
88
98
 
89
99
  /**
90
100
  * For error preview page that shows all possible errors on a component
@@ -107,7 +117,7 @@ export class GeospatialField extends FormComponent {
107
117
  template: messageTemplate.selectRequired
108
118
  }, {
109
119
  type: 'array.min',
110
- template: '{{#title}} must contain at least 1 items'
120
+ template: '{{#title}} must contain at least 1 item'
111
121
  }, {
112
122
  type: 'object.invalidjson',
113
123
  template: messageTemplate.format
@@ -115,5 +125,18 @@ export class GeospatialField extends FormComponent {
115
125
  advancedSettingsErrors: []
116
126
  };
117
127
  }
128
+ static buildErrorMessages(language, limits = {}) {
129
+ return {
130
+ 'array.min': tPlugin('validation.featuresMin', language, {
131
+ count: limits.min ?? 1
132
+ }),
133
+ 'array.max': tPlugin('validation.featuresMax', language, {
134
+ count: limits.max ?? 1
135
+ }),
136
+ 'array.length': tPlugin('validation.featuresLength', language, {
137
+ count: limits.length ?? 1
138
+ })
139
+ };
140
+ }
118
141
  }
119
142
  //# sourceMappingURL=GeospatialField.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GeospatialField.js","names":["FormComponent","isGeospatialState","getMapLayers","getGeospatialSchema","messageTemplate","GeospatialField","constructor","def","props","options","formSchema","label","messages","featuresMin","featuresMax","featuresLength","stateSchema","default","required","allow","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getDisplayStringFromFormValue","features","translator","length","t","count","getDisplayStringFromState","getContextValueFromFormValue","map","id","getContextValueFromState","getViewModel","context","viewModel","JSON","stringify","country","countries","at","geometryTypes","mapLayers","getErrors","errors","fieldErrors","forEach","err","href","path","text","Number","toLowerCase","getViewErrors","getAllPossibleErrors","staticErrors","advancedSettingsErrors","baseErrors","type","template","selectRequired","format"],"sources":["../../../../../src/server/plugins/engine/components/GeospatialField.ts"],"sourcesContent":["import { type GeospatialFieldComponent } from '@defra/forms-model'\nimport { type ArraySchema } from 'joi'\n\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport {\n FormComponent,\n isGeospatialState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { getMapLayers } from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport { getGeospatialSchema } from '~/src/server/plugins/engine/components/helpers/geospatial.js'\nimport { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState,\n type GeospatialState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class GeospatialField extends FormComponent {\n declare options: GeospatialFieldComponent['options']\n declare formSchema: ArraySchema<GeospatialState>\n declare stateSchema: ArraySchema<GeospatialState>\n\n constructor(\n def: GeospatialFieldComponent,\n props: ConstructorParameters<typeof ComponentBase>[1]\n ) {\n super(def, props)\n\n const { options } = def\n\n const formSchema = getGeospatialSchema(def).label(this.label).messages({\n 'array.min': messageTemplate.featuresMin,\n 'array.max': messageTemplate.featuresMax,\n 'array.length': messageTemplate.featuresLength\n })\n\n this.formSchema = formSchema\n this.stateSchema = formSchema.default(null)\n\n if (options.required === false) {\n this.stateSchema = this.stateSchema.allow(null)\n }\n\n this.options = options\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 getDisplayStringFromFormValue(\n features: GeospatialState | undefined,\n translator: Translator\n ): string {\n if (!features?.length) {\n return ''\n }\n\n return translator.t('components.geospatialField.added', {\n count: features.length\n })\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n translator: Translator\n ) {\n const features = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(features, translator)\n }\n\n getContextValueFromFormValue(\n features: GeospatialState | undefined\n ): string[] | null {\n return features?.map(({ id }) => id) ?? null\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const features = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(features)\n }\n\n getViewModel(context: RenderContext) {\n const viewModel = super.getViewModel(context)\n const value =\n typeof viewModel.value === 'string'\n ? viewModel.value\n : JSON.stringify(viewModel.value, null, 2)\n\n return {\n ...viewModel,\n country: this.options.countries?.at(0),\n geometryTypes: this.options.geometryTypes,\n mapLayers: getMapLayers(this),\n value\n }\n }\n\n getErrors(\n translator: Translator,\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const fieldErrors = super.getErrors(translator, errors)\n\n fieldErrors?.forEach((err) => {\n if (err.name === 'description') {\n err.href = `#description_${err.path[1]}`\n err.text = translator.t(\n 'components.geospatialField.validation.descriptionRequired',\n { count: Number(err.path[1]) + 1 }\n )\n } else if (typeof err.name === 'number' && err.context?.country) {\n err.href = `#description_${err.path[1]}`\n err.text = translator.t(\n 'components.geospatialField.validation.wrongCountry',\n {\n count: Number(err.path[1]) + 1,\n country: translator.t(\n `common.${(err.context.country as string).toLowerCase()}`\n )\n }\n )\n }\n })\n\n return fieldErrors\n }\n\n getViewErrors(\n translator: Translator,\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n return this.getErrors(translator, errors)\n }\n\n isValue(value?: FormStateValue | FormState): value is GeospatialState {\n return isGeospatialState(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n const staticErrors = GeospatialField.getAllPossibleErrors()\n return {\n ...staticErrors,\n advancedSettingsErrors: [...staticErrors.advancedSettingsErrors]\n }\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.selectRequired },\n {\n type: 'array.min',\n template: '{{#title}} must contain at least 1 items'\n },\n { type: 'object.invalidjson', template: messageTemplate.format }\n ],\n advancedSettingsErrors: []\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"GeospatialField.js","names":["EN_GB","FormComponent","isGeospatialState","getMapLayers","determineLimits","getGeospatialSchema","t","tPlugin","messageTemplate","GeospatialField","constructor","def","props","options","formSchema","label","isOptional","required","limits","messages","buildErrorMessages","stateSchema","default","allow","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getDisplayStringFromFormValue","features","translator","length","count","getDisplayStringFromState","getContextValueFromFormValue","map","id","getContextValueFromState","getViewModel","context","viewModel","JSON","stringify","country","countries","at","geometryTypes","mapLayers","getErrors","errors","fieldErrors","forEach","err","href","path","text","Number","getViewErrors","getValidationMessagesOverride","language","getAllPossibleErrors","staticErrors","advancedSettingsErrors","baseErrors","type","template","selectRequired","format","min","max"],"sources":["../../../../../src/server/plugins/engine/components/GeospatialField.ts"],"sourcesContent":["import { type GeospatialFieldComponent } from '@defra/forms-model'\nimport { type ArraySchema } from 'joi'\n\nimport { EN_GB } from '~/src/server/constants.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport {\n FormComponent,\n isGeospatialState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { getMapLayers } from '~/src/server/plugins/engine/components/LocationFieldHelpers.js'\nimport {\n determineLimits,\n getGeospatialSchema\n} from '~/src/server/plugins/engine/components/helpers/geospatial.js'\nimport { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport { t as tPlugin } from '~/src/server/plugins/engine/i18n/index.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState,\n type GeospatialState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class GeospatialField extends FormComponent {\n declare options: GeospatialFieldComponent['options']\n declare formSchema: ArraySchema<GeospatialState>\n declare stateSchema: ArraySchema<GeospatialState>\n declare limits: { min?: number; max?: number; length?: number }\n\n constructor(\n def: GeospatialFieldComponent,\n props: ConstructorParameters<typeof ComponentBase>[1]\n ) {\n super(def, props)\n\n const { options } = def\n\n let formSchema = getGeospatialSchema(def).label(this.label)\n\n const isOptional = options.required === false\n const limits = determineLimits(def, isOptional)\n\n formSchema = formSchema.messages(\n GeospatialField.buildErrorMessages(EN_GB, limits)\n )\n\n this.formSchema = formSchema\n this.stateSchema = formSchema.default(null)\n\n if (isOptional) {\n this.stateSchema = this.stateSchema.allow(null)\n }\n\n this.options = options\n this.limits = limits\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 getDisplayStringFromFormValue(\n features: GeospatialState | undefined,\n translator: Translator\n ): string {\n if (!features?.length) {\n return ''\n }\n\n return translator.t('components.geospatialField.added', {\n count: features.length\n })\n }\n\n getDisplayStringFromState(\n state: FormSubmissionState,\n translator: Translator\n ) {\n const features = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(features, translator)\n }\n\n getContextValueFromFormValue(\n features: GeospatialState | undefined\n ): string[] | null {\n return features?.map(({ id }) => id) ?? null\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const features = this.getFormValueFromState(state)\n\n return this.getContextValueFromFormValue(features)\n }\n\n getViewModel(context: RenderContext) {\n const viewModel = super.getViewModel(context)\n const value =\n typeof viewModel.value === 'string'\n ? viewModel.value\n : JSON.stringify(viewModel.value, null, 2)\n\n return {\n ...viewModel,\n country: this.options.countries?.at(0),\n geometryTypes: this.options.geometryTypes,\n mapLayers: getMapLayers(this),\n value\n }\n }\n\n getErrors(\n translator: Translator,\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n const fieldErrors = super.getErrors(translator, errors)\n\n fieldErrors?.forEach((err) => {\n if (err.name === 'description') {\n err.href = `#description_${err.path[1]}`\n err.text = translator.t(\n 'components.geospatialField.validation.descriptionRequired',\n { count: Number(err.path[1]) + 1 }\n )\n } else if (typeof err.name === 'number' && err.context?.country) {\n err.href = `#description_${err.path[1]}`\n err.text = translator.t(\n 'components.geospatialField.validation.wrongCountry',\n {\n count: Number(err.path[1]) + 1,\n country: translator.t(`common.${err.context.country}`)\n }\n )\n }\n })\n\n return fieldErrors\n }\n\n getViewErrors(\n translator: Translator,\n errors?: FormSubmissionError[]\n ): FormSubmissionError[] | undefined {\n return this.getErrors(translator, errors)\n }\n\n isValue(value?: FormStateValue | FormState): value is GeospatialState {\n return isGeospatialState(value)\n }\n\n getValidationMessagesOverride(translator: Translator) {\n const { language } = translator\n return {\n [this.name]: GeospatialField.buildErrorMessages(language, this.limits)\n }\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n const staticErrors = GeospatialField.getAllPossibleErrors()\n return {\n ...staticErrors,\n advancedSettingsErrors: [...staticErrors.advancedSettingsErrors]\n }\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.selectRequired },\n {\n type: 'array.min',\n template: '{{#title}} must contain at least 1 item'\n },\n { type: 'object.invalidjson', template: messageTemplate.format }\n ],\n advancedSettingsErrors: []\n }\n }\n\n static buildErrorMessages(\n language: string,\n limits: { min?: number; max?: number; length?: number } = {}\n ) {\n return {\n 'array.min': tPlugin('validation.featuresMin', language, {\n count: limits.min ?? 1\n }),\n 'array.max': tPlugin('validation.featuresMax', language, {\n count: limits.max ?? 1\n }),\n 'array.length': tPlugin('validation.featuresLength', language, {\n count: limits.length ?? 1\n })\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -66,7 +66,9 @@ export declare class LatLongField extends FormComponent {
66
66
  uploadingLabel?: string;
67
67
  };
68
68
  } | {
69
+ instructionSummary: string;
69
70
  instructionText: string;
71
+ t: (key: string, opts?: Record<string, unknown>) => string;
70
72
  fieldset: {
71
73
  attributes?: string | Record<string, string>;
72
74
  legend?: import("../../../../server/plugins/engine/components/types.js").Label;
@@ -57,9 +57,12 @@ export class LocationFieldBase extends FormComponent {
57
57
  getViewModel(context) {
58
58
  const viewModel = super.getViewModel(context);
59
59
  if (this.instructionText) {
60
+ const {
61
+ tComponent
62
+ } = context.translator;
60
63
  return {
61
64
  ...viewModel,
62
- instructionText: this.instructionText
65
+ instructionText: tComponent(this.def, 'instructionText') || this.instructionText
63
66
  };
64
67
  }
65
68
  return viewModel;
@@ -1 +1 @@
1
- {"version":3,"file":"LocationFieldBase.js","names":["joi","FormComponent","isFormValue","addClassOptionIfNone","buildValidationMessages","messageTemplate","LocationFieldBase","instructionText","constructor","def","props","options","locationOptions","config","getValidationConfig","undefined","requiredMessage","required","messages","patternErrorMessage","formSchema","string","trim","label","pattern","allow","customValidationMessage","message","messageKeys","reduce","acc","key","customValidationMessages","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","isText","getViewModel","context","viewModel","getValidationMessagesOverride","translator","t","objectRequired","getAllPossibleErrors","baseErrors","type","template","getErrorTemplates","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldBase.ts"],"sourcesContent":["import { type FormComponentsDef } from '@defra/forms-model'\nimport joi, {\n type JoiExpression,\n type LanguageMessages,\n type StringSchema\n} 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 { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport { buildValidationMessages } from '~/src/server/plugins/engine/i18n/buildValidationMessages.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport { type Translator } from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\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: JoiExpression\n requiredMessage?: JoiExpression\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(\n translator: Translator | undefined\n ): ValidationConfig\n protected abstract getErrorTemplates(): {\n type: string\n template: JoiExpression\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(undefined)\n const requiredMessage = config.requiredMessage ?? messageTemplate.required\n\n const messages = {\n 'any.required': requiredMessage,\n 'string.empty': requiredMessage,\n 'string.pattern.base': config.patternErrorMessage\n } as LanguageMessages\n\n let formSchema = joi\n .string()\n .trim()\n .label(this.label)\n .required()\n .pattern(config.pattern)\n .messages(messages)\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 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(context: RenderContext) {\n const viewModel = super.getViewModel(context)\n\n if (this.instructionText) {\n return {\n ...viewModel,\n instructionText: this.instructionText\n }\n }\n\n return viewModel\n }\n\n getValidationMessagesOverride(translator: Translator) {\n const { t } = translator\n const config = this.getValidationConfig(translator)\n return {\n [this.name]: {\n 'any.required':\n config.requiredMessage ?? buildValidationMessages(t).objectRequired,\n 'string.empty':\n config.requiredMessage ?? buildValidationMessages(t).objectRequired,\n 'string.pattern.base': config.patternErrorMessage\n } as LanguageMessages\n }\n }\n\n getAllPossibleErrors(): ErrorMessageTemplateList {\n const config = this.getValidationConfig(undefined)\n\n return {\n baseErrors: [\n {\n type: 'required',\n template: config.requiredMessage ?? messageTemplate.required\n },\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":[]}
1
+ {"version":3,"file":"LocationFieldBase.js","names":["joi","FormComponent","isFormValue","addClassOptionIfNone","buildValidationMessages","messageTemplate","LocationFieldBase","instructionText","constructor","def","props","options","locationOptions","config","getValidationConfig","undefined","requiredMessage","required","messages","patternErrorMessage","formSchema","string","trim","label","pattern","allow","customValidationMessage","message","messageKeys","reduce","acc","key","customValidationMessages","default","stateSchema","getFormValueFromState","state","name","getFormValue","value","isValue","isText","getViewModel","context","viewModel","tComponent","translator","getValidationMessagesOverride","t","objectRequired","getAllPossibleErrors","baseErrors","type","template","getErrorTemplates","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldBase.ts"],"sourcesContent":["import { type FormComponentsDef } from '@defra/forms-model'\nimport joi, {\n type JoiExpression,\n type LanguageMessages,\n type StringSchema\n} 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 { type RenderContext } from '~/src/server/plugins/engine/components/types.js'\nimport { buildValidationMessages } from '~/src/server/plugins/engine/i18n/buildValidationMessages.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport { type Translator } from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormState,\n type FormStateValue,\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: JoiExpression\n requiredMessage?: JoiExpression\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(\n translator: Translator | undefined\n ): ValidationConfig\n protected abstract getErrorTemplates(): {\n type: string\n template: JoiExpression\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(undefined)\n const requiredMessage = config.requiredMessage ?? messageTemplate.required\n\n const messages = {\n 'any.required': requiredMessage,\n 'string.empty': requiredMessage,\n 'string.pattern.base': config.patternErrorMessage\n } as LanguageMessages\n\n let formSchema = joi\n .string()\n .trim()\n .label(this.label)\n .required()\n .pattern(config.pattern)\n .messages(messages)\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 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(context: RenderContext) {\n const viewModel = super.getViewModel(context)\n\n if (this.instructionText) {\n const { tComponent } = context.translator\n return {\n ...viewModel,\n instructionText:\n tComponent(this.def, 'instructionText') || this.instructionText\n }\n }\n\n return viewModel\n }\n\n getValidationMessagesOverride(translator: Translator) {\n const { t } = translator\n const config = this.getValidationConfig(translator)\n return {\n [this.name]: {\n 'any.required':\n config.requiredMessage ?? buildValidationMessages(t).objectRequired,\n 'string.empty':\n config.requiredMessage ?? buildValidationMessages(t).objectRequired,\n 'string.pattern.base': config.patternErrorMessage\n } as LanguageMessages\n }\n }\n\n getAllPossibleErrors(): ErrorMessageTemplateList {\n const config = this.getValidationConfig(undefined)\n\n return {\n baseErrors: [\n {\n type: 'required',\n template: config.requiredMessage ?? messageTemplate.required\n },\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":[]}
@@ -67,7 +67,9 @@ export declare function getLocationFieldViewModel(component: LocationField, view
67
67
  uploadingLabel?: string;
68
68
  };
69
69
  } | {
70
+ instructionSummary: string;
70
71
  instructionText: string;
72
+ t: (key: string, opts?: Record<string, unknown>) => string;
71
73
  fieldset: {
72
74
  attributes?: string | Record<string, string>;
73
75
  legend?: Label;
@@ -127,9 +127,15 @@ export function getLocationFieldViewModel(component, viewModel, context) {
127
127
  country: component instanceof EastingNorthingField ? component.options.countries?.at(0) : undefined
128
128
  };
129
129
  if (component.options.instructionText) {
130
+ const {
131
+ t,
132
+ tComponent
133
+ } = context.translator;
130
134
  return {
131
135
  ...result,
132
- instructionText: component.options.instructionText
136
+ instructionSummary: t('components.locationFieldBase.howToFind'),
137
+ instructionText: tComponent(component.def, 'instructionText') || component.options.instructionText,
138
+ t
133
139
  };
134
140
  }
135
141
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"LocationFieldHelpers.js","names":["EastingNorthingField","isFormValue","lowercaseMessageStart","message","charAt","toLowerCase","slice","joinWithAnd","items","translator","t","length","leading","join","last","formatErrorList","messages","formattedMessages","map","msg","index","mergeCssClasses","classNames","tokens","flatMap","name","split","token","trim","filter","Boolean","undefined","Array","from","Set","deduplicateErrorsByHref","errors","error","self","findIndex","err","href","getMapLayers","component","mapLayers","options","layers","sssi","push","getLocationFieldViewModel","viewModel","context","collection","fieldset","existingFieldset","label","subViewModels","getViewModel","fieldErrors","forEach","model","errorMessage","text","hasFieldErrors","type","value","classes","prefix","suffix","toString","baseItem","id","showFieldsetError","legend","result","country","countries","at","instructionText","createLocationFieldValidator","payload","helpers","values","getFormValueFromState","getStateFromValidForm","missing","keys","key","isState","required"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldHelpers.ts"],"sourcesContent":["import { type Context, type CustomValidator } from 'joi'\n\nimport { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type GeospatialField } from '~/src/server/plugins/engine/components/GeospatialField.js'\nimport { type LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nimport { type OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'\nimport {\n type DateInputItem,\n type Label,\n type RenderContext,\n type ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/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\n/**\n * Lowercases the start of a message for concatenation.\n * Handles patterns like:\n * - \"Enter latitude\" -> \"enter latitude\"\n * - \"Enter a valid latitude...\" -> \"enter a valid latitude...\"\n * - \"latitude must be...\" -> \"latitude must be...\"\n */\nfunction lowercaseMessageStart(message: string): string {\n // Lowercase first character for a message\n return message.charAt(0).toLowerCase() + message.slice(1)\n}\n\nexport function joinWithAnd(items: string[], translator: Translator): string {\n const { t } = translator\n if (items.length === 2) {\n return `${items[0]} ${t('common.and')} ${items[1]}`\n }\n\n const leading = items.slice(0, -1).join(', ')\n const last = items[items.length - 1]\n return `${leading} ${t('common.and')} ${last}`\n}\n\nexport function formatErrorList(\n messages: string[],\n translator: Translator\n): string {\n if (!messages.length) {\n return ''\n }\n\n if (messages.length === 1) {\n return messages[0]\n }\n\n const formattedMessages = messages.map((msg, index) =>\n index === 0 ? msg : lowercaseMessageStart(msg)\n )\n\n return joinWithAnd(formattedMessages, translator)\n}\n\nexport function mergeCssClasses(...classNames: (string | undefined)[]) {\n const tokens = classNames\n .flatMap((name) => name?.split(/\\s+/) ?? [])\n .map((token) => token.trim())\n .filter(Boolean)\n\n if (!tokens.length) {\n return undefined\n }\n\n return Array.from(new Set(tokens)).join(' ')\n}\n\nexport function deduplicateErrorsByHref(\n errors?: FormSubmissionError[]\n): FormSubmissionError[] | undefined {\n if (!errors?.length) {\n return undefined\n }\n\n return errors.filter(\n (error, index, self) =>\n index === self.findIndex((err) => err.href === error.href)\n )\n}\n\nexport function getMapLayers(\n component:\n | GeospatialField\n | OsGridRefField\n | LatLongField\n | EastingNorthingField\n) {\n const { mapLayers } = component.options\n const layers = []\n\n if (mapLayers?.sssi) {\n layers.push('sssi')\n }\n\n return layers.join(',')\n}\n\nexport function getLocationFieldViewModel(\n component: LocationField,\n viewModel: ViewModel & {\n label: Label\n id: string\n name: string\n value: FormValue\n },\n context: RenderContext\n) {\n const { collection } = component\n const { fieldset: existingFieldset, label } = viewModel\n\n const subViewModels = collection.getViewModel(context)\n\n const fieldErrors: string[] = []\n subViewModels.forEach(({ model }) => {\n if (model.errorMessage?.text) {\n fieldErrors.push(model.errorMessage.text)\n }\n })\n\n const hasFieldErrors = fieldErrors.length > 0\n\n const items: DateInputItem[] = subViewModels.map(\n ({ model }): DateInputItem => {\n let { label, type, value, classes, prefix, suffix, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text\n }\n\n if (!isFormValue(value)) {\n value = undefined\n }\n\n const baseItem: DateInputItem = {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes: mergeCssClasses(\n classes,\n hasFieldErrors ? 'govuk-input--error' : undefined\n ),\n prefix,\n suffix\n }\n\n if (!hasFieldErrors && errorMessage) {\n baseItem.errorMessage = errorMessage\n }\n\n return baseItem\n }\n )\n\n const showFieldsetError =\n hasFieldErrors || Boolean(viewModel.errorMessage?.text)\n\n viewModel.showFieldsetError = showFieldsetError\n\n if (hasFieldErrors) {\n viewModel.errorMessage = {\n text:\n fieldErrors.length === 1\n ? fieldErrors[0]\n : formatErrorList(fieldErrors, context.translator)\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 mapLayers: getMapLayers(component),\n country:\n component instanceof EastingNorthingField\n ? component.options.countries?.at(0)\n : undefined\n }\n\n if (component.options.instructionText) {\n return {\n ...result,\n instructionText: component.options.instructionText\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":"AAEA,SAASA,oBAAoB;AAC7B,SAASC,WAAW;AAqBpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,qBAAqBA,CAACC,OAAe,EAAU;EACtD;EACA,OAAOA,OAAO,CAACC,MAAM,CAAC,CAAC,CAAC,CAACC,WAAW,CAAC,CAAC,GAAGF,OAAO,CAACG,KAAK,CAAC,CAAC,CAAC;AAC3D;AAEA,OAAO,SAASC,WAAWA,CAACC,KAAe,EAAEC,UAAsB,EAAU;EAC3E,MAAM;IAAEC;EAAE,CAAC,GAAGD,UAAU;EACxB,IAAID,KAAK,CAACG,MAAM,KAAK,CAAC,EAAE;IACtB,OAAO,GAAGH,KAAK,CAAC,CAAC,CAAC,IAAIE,CAAC,CAAC,YAAY,CAAC,IAAIF,KAAK,CAAC,CAAC,CAAC,EAAE;EACrD;EAEA,MAAMI,OAAO,GAAGJ,KAAK,CAACF,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAACO,IAAI,CAAC,IAAI,CAAC;EAC7C,MAAMC,IAAI,GAAGN,KAAK,CAACA,KAAK,CAACG,MAAM,GAAG,CAAC,CAAC;EACpC,OAAO,GAAGC,OAAO,IAAIF,CAAC,CAAC,YAAY,CAAC,IAAII,IAAI,EAAE;AAChD;AAEA,OAAO,SAASC,eAAeA,CAC7BC,QAAkB,EAClBP,UAAsB,EACd;EACR,IAAI,CAACO,QAAQ,CAACL,MAAM,EAAE;IACpB,OAAO,EAAE;EACX;EAEA,IAAIK,QAAQ,CAACL,MAAM,KAAK,CAAC,EAAE;IACzB,OAAOK,QAAQ,CAAC,CAAC,CAAC;EACpB;EAEA,MAAMC,iBAAiB,GAAGD,QAAQ,CAACE,GAAG,CAAC,CAACC,GAAG,EAAEC,KAAK,KAChDA,KAAK,KAAK,CAAC,GAAGD,GAAG,GAAGjB,qBAAqB,CAACiB,GAAG,CAC/C,CAAC;EAED,OAAOZ,WAAW,CAACU,iBAAiB,EAAER,UAAU,CAAC;AACnD;AAEA,OAAO,SAASY,eAAeA,CAAC,GAAGC,UAAkC,EAAE;EACrE,MAAMC,MAAM,GAAGD,UAAU,CACtBE,OAAO,CAAEC,IAAI,IAAKA,IAAI,EAAEC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAC3CR,GAAG,CAAES,KAAK,IAAKA,KAAK,CAACC,IAAI,CAAC,CAAC,CAAC,CAC5BC,MAAM,CAACC,OAAO,CAAC;EAElB,IAAI,CAACP,MAAM,CAACZ,MAAM,EAAE;IAClB,OAAOoB,SAAS;EAClB;EAEA,OAAOC,KAAK,CAACC,IAAI,CAAC,IAAIC,GAAG,CAACX,MAAM,CAAC,CAAC,CAACV,IAAI,CAAC,GAAG,CAAC;AAC9C;AAEA,OAAO,SAASsB,uBAAuBA,CACrCC,MAA8B,EACK;EACnC,IAAI,CAACA,MAAM,EAAEzB,MAAM,EAAE;IACnB,OAAOoB,SAAS;EAClB;EAEA,OAAOK,MAAM,CAACP,MAAM,CAClB,CAACQ,KAAK,EAAEjB,KAAK,EAAEkB,IAAI,KACjBlB,KAAK,KAAKkB,IAAI,CAACC,SAAS,CAAEC,GAAG,IAAKA,GAAG,CAACC,IAAI,KAAKJ,KAAK,CAACI,IAAI,CAC7D,CAAC;AACH;AAEA,OAAO,SAASC,YAAYA,CAC1BC,SAIwB,EACxB;EACA,MAAM;IAAEC;EAAU,CAAC,GAAGD,SAAS,CAACE,OAAO;EACvC,MAAMC,MAAM,GAAG,EAAE;EAEjB,IAAIF,SAAS,EAAEG,IAAI,EAAE;IACnBD,MAAM,CAACE,IAAI,CAAC,MAAM,CAAC;EACrB;EAEA,OAAOF,MAAM,CAACjC,IAAI,CAAC,GAAG,CAAC;AACzB;AAEA,OAAO,SAASoC,yBAAyBA,CACvCN,SAAwB,EACxBO,SAKC,EACDC,OAAsB,EACtB;EACA,MAAM;IAAEC;EAAW,CAAC,GAAGT,SAAS;EAChC,MAAM;IAAEU,QAAQ,EAAEC,gBAAgB;IAAEC;EAAM,CAAC,GAAGL,SAAS;EAEvD,MAAMM,aAAa,GAAGJ,UAAU,CAACK,YAAY,CAACN,OAAO,CAAC;EAEtD,MAAMO,WAAqB,GAAG,EAAE;EAChCF,aAAa,CAACG,OAAO,CAAC,CAAC;IAAEC;EAAM,CAAC,KAAK;IACnC,IAAIA,KAAK,CAACC,YAAY,EAAEC,IAAI,EAAE;MAC5BJ,WAAW,CAACV,IAAI,CAACY,KAAK,CAACC,YAAY,CAACC,IAAI,CAAC;IAC3C;EACF,CAAC,CAAC;EAEF,MAAMC,cAAc,GAAGL,WAAW,CAAC/C,MAAM,GAAG,CAAC;EAE7C,MAAMH,KAAsB,GAAGgD,aAAa,CAACtC,GAAG,CAC9C,CAAC;IAAE0C;EAAM,CAAC,KAAoB;IAC5B,IAAI;MAAEL,KAAK;MAAES,IAAI;MAAEC,KAAK;MAAEC,OAAO;MAAEC,MAAM;MAAEC,MAAM;MAAEP;IAAa,CAAC,GAAGD,KAAK;IAEzE,IAAIL,KAAK,EAAE;MACTA,KAAK,CAACc,QAAQ,GAAG,MAAMd,KAAK,CAACO,IAAI;IACnC;IAEA,IAAI,CAAC7D,WAAW,CAACgE,KAAK,CAAC,EAAE;MACvBA,KAAK,GAAGlC,SAAS;IACnB;IAEA,MAAMuC,QAAuB,GAAG;MAC9Bf,KAAK;MACLgB,EAAE,EAAEX,KAAK,CAACW,EAAE;MACZ9C,IAAI,EAAEmC,KAAK,CAACnC,IAAI;MAChBuC,IAAI;MACJC,KAAK;MACLC,OAAO,EAAE7C,eAAe,CACtB6C,OAAO,EACPH,cAAc,GAAG,oBAAoB,GAAGhC,SAC1C,CAAC;MACDoC,MAAM;MACNC;IACF,CAAC;IAED,IAAI,CAACL,cAAc,IAAIF,YAAY,EAAE;MACnCS,QAAQ,CAACT,YAAY,GAAGA,YAAY;IACtC;IAEA,OAAOS,QAAQ;EACjB,CACF,CAAC;EAED,MAAME,iBAAiB,GACrBT,cAAc,IAAIjC,OAAO,CAACoB,SAAS,CAACW,YAAY,EAAEC,IAAI,CAAC;EAEzDZ,SAAS,CAACsB,iBAAiB,GAAGA,iBAAiB;EAE/C,IAAIT,cAAc,EAAE;IAClBb,SAAS,CAACW,YAAY,GAAG;MACvBC,IAAI,EACFJ,WAAW,CAAC/C,MAAM,KAAK,CAAC,GACpB+C,WAAW,CAAC,CAAC,CAAC,GACd3C,eAAe,CAAC2C,WAAW,EAAEP,OAAO,CAAC1C,UAAU;IACvD,CAAC;EACH;EAEA,MAAM4C,QAAQ,GAAGC,gBAAgB,IAAI;IACnCmB,MAAM,EAAE;MACNX,IAAI,EAAEP,KAAK,CAACO,IAAI;MAChBI,OAAO,EAAE;IACX;EACF,CAAC;EAED,MAAMQ,MAAM,GAAG;IACb,GAAGxB,SAAS;IACZG,QAAQ;IACR7C,KAAK;IACLoC,SAAS,EAAEF,YAAY,CAACC,SAAS,CAAC;IAClCgC,OAAO,EACLhC,SAAS,YAAY3C,oBAAoB,GACrC2C,SAAS,CAACE,OAAO,CAAC+B,SAAS,EAAEC,EAAE,CAAC,CAAC,CAAC,GAClC9C;EACR,CAAC;EAED,IAAIY,SAAS,CAACE,OAAO,CAACiC,eAAe,EAAE;IACrC,OAAO;MACL,GAAGJ,MAAM;MACTI,eAAe,EAAEnC,SAAS,CAACE,OAAO,CAACiC;IACrC,CAAC;EACH;EAEA,OAAOJ,MAAM;AACf;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASK,4BAA4BA,CAC1CpC,SAAwB,EACP;EACjB,OAAO,CAACqC,OAAoB,EAAEC,OAAO,KAAK;IACxC,MAAM;MAAE7B,UAAU;MAAE3B,IAAI;MAAEoB;IAAQ,CAAC,GAAGF,SAAS;IAE/C,MAAMuC,MAAM,GAAGvC,SAAS,CAACwC,qBAAqB,CAC5CxC,SAAS,CAACyC,qBAAqB,CAACJ,OAAO,CACzC,CAAC;IAED,MAAM7B,OAAgB,GAAG;MACvBkC,OAAO,EAAEjC,UAAU,CAACkC,IAAI;MACxBC,GAAG,EAAE9D;IACP,CAAC;IAED,IAAI,CAACkB,SAAS,CAAC6C,OAAO,CAACN,MAAM,CAAC,EAAE;MAC9B,OAAOrC,OAAO,CAAC4C,QAAQ,KAAK,KAAK,GAC7BR,OAAO,CAAC5C,KAAK,CAAC,iBAAiB,EAAEc,OAAO,CAAC,GACzC6B,OAAO;IACb;IAEA,OAAOA,OAAO;EAChB,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"file":"LocationFieldHelpers.js","names":["EastingNorthingField","isFormValue","lowercaseMessageStart","message","charAt","toLowerCase","slice","joinWithAnd","items","translator","t","length","leading","join","last","formatErrorList","messages","formattedMessages","map","msg","index","mergeCssClasses","classNames","tokens","flatMap","name","split","token","trim","filter","Boolean","undefined","Array","from","Set","deduplicateErrorsByHref","errors","error","self","findIndex","err","href","getMapLayers","component","mapLayers","options","layers","sssi","push","getLocationFieldViewModel","viewModel","context","collection","fieldset","existingFieldset","label","subViewModels","getViewModel","fieldErrors","forEach","model","errorMessage","text","hasFieldErrors","type","value","classes","prefix","suffix","toString","baseItem","id","showFieldsetError","legend","result","country","countries","at","instructionText","tComponent","instructionSummary","def","createLocationFieldValidator","payload","helpers","values","getFormValueFromState","getStateFromValidForm","missing","keys","key","isState","required"],"sources":["../../../../../src/server/plugins/engine/components/LocationFieldHelpers.ts"],"sourcesContent":["import { type Context, type CustomValidator } from 'joi'\n\nimport { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type GeospatialField } from '~/src/server/plugins/engine/components/GeospatialField.js'\nimport { type LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nimport { type OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'\nimport {\n type DateInputItem,\n type Label,\n type RenderContext,\n type ViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/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\n/**\n * Lowercases the start of a message for concatenation.\n * Handles patterns like:\n * - \"Enter latitude\" -> \"enter latitude\"\n * - \"Enter a valid latitude...\" -> \"enter a valid latitude...\"\n * - \"latitude must be...\" -> \"latitude must be...\"\n */\nfunction lowercaseMessageStart(message: string): string {\n // Lowercase first character for a message\n return message.charAt(0).toLowerCase() + message.slice(1)\n}\n\nexport function joinWithAnd(items: string[], translator: Translator): string {\n const { t } = translator\n if (items.length === 2) {\n return `${items[0]} ${t('common.and')} ${items[1]}`\n }\n\n const leading = items.slice(0, -1).join(', ')\n const last = items[items.length - 1]\n return `${leading} ${t('common.and')} ${last}`\n}\n\nexport function formatErrorList(\n messages: string[],\n translator: Translator\n): string {\n if (!messages.length) {\n return ''\n }\n\n if (messages.length === 1) {\n return messages[0]\n }\n\n const formattedMessages = messages.map((msg, index) =>\n index === 0 ? msg : lowercaseMessageStart(msg)\n )\n\n return joinWithAnd(formattedMessages, translator)\n}\n\nexport function mergeCssClasses(...classNames: (string | undefined)[]) {\n const tokens = classNames\n .flatMap((name) => name?.split(/\\s+/) ?? [])\n .map((token) => token.trim())\n .filter(Boolean)\n\n if (!tokens.length) {\n return undefined\n }\n\n return Array.from(new Set(tokens)).join(' ')\n}\n\nexport function deduplicateErrorsByHref(\n errors?: FormSubmissionError[]\n): FormSubmissionError[] | undefined {\n if (!errors?.length) {\n return undefined\n }\n\n return errors.filter(\n (error, index, self) =>\n index === self.findIndex((err) => err.href === error.href)\n )\n}\n\nexport function getMapLayers(\n component:\n | GeospatialField\n | OsGridRefField\n | LatLongField\n | EastingNorthingField\n) {\n const { mapLayers } = component.options\n const layers = []\n\n if (mapLayers?.sssi) {\n layers.push('sssi')\n }\n\n return layers.join(',')\n}\n\nexport function getLocationFieldViewModel(\n component: LocationField,\n viewModel: ViewModel & {\n label: Label\n id: string\n name: string\n value: FormValue\n },\n context: RenderContext\n) {\n const { collection } = component\n const { fieldset: existingFieldset, label } = viewModel\n\n const subViewModels = collection.getViewModel(context)\n\n const fieldErrors: string[] = []\n subViewModels.forEach(({ model }) => {\n if (model.errorMessage?.text) {\n fieldErrors.push(model.errorMessage.text)\n }\n })\n\n const hasFieldErrors = fieldErrors.length > 0\n\n const items: DateInputItem[] = subViewModels.map(\n ({ model }): DateInputItem => {\n let { label, type, value, classes, prefix, suffix, errorMessage } = model\n\n if (label) {\n label.toString = () => label.text\n }\n\n if (!isFormValue(value)) {\n value = undefined\n }\n\n const baseItem: DateInputItem = {\n label,\n id: model.id,\n name: model.name,\n type,\n value,\n classes: mergeCssClasses(\n classes,\n hasFieldErrors ? 'govuk-input--error' : undefined\n ),\n prefix,\n suffix\n }\n\n if (!hasFieldErrors && errorMessage) {\n baseItem.errorMessage = errorMessage\n }\n\n return baseItem\n }\n )\n\n const showFieldsetError =\n hasFieldErrors || Boolean(viewModel.errorMessage?.text)\n\n viewModel.showFieldsetError = showFieldsetError\n\n if (hasFieldErrors) {\n viewModel.errorMessage = {\n text:\n fieldErrors.length === 1\n ? fieldErrors[0]\n : formatErrorList(fieldErrors, context.translator)\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 mapLayers: getMapLayers(component),\n country:\n component instanceof EastingNorthingField\n ? component.options.countries?.at(0)\n : undefined\n }\n\n if (component.options.instructionText) {\n const { t, tComponent } = context.translator\n\n return {\n ...result,\n instructionSummary: t('components.locationFieldBase.howToFind'),\n instructionText:\n tComponent(component.def, 'instructionText') ||\n component.options.instructionText,\n t\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":"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","ignoreList":[]}
@@ -2,5 +2,10 @@ import { type GeospatialFieldComponent, type GeospatialFieldOptionsCountry } fro
2
2
  import JoiBase from 'joi';
3
3
  import { type EastingNorthingField } from '../../../../../server/plugins/engine/components/EastingNorthingField.js';
4
4
  import { type Feature } from '../../../../../server/plugins/engine/types.js';
5
+ export declare function determineLimits(def: GeospatialFieldComponent, isOptional: boolean): {
6
+ min?: number;
7
+ max?: number;
8
+ length?: number;
9
+ };
5
10
  export declare function getGeospatialSchema(def: GeospatialFieldComponent): JoiBase.ArraySchema<Feature[]>;
6
11
  export declare function getEastingNorthingCountryValidator(field: EastingNorthingField, country: GeospatialFieldOptionsCountry): JoiBase.CustomValidator;
@@ -1,4 +1,3 @@
1
- import { GeospatialFieldOptionsCountryEnum } from '@defra/forms-model';
2
1
  import Bourne from '@hapi/bourne';
3
2
  import { booleanWithin } from '@turf/boolean-within';
4
3
  import { point } from '@turf/helpers';
@@ -6,12 +5,6 @@ import { point } from '@turf/helpers';
6
5
  import OsGridRef from 'geodesy/osgridref.js';
7
6
  import JoiBase from 'joi';
8
7
  import { countries } from "../../../map/routes/index.js";
9
- const countriesDesc = {
10
- [GeospatialFieldOptionsCountryEnum.England]: 'England',
11
- [GeospatialFieldOptionsCountryEnum.NorthernIreland]: 'Northern Ireland',
12
- [GeospatialFieldOptionsCountryEnum.Scotland]: 'Scotland',
13
- [GeospatialFieldOptionsCountryEnum.Wales]: 'Wales'
14
- };
15
8
  const Joi = JoiBase.extend({
16
9
  type: 'array',
17
10
  base: JoiBase.array(),
@@ -76,21 +69,37 @@ const featureSchema = Joi.object().keys({
76
69
  properties: featurePropertiesSchema,
77
70
  geometry: featureGeometrySchema
78
71
  });
79
- function applySchemaConstraints(schema, def) {
72
+ export function determineLimits(def, isOptional) {
80
73
  const {
81
- options,
82
74
  schema: constraints
83
75
  } = def;
84
- const isOptional = options.required === false;
76
+ const limits = {};
85
77
  if (typeof constraints?.length === 'number') {
86
- schema = schema.length(constraints.length);
78
+ limits.length = constraints.length;
87
79
  } else {
88
80
  if (typeof constraints?.min === 'number') {
89
- schema = schema.min(constraints.min);
81
+ limits.min = constraints.min;
90
82
  } else if (!isOptional) {
91
- schema = schema.min(1);
83
+ limits.min = 1;
92
84
  }
93
- schema = schema.max(typeof constraints?.max === 'number' ? constraints.max : 50);
85
+ limits.max = typeof constraints?.max === 'number' ? constraints.max : 50;
86
+ }
87
+ return limits;
88
+ }
89
+ function applySchemaConstraints(schema, def) {
90
+ const {
91
+ options
92
+ } = def;
93
+ const isOptional = options.required === false;
94
+ const limits = determineLimits(def, isOptional);
95
+ if (limits.length !== undefined) {
96
+ schema = schema.length(limits.length);
97
+ }
98
+ if (limits.min !== undefined) {
99
+ schema = schema.min(limits.min);
100
+ }
101
+ if (limits.max !== undefined) {
102
+ schema = schema.max(limits.max);
94
103
  }
95
104
  if (isOptional) {
96
105
  schema = schema.optional();
@@ -116,7 +125,7 @@ export function getGeospatialSchema(def) {
116
125
  const result = booleanWithin(value, countryFeature);
117
126
  if (!result) {
118
127
  return helpers.error('any.custom', {
119
- country: countriesDesc[country]
128
+ country
120
129
  });
121
130
  }
122
131
 
@@ -143,7 +152,7 @@ export function getEastingNorthingCountryValidator(field, country) {
143
152
  const within = booleanWithin(coord, countryFeature);
144
153
  if (!within) {
145
154
  return helpers.error('any.custom', {
146
- country: countriesDesc[country]
155
+ country
147
156
  }, {
148
157
  path: [field.name]
149
158
  });
@@ -1 +1 @@
1
- {"version":3,"file":"geospatial.js","names":["GeospatialFieldOptionsCountryEnum","Bourne","booleanWithin","point","OsGridRef","JoiBase","countries","countriesDesc","England","NorthernIreland","Scotland","Wales","Joi","extend","type","base","array","messages","coerce","from","method","value","helpers","trimmed","trim","undefined","parse","result","errors","error","coordinatesSchema","items","number","required","featurePropertiesSchema","object","keys","description","string","coordinateGridReference","centroidGridReference","featureGeometrySchema","valid","coordinates","when","switch","is","then","min","featureSchema","id","properties","geometry","applySchemaConstraints","schema","def","options","constraints","isOptional","length","max","optional","getGeospatialSchema","country","at","unique","validateCountryBounds","countryFeature","features","find","feature","custom","getEastingNorthingCountryValidator","field","eastingNorthing","getFormValueFromState","gridRef","easting","northing","latLong","toLatLon","coord","longitude","latitude","within","path","name"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/geospatial.ts"],"sourcesContent":["import {\n GeospatialFieldOptionsCountryEnum,\n type GeospatialFieldComponent,\n type GeospatialFieldOptionsCountry\n} from '@defra/forms-model'\nimport Bourne from '@hapi/bourne'\nimport { booleanWithin } from '@turf/boolean-within'\nimport { point } from '@turf/helpers'\n// @ts-expect-error - no types\nimport OsGridRef from 'geodesy/osgridref.js'\nimport JoiBase, { type CustomValidator } from 'joi'\n\nimport { type EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.js'\nimport {\n type Coordinates,\n type Feature,\n type FeatureProperties,\n type Geometry\n} from '~/src/server/plugins/engine/types.js'\nimport { countries } from '~/src/server/plugins/map/routes/index.js'\n\nconst countriesDesc: Record<GeospatialFieldOptionsCountryEnum, string> = {\n [GeospatialFieldOptionsCountryEnum.England]: 'England',\n [GeospatialFieldOptionsCountryEnum.NorthernIreland]: 'Northern Ireland',\n [GeospatialFieldOptionsCountryEnum.Scotland]: 'Scotland',\n [GeospatialFieldOptionsCountryEnum.Wales]: 'Wales'\n}\n\nconst Joi = JoiBase.extend({\n type: 'array',\n base: JoiBase.array(),\n messages: {\n 'object.invalidjson': '{{#label}} must be a valid json array string'\n },\n coerce: {\n from: 'string',\n method(value, helpers) {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed === '' || trimmed === '[]') {\n return {\n value: undefined\n }\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n return { value: Bourne.parse(value) }\n } catch {\n const result = {\n value,\n errors: [helpers.error('object.invalidjson')]\n }\n\n return result\n }\n } else {\n return {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n value\n }\n }\n }\n }\n}) as JoiBase.Root\n\nconst coordinatesSchema = Joi.array<Coordinates[]>()\n .items(Joi.number().required(), Joi.number().required())\n .required()\n\nconst featurePropertiesSchema = Joi.object<FeatureProperties>()\n .keys({\n description: Joi.string().required(),\n coordinateGridReference: Joi.string().required(),\n centroidGridReference: Joi.string().required()\n })\n .required()\n\nconst featureGeometrySchema = Joi.object<Geometry>().keys({\n type: Joi.string().valid('Point', 'LineString', 'Polygon').required(),\n coordinates: Joi.array()\n .when('type', {\n switch: [\n { is: 'Point', then: coordinatesSchema },\n {\n is: 'LineString',\n then: Joi.array().items(coordinatesSchema).min(2)\n },\n {\n is: 'Polygon',\n then: Joi.array().items(Joi.array().items(coordinatesSchema).min(3))\n }\n ]\n })\n .required()\n})\n\nconst featureSchema = Joi.object<Feature>().keys({\n id: Joi.string().required(),\n type: Joi.string().valid('Feature').required(),\n properties: featurePropertiesSchema,\n geometry: featureGeometrySchema\n})\n\nfunction applySchemaConstraints(\n schema: JoiBase.ArraySchema<Feature[]>,\n def: GeospatialFieldComponent\n) {\n const { options, schema: constraints } = def\n const isOptional = options.required === false\n\n if (typeof constraints?.length === 'number') {\n schema = schema.length(constraints.length)\n } else {\n if (typeof constraints?.min === 'number') {\n schema = schema.min(constraints.min)\n } else if (!isOptional) {\n schema = schema.min(1)\n }\n\n schema = schema.max(\n typeof constraints?.max === 'number' ? constraints.max : 50\n )\n }\n\n if (isOptional) {\n schema = schema.optional()\n } else {\n schema = schema.required()\n }\n\n return schema\n}\n\nexport function getGeospatialSchema(\n def: GeospatialFieldComponent\n): JoiBase.ArraySchema<Feature[]> {\n const { options = {} } = def\n const country: GeospatialFieldOptionsCountry | undefined =\n options.countries?.at(0)\n\n if (!country) {\n return applySchemaConstraints(\n Joi.array<Feature[]>().items(featureSchema).unique('id'),\n def\n )\n }\n\n const validateCountryBounds: CustomValidator = (value, helpers) => {\n const countryFeature = countries.features.find(\n (feature) => feature.id === country\n )\n\n if (!countryFeature) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n const result = booleanWithin(value as Geometry | Feature, countryFeature)\n\n if (!result) {\n return helpers.error('any.custom', {\n country: countriesDesc[country as GeospatialFieldOptionsCountryEnum]\n })\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n return applySchemaConstraints(\n Joi.array<Feature[]>()\n .items(featureSchema.custom(validateCountryBounds))\n .unique('id'),\n def\n )\n}\n\nexport function getEastingNorthingCountryValidator(\n field: EastingNorthingField,\n country: GeospatialFieldOptionsCountry\n) {\n const validateCountryBounds: CustomValidator = (value, helpers) => {\n const countryFeature = countries.features.find(\n (feature) => feature.id === country\n )\n\n if (!countryFeature) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n const eastingNorthing = field.getFormValueFromState(\n value as EastingNorthingState\n )\n\n if (!eastingNorthing) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n const gridRef = new OsGridRef(\n eastingNorthing.easting,\n eastingNorthing.northing\n )\n const latLong = gridRef.toLatLon()\n const coord = point([latLong.longitude, latLong.latitude])\n const within = booleanWithin(coord, countryFeature)\n\n if (!within) {\n return helpers.error(\n 'any.custom',\n {\n country: countriesDesc[country as GeospatialFieldOptionsCountryEnum]\n },\n { path: [field.name] }\n )\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n return validateCountryBounds\n}\n"],"mappings":"AAAA,SACEA,iCAAiC,QAG5B,oBAAoB;AAC3B,OAAOC,MAAM,MAAM,cAAc;AACjC,SAASC,aAAa,QAAQ,sBAAsB;AACpD,SAASC,KAAK,QAAQ,eAAe;AACrC;AACA,OAAOC,SAAS,MAAM,sBAAsB;AAC5C,OAAOC,OAAO,MAAgC,KAAK;AAUnD,SAASC,SAAS;AAElB,MAAMC,aAAgE,GAAG;EACvE,CAACP,iCAAiC,CAACQ,OAAO,GAAG,SAAS;EACtD,CAACR,iCAAiC,CAACS,eAAe,GAAG,kBAAkB;EACvE,CAACT,iCAAiC,CAACU,QAAQ,GAAG,UAAU;EACxD,CAACV,iCAAiC,CAACW,KAAK,GAAG;AAC7C,CAAC;AAED,MAAMC,GAAG,GAAGP,OAAO,CAACQ,MAAM,CAAC;EACzBC,IAAI,EAAE,OAAO;EACbC,IAAI,EAAEV,OAAO,CAACW,KAAK,CAAC,CAAC;EACrBC,QAAQ,EAAE;IACR,oBAAoB,EAAE;EACxB,CAAC;EACDC,MAAM,EAAE;IACNC,IAAI,EAAE,QAAQ;IACdC,MAAMA,CAACC,KAAK,EAAEC,OAAO,EAAE;MACrB,IAAI,OAAOD,KAAK,KAAK,QAAQ,EAAE;QAC7B,MAAME,OAAO,GAAGF,KAAK,CAACG,IAAI,CAAC,CAAC;QAC5B,IAAID,OAAO,KAAK,EAAE,IAAIA,OAAO,KAAK,IAAI,EAAE;UACtC,OAAO;YACLF,KAAK,EAAEI;UACT,CAAC;QACH;QAEA,IAAI;UACF;UACA,OAAO;YAAEJ,KAAK,EAAEpB,MAAM,CAACyB,KAAK,CAACL,KAAK;UAAE,CAAC;QACvC,CAAC,CAAC,MAAM;UACN,MAAMM,MAAM,GAAG;YACbN,KAAK;YACLO,MAAM,EAAE,CAACN,OAAO,CAACO,KAAK,CAAC,oBAAoB,CAAC;UAC9C,CAAC;UAED,OAAOF,MAAM;QACf;MACF,CAAC,MAAM;QACL,OAAO;UACL;UACAN;QACF,CAAC;MACH;IACF;EACF;AACF,CAAC,CAAiB;AAElB,MAAMS,iBAAiB,GAAGlB,GAAG,CAACI,KAAK,CAAgB,CAAC,CACjDe,KAAK,CAACnB,GAAG,CAACoB,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC,EAAErB,GAAG,CAACoB,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC,CAAC,CACvDA,QAAQ,CAAC,CAAC;AAEb,MAAMC,uBAAuB,GAAGtB,GAAG,CAACuB,MAAM,CAAoB,CAAC,CAC5DC,IAAI,CAAC;EACJC,WAAW,EAAEzB,GAAG,CAAC0B,MAAM,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EACpCM,uBAAuB,EAAE3B,GAAG,CAAC0B,MAAM,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EAChDO,qBAAqB,EAAE5B,GAAG,CAAC0B,MAAM,CAAC,CAAC,CAACL,QAAQ,CAAC;AAC/C,CAAC,CAAC,CACDA,QAAQ,CAAC,CAAC;AAEb,MAAMQ,qBAAqB,GAAG7B,GAAG,CAACuB,MAAM,CAAW,CAAC,CAACC,IAAI,CAAC;EACxDtB,IAAI,EAAEF,GAAG,CAAC0B,MAAM,CAAC,CAAC,CAACI,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,CAACT,QAAQ,CAAC,CAAC;EACrEU,WAAW,EAAE/B,GAAG,CAACI,KAAK,CAAC,CAAC,CACrB4B,IAAI,CAAC,MAAM,EAAE;IACZC,MAAM,EAAE,CACN;MAAEC,EAAE,EAAE,OAAO;MAAEC,IAAI,EAAEjB;IAAkB,CAAC,EACxC;MACEgB,EAAE,EAAE,YAAY;MAChBC,IAAI,EAAEnC,GAAG,CAACI,KAAK,CAAC,CAAC,CAACe,KAAK,CAACD,iBAAiB,CAAC,CAACkB,GAAG,CAAC,CAAC;IAClD,CAAC,EACD;MACEF,EAAE,EAAE,SAAS;MACbC,IAAI,EAAEnC,GAAG,CAACI,KAAK,CAAC,CAAC,CAACe,KAAK,CAACnB,GAAG,CAACI,KAAK,CAAC,CAAC,CAACe,KAAK,CAACD,iBAAiB,CAAC,CAACkB,GAAG,CAAC,CAAC,CAAC;IACrE,CAAC;EAEL,CAAC,CAAC,CACDf,QAAQ,CAAC;AACd,CAAC,CAAC;AAEF,MAAMgB,aAAa,GAAGrC,GAAG,CAACuB,MAAM,CAAU,CAAC,CAACC,IAAI,CAAC;EAC/Cc,EAAE,EAAEtC,GAAG,CAAC0B,MAAM,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EAC3BnB,IAAI,EAAEF,GAAG,CAAC0B,MAAM,CAAC,CAAC,CAACI,KAAK,CAAC,SAAS,CAAC,CAACT,QAAQ,CAAC,CAAC;EAC9CkB,UAAU,EAAEjB,uBAAuB;EACnCkB,QAAQ,EAAEX;AACZ,CAAC,CAAC;AAEF,SAASY,sBAAsBA,CAC7BC,MAAsC,EACtCC,GAA6B,EAC7B;EACA,MAAM;IAAEC,OAAO;IAAEF,MAAM,EAAEG;EAAY,CAAC,GAAGF,GAAG;EAC5C,MAAMG,UAAU,GAAGF,OAAO,CAACvB,QAAQ,KAAK,KAAK;EAE7C,IAAI,OAAOwB,WAAW,EAAEE,MAAM,KAAK,QAAQ,EAAE;IAC3CL,MAAM,GAAGA,MAAM,CAACK,MAAM,CAACF,WAAW,CAACE,MAAM,CAAC;EAC5C,CAAC,MAAM;IACL,IAAI,OAAOF,WAAW,EAAET,GAAG,KAAK,QAAQ,EAAE;MACxCM,MAAM,GAAGA,MAAM,CAACN,GAAG,CAACS,WAAW,CAACT,GAAG,CAAC;IACtC,CAAC,MAAM,IAAI,CAACU,UAAU,EAAE;MACtBJ,MAAM,GAAGA,MAAM,CAACN,GAAG,CAAC,CAAC,CAAC;IACxB;IAEAM,MAAM,GAAGA,MAAM,CAACM,GAAG,CACjB,OAAOH,WAAW,EAAEG,GAAG,KAAK,QAAQ,GAAGH,WAAW,CAACG,GAAG,GAAG,EAC3D,CAAC;EACH;EAEA,IAAIF,UAAU,EAAE;IACdJ,MAAM,GAAGA,MAAM,CAACO,QAAQ,CAAC,CAAC;EAC5B,CAAC,MAAM;IACLP,MAAM,GAAGA,MAAM,CAACrB,QAAQ,CAAC,CAAC;EAC5B;EAEA,OAAOqB,MAAM;AACf;AAEA,OAAO,SAASQ,mBAAmBA,CACjCP,GAA6B,EACG;EAChC,MAAM;IAAEC,OAAO,GAAG,CAAC;EAAE,CAAC,GAAGD,GAAG;EAC5B,MAAMQ,OAAkD,GACtDP,OAAO,CAAClD,SAAS,EAAE0D,EAAE,CAAC,CAAC,CAAC;EAE1B,IAAI,CAACD,OAAO,EAAE;IACZ,OAAOV,sBAAsB,CAC3BzC,GAAG,CAACI,KAAK,CAAY,CAAC,CAACe,KAAK,CAACkB,aAAa,CAAC,CAACgB,MAAM,CAAC,IAAI,CAAC,EACxDV,GACF,CAAC;EACH;EAEA,MAAMW,qBAAsC,GAAGA,CAAC7C,KAAK,EAAEC,OAAO,KAAK;IACjE,MAAM6C,cAAc,GAAG7D,SAAS,CAAC8D,QAAQ,CAACC,IAAI,CAC3CC,OAAO,IAAKA,OAAO,CAACpB,EAAE,KAAKa,OAC9B,CAAC;IAED,IAAI,CAACI,cAAc,EAAE;MACnB;MACA,OAAO9C,KAAK;IACd;IAEA,MAAMM,MAAM,GAAGzB,aAAa,CAACmB,KAAK,EAAwB8C,cAAc,CAAC;IAEzE,IAAI,CAACxC,MAAM,EAAE;MACX,OAAOL,OAAO,CAACO,KAAK,CAAC,YAAY,EAAE;QACjCkC,OAAO,EAAExD,aAAa,CAACwD,OAAO;MAChC,CAAC,CAAC;IACJ;;IAEA;IACA,OAAO1C,KAAK;EACd,CAAC;EAED,OAAOgC,sBAAsB,CAC3BzC,GAAG,CAACI,KAAK,CAAY,CAAC,CACnBe,KAAK,CAACkB,aAAa,CAACsB,MAAM,CAACL,qBAAqB,CAAC,CAAC,CAClDD,MAAM,CAAC,IAAI,CAAC,EACfV,GACF,CAAC;AACH;AAEA,OAAO,SAASiB,kCAAkCA,CAChDC,KAA2B,EAC3BV,OAAsC,EACtC;EACA,MAAMG,qBAAsC,GAAGA,CAAC7C,KAAK,EAAEC,OAAO,KAAK;IACjE,MAAM6C,cAAc,GAAG7D,SAAS,CAAC8D,QAAQ,CAACC,IAAI,CAC3CC,OAAO,IAAKA,OAAO,CAACpB,EAAE,KAAKa,OAC9B,CAAC;IAED,IAAI,CAACI,cAAc,EAAE;MACnB;MACA,OAAO9C,KAAK;IACd;IAEA,MAAMqD,eAAe,GAAGD,KAAK,CAACE,qBAAqB,CACjDtD,KACF,CAAC;IAED,IAAI,CAACqD,eAAe,EAAE;MACpB;MACA,OAAOrD,KAAK;IACd;IAEA,MAAMuD,OAAO,GAAG,IAAIxE,SAAS,CAC3BsE,eAAe,CAACG,OAAO,EACvBH,eAAe,CAACI,QAClB,CAAC;IACD,MAAMC,OAAO,GAAGH,OAAO,CAACI,QAAQ,CAAC,CAAC;IAClC,MAAMC,KAAK,GAAG9E,KAAK,CAAC,CAAC4E,OAAO,CAACG,SAAS,EAAEH,OAAO,CAACI,QAAQ,CAAC,CAAC;IAC1D,MAAMC,MAAM,GAAGlF,aAAa,CAAC+E,KAAK,EAAEd,cAAc,CAAC;IAEnD,IAAI,CAACiB,MAAM,EAAE;MACX,OAAO9D,OAAO,CAACO,KAAK,CAClB,YAAY,EACZ;QACEkC,OAAO,EAAExD,aAAa,CAACwD,OAAO;MAChC,CAAC,EACD;QAAEsB,IAAI,EAAE,CAACZ,KAAK,CAACa,IAAI;MAAE,CACvB,CAAC;IACH;;IAEA;IACA,OAAOjE,KAAK;EACd,CAAC;EAED,OAAO6C,qBAAqB;AAC9B","ignoreList":[]}
1
+ {"version":3,"file":"geospatial.js","names":["Bourne","booleanWithin","point","OsGridRef","JoiBase","countries","Joi","extend","type","base","array","messages","coerce","from","method","value","helpers","trimmed","trim","undefined","parse","result","errors","error","coordinatesSchema","items","number","required","featurePropertiesSchema","object","keys","description","string","coordinateGridReference","centroidGridReference","featureGeometrySchema","valid","coordinates","when","switch","is","then","min","featureSchema","id","properties","geometry","determineLimits","def","isOptional","schema","constraints","limits","length","max","applySchemaConstraints","options","optional","getGeospatialSchema","country","at","unique","validateCountryBounds","countryFeature","features","find","feature","custom","getEastingNorthingCountryValidator","field","eastingNorthing","getFormValueFromState","gridRef","easting","northing","latLong","toLatLon","coord","longitude","latitude","within","path","name"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/geospatial.ts"],"sourcesContent":["import {\n type GeospatialFieldComponent,\n type GeospatialFieldOptionsCountry\n} from '@defra/forms-model'\nimport Bourne from '@hapi/bourne'\nimport { booleanWithin } from '@turf/boolean-within'\nimport { point } from '@turf/helpers'\n// @ts-expect-error - no types\nimport OsGridRef from 'geodesy/osgridref.js'\nimport JoiBase, { type CustomValidator } from 'joi'\n\nimport { type EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nimport { type EastingNorthingState } from '~/src/server/plugins/engine/components/types.js'\nimport {\n type Coordinates,\n type Feature,\n type FeatureProperties,\n type Geometry\n} from '~/src/server/plugins/engine/types.js'\nimport { countries } from '~/src/server/plugins/map/routes/index.js'\n\nconst Joi = JoiBase.extend({\n type: 'array',\n base: JoiBase.array(),\n messages: {\n 'object.invalidjson': '{{#label}} must be a valid json array string'\n },\n coerce: {\n from: 'string',\n method(value, helpers) {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed === '' || trimmed === '[]') {\n return {\n value: undefined\n }\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n return { value: Bourne.parse(value) }\n } catch {\n const result = {\n value,\n errors: [helpers.error('object.invalidjson')]\n }\n\n return result\n }\n } else {\n return {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n value\n }\n }\n }\n }\n}) as JoiBase.Root\n\nconst coordinatesSchema = Joi.array<Coordinates[]>()\n .items(Joi.number().required(), Joi.number().required())\n .required()\n\nconst featurePropertiesSchema = Joi.object<FeatureProperties>()\n .keys({\n description: Joi.string().required(),\n coordinateGridReference: Joi.string().required(),\n centroidGridReference: Joi.string().required()\n })\n .required()\n\nconst featureGeometrySchema = Joi.object<Geometry>().keys({\n type: Joi.string().valid('Point', 'LineString', 'Polygon').required(),\n coordinates: Joi.array()\n .when('type', {\n switch: [\n { is: 'Point', then: coordinatesSchema },\n {\n is: 'LineString',\n then: Joi.array().items(coordinatesSchema).min(2)\n },\n {\n is: 'Polygon',\n then: Joi.array().items(Joi.array().items(coordinatesSchema).min(3))\n }\n ]\n })\n .required()\n})\n\nconst featureSchema = Joi.object<Feature>().keys({\n id: Joi.string().required(),\n type: Joi.string().valid('Feature').required(),\n properties: featurePropertiesSchema,\n geometry: featureGeometrySchema\n})\n\nexport function determineLimits(\n def: GeospatialFieldComponent,\n isOptional: boolean\n) {\n const { schema: constraints } = def\n const limits: { min?: number; max?: number; length?: number } = {}\n if (typeof constraints?.length === 'number') {\n limits.length = constraints.length\n } else {\n if (typeof constraints?.min === 'number') {\n limits.min = constraints.min\n } else if (!isOptional) {\n limits.min = 1\n }\n\n limits.max = typeof constraints?.max === 'number' ? constraints.max : 50\n }\n return limits\n}\n\nfunction applySchemaConstraints(\n schema: JoiBase.ArraySchema<Feature[]>,\n def: GeospatialFieldComponent\n) {\n const { options } = def\n const isOptional = options.required === false\n\n const limits = determineLimits(def, isOptional)\n\n if (limits.length !== undefined) {\n schema = schema.length(limits.length)\n }\n if (limits.min !== undefined) {\n schema = schema.min(limits.min)\n }\n if (limits.max !== undefined) {\n schema = schema.max(limits.max)\n }\n\n if (isOptional) {\n schema = schema.optional()\n } else {\n schema = schema.required()\n }\n\n return schema\n}\n\nexport function getGeospatialSchema(\n def: GeospatialFieldComponent\n): JoiBase.ArraySchema<Feature[]> {\n const { options = {} } = def\n const country: GeospatialFieldOptionsCountry | undefined =\n options.countries?.at(0)\n\n if (!country) {\n return applySchemaConstraints(\n Joi.array<Feature[]>().items(featureSchema).unique('id'),\n def\n )\n }\n\n const validateCountryBounds: CustomValidator = (value, helpers) => {\n const countryFeature = countries.features.find(\n (feature) => feature.id === country\n )\n\n if (!countryFeature) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n const result = booleanWithin(value as Geometry | Feature, countryFeature)\n\n if (!result) {\n return helpers.error('any.custom', {\n country\n })\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n return applySchemaConstraints(\n Joi.array<Feature[]>()\n .items(featureSchema.custom(validateCountryBounds))\n .unique('id'),\n def\n )\n}\n\nexport function getEastingNorthingCountryValidator(\n field: EastingNorthingField,\n country: GeospatialFieldOptionsCountry\n) {\n const validateCountryBounds: CustomValidator = (value, helpers) => {\n const countryFeature = countries.features.find(\n (feature) => feature.id === country\n )\n\n if (!countryFeature) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n const eastingNorthing = field.getFormValueFromState(\n value as EastingNorthingState\n )\n\n if (!eastingNorthing) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n const gridRef = new OsGridRef(\n eastingNorthing.easting,\n eastingNorthing.northing\n )\n const latLong = gridRef.toLatLon()\n const coord = point([latLong.longitude, latLong.latitude])\n const within = booleanWithin(coord, countryFeature)\n\n if (!within) {\n return helpers.error(\n 'any.custom',\n {\n country\n },\n { path: [field.name] }\n )\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return value\n }\n\n return validateCountryBounds\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,15 +1,16 @@
1
- import { FormStatus, type FormMetadata } from '@defra/forms-model';
1
+ import { FormStatus, type FormDefinition, type FormMetadata } from '@defra/forms-model';
2
2
  import Boom from '@hapi/boom';
3
3
  export interface OfflineBoomData {
4
4
  offline: true;
5
5
  metadata: FormMetadata;
6
+ definition: FormDefinition | undefined;
6
7
  }
7
8
  /**
8
9
  * Throws when the form has been taken offline. The plugin's
9
10
  * unavailable-response extension catches the marker and renders the
10
11
  * "Sorry, this form is unavailable" view at HTTP 200.
11
12
  */
12
- export declare function assertFormAvailable(metadata: FormMetadata, formState: FormStatus, isPreview: boolean): void;
13
+ export declare function assertFormAvailable(metadata: FormMetadata, definition: FormDefinition | undefined, formState: FormStatus, isPreview: boolean): void;
13
14
  /** Type guard for the offline Boom marker. */
14
15
  export declare function isOfflineBoom(err: unknown): err is Boom.Boom<OfflineBoomData> & {
15
16
  data: OfflineBoomData;
@@ -5,11 +5,12 @@ import Boom from '@hapi/boom';
5
5
  * unavailable-response extension catches the marker and renders the
6
6
  * "Sorry, this form is unavailable" view at HTTP 200.
7
7
  */
8
- export function assertFormAvailable(metadata, formState, isPreview) {
8
+ export function assertFormAvailable(metadata, definition, formState, isPreview) {
9
9
  if (metadata.offline === true && formState === FormStatus.Live && !isPreview) {
10
10
  const data = {
11
11
  offline: true,
12
- metadata
12
+ metadata,
13
+ definition
13
14
  };
14
15
  throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
15
16
  statusCode: 503,
@@ -1 +1 @@
1
- {"version":3,"file":"form-availability.js","names":["FormStatus","Boom","assertFormAvailable","metadata","formState","isPreview","offline","Live","data","boomify","Error","slug","statusCode","isOfflineBoom","err","isBoom"],"sources":["../../../../src/server/plugins/engine/form-availability.ts"],"sourcesContent":["import { FormStatus, type FormMetadata } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\n\nexport interface OfflineBoomData {\n offline: true\n metadata: FormMetadata\n}\n\n/**\n * Throws when the form has been taken offline. The plugin's\n * unavailable-response extension catches the marker and renders the\n * \"Sorry, this form is unavailable\" view at HTTP 200.\n */\nexport function assertFormAvailable(\n metadata: FormMetadata,\n formState: FormStatus,\n isPreview: boolean\n): void {\n if (\n metadata.offline === true &&\n formState === FormStatus.Live &&\n !isPreview\n ) {\n const data: OfflineBoomData = { offline: true, metadata }\n throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {\n statusCode: 503,\n data\n })\n }\n}\n\n/** Type guard for the offline Boom marker. */\nexport function isOfflineBoom(\n err: unknown\n): err is Boom.Boom<OfflineBoomData> & { data: OfflineBoomData } {\n if (!Boom.isBoom(err)) return false\n const data = err.data as Partial<OfflineBoomData> | null | undefined\n return data?.offline === true && !!data.metadata\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAA2B,oBAAoB;AAClE,OAAOC,IAAI,MAAM,YAAY;AAO7B;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CACjCC,QAAsB,EACtBC,SAAqB,EACrBC,SAAkB,EACZ;EACN,IACEF,QAAQ,CAACG,OAAO,KAAK,IAAI,IACzBF,SAAS,KAAKJ,UAAU,CAACO,IAAI,IAC7B,CAACF,SAAS,EACV;IACA,MAAMG,IAAqB,GAAG;MAAEF,OAAO,EAAE,IAAI;MAAEH;IAAS,CAAC;IACzD,MAAMF,IAAI,CAACQ,OAAO,CAAC,IAAIC,KAAK,CAAC,QAAQP,QAAQ,CAACQ,IAAI,aAAa,CAAC,EAAE;MAChEC,UAAU,EAAE,GAAG;MACfJ;IACF,CAAC,CAAC;EACJ;AACF;;AAEA;AACA,OAAO,SAASK,aAAaA,CAC3BC,GAAY,EACmD;EAC/D,IAAI,CAACb,IAAI,CAACc,MAAM,CAACD,GAAG,CAAC,EAAE,OAAO,KAAK;EACnC,MAAMN,IAAI,GAAGM,GAAG,CAACN,IAAmD;EACpE,OAAOA,IAAI,EAAEF,OAAO,KAAK,IAAI,IAAI,CAAC,CAACE,IAAI,CAACL,QAAQ;AAClD","ignoreList":[]}
1
+ {"version":3,"file":"form-availability.js","names":["FormStatus","Boom","assertFormAvailable","metadata","definition","formState","isPreview","offline","Live","data","boomify","Error","slug","statusCode","isOfflineBoom","err","isBoom"],"sources":["../../../../src/server/plugins/engine/form-availability.ts"],"sourcesContent":["import {\n FormStatus,\n type FormDefinition,\n type FormMetadata\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\n\nexport interface OfflineBoomData {\n offline: true\n metadata: FormMetadata\n definition: FormDefinition | undefined\n}\n\n/**\n * Throws when the form has been taken offline. The plugin's\n * unavailable-response extension catches the marker and renders the\n * \"Sorry, this form is unavailable\" view at HTTP 200.\n */\nexport function assertFormAvailable(\n metadata: FormMetadata,\n definition: FormDefinition | undefined,\n formState: FormStatus,\n isPreview: boolean\n): void {\n if (\n metadata.offline === true &&\n formState === FormStatus.Live &&\n !isPreview\n ) {\n const data: OfflineBoomData = { offline: true, metadata, definition }\n throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {\n statusCode: 503,\n data\n })\n }\n}\n\n/** Type guard for the offline Boom marker. */\nexport function isOfflineBoom(\n err: unknown\n): err is Boom.Boom<OfflineBoomData> & { data: OfflineBoomData } {\n if (!Boom.isBoom(err)) return false\n const data = err.data as Partial<OfflineBoomData> | null | undefined\n return data?.offline === true && !!data.metadata\n}\n"],"mappings":"AAAA,SACEA,UAAU,QAGL,oBAAoB;AAC3B,OAAOC,IAAI,MAAM,YAAY;AAQ7B;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CACjCC,QAAsB,EACtBC,UAAsC,EACtCC,SAAqB,EACrBC,SAAkB,EACZ;EACN,IACEH,QAAQ,CAACI,OAAO,KAAK,IAAI,IACzBF,SAAS,KAAKL,UAAU,CAACQ,IAAI,IAC7B,CAACF,SAAS,EACV;IACA,MAAMG,IAAqB,GAAG;MAAEF,OAAO,EAAE,IAAI;MAAEJ,QAAQ;MAAEC;IAAW,CAAC;IACrE,MAAMH,IAAI,CAACS,OAAO,CAAC,IAAIC,KAAK,CAAC,QAAQR,QAAQ,CAACS,IAAI,aAAa,CAAC,EAAE;MAChEC,UAAU,EAAE,GAAG;MACfJ;IACF,CAAC,CAAC;EACJ;AACF;;AAEA;AACA,OAAO,SAASK,aAAaA,CAC3BC,GAAY,EACmD;EAC/D,IAAI,CAACd,IAAI,CAACe,MAAM,CAACD,GAAG,CAAC,EAAE,OAAO,KAAK;EACnC,MAAMN,IAAI,GAAGM,GAAG,CAACN,IAAmD;EACpE,OAAOA,IAAI,EAAEF,OAAO,KAAK,IAAI,IAAI,CAAC,CAACE,IAAI,CAACN,QAAQ;AAClD","ignoreList":[]}
@@ -16,8 +16,8 @@ export async function getFormModel(slug, state, options = {}) {
16
16
  const isPreview = isPreviewState(state, options);
17
17
  const formState = resolveState(state);
18
18
  const metadata = await formsService.getFormMetadata(slug);
19
- assertFormAvailable(metadata, formState, isPreview);
20
19
  const definition = await formsService.getFormDefinition(metadata.id, formState);
20
+ assertFormAvailable(metadata, definition, formState, isPreview);
21
21
  if (!definition) {
22
22
  throw Boom.notFound(`No definition found for form metadata ${metadata.id} (${slug}) ${state}`);
23
23
  }
@@ -66,7 +66,6 @@ export async function resolveFormModel(server, slug, state, options = {}) {
66
66
  const metadata = await formsService.getFormMetadata(slug);
67
67
  const formState = resolveState(state);
68
68
  const isPreview = options.isPreview ?? isPreviewState(state, options);
69
- assertFormAvailable(metadata, formState, isPreview);
70
69
  const stateMetadata = metadata[formState];
71
70
  if (!stateMetadata) {
72
71
  throw Boom.notFound(`No '${formState}' state for form metadata ${metadata.id}`);
@@ -95,6 +94,7 @@ export async function resolveFormModel(server, slug, state, options = {}) {
95
94
  };
96
95
  cache.set(cacheKey, entry);
97
96
  }
97
+ assertFormAvailable(metadata, entry.model.def, formState, isPreview);
98
98
  return entry.model;
99
99
  }
100
100
  function buildBasePath(routePrefix, slug, state, isPreview) {