@defra/forms-engine-plugin 5.0.0-alpha.2 → 5.0.0-alpha.20

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 (339) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.LICENSE.txt +7 -0
  3. package/.public/javascripts/application.min.js.map +1 -1
  4. package/.public/javascripts/shared.min.js +1 -1
  5. package/.public/javascripts/shared.min.js.LICENSE.txt +7 -0
  6. package/.public/javascripts/shared.min.js.map +1 -1
  7. package/.public/stylesheets/application.min.css.map +1 -1
  8. package/.server/client/javascripts/file-upload.js +6 -3
  9. package/.server/client/javascripts/file-upload.js.map +1 -1
  10. package/.server/client/javascripts/geospatial-map.d.ts +153 -5
  11. package/.server/client/javascripts/geospatial-map.js +157 -77
  12. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  13. package/.server/client/javascripts/govuk.js +2 -1
  14. package/.server/client/javascripts/govuk.js.map +1 -1
  15. package/.server/client/javascripts/location-map.d.ts +44 -0
  16. package/.server/client/javascripts/location-map.js +54 -5
  17. package/.server/client/javascripts/location-map.js.map +1 -1
  18. package/.server/client/javascripts/map-constants.d.ts +3 -0
  19. package/.server/client/javascripts/map-constants.js +4 -0
  20. package/.server/client/javascripts/map-constants.js.map +1 -0
  21. package/.server/client/javascripts/map.d.ts +10 -0
  22. package/.server/client/javascripts/map.js +42 -0
  23. package/.server/client/javascripts/map.js.map +1 -1
  24. package/.server/client/javascripts/maps.d.ts +5 -0
  25. package/.server/client/javascripts/maps.js +6 -0
  26. package/.server/client/javascripts/maps.js.map +1 -0
  27. package/.server/client/javascripts/shared.d.ts +1 -4
  28. package/.server/client/javascripts/shared.js +1 -4
  29. package/.server/client/javascripts/shared.js.map +1 -1
  30. package/.server/client/javascripts/sssi-dataset.d.ts +18 -20
  31. package/.server/client/javascripts/sssi-dataset.js +37 -39
  32. package/.server/client/javascripts/sssi-dataset.js.map +1 -1
  33. package/.server/client/stylesheets/maps.scss +4 -0
  34. package/.server/client/stylesheets/shared.scss +1 -5
  35. package/.server/server/constants.d.ts +2 -0
  36. package/.server/server/constants.js +2 -0
  37. package/.server/server/constants.js.map +1 -1
  38. package/.server/server/plugins/engine/__stubs__/definitions.d.ts +29 -0
  39. package/.server/server/plugins/engine/__stubs__/definitions.js +125 -0
  40. package/.server/server/plugins/engine/__stubs__/definitions.js.map +1 -0
  41. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +9 -3
  42. package/.server/server/plugins/engine/components/AutocompleteField.js +12 -0
  43. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  44. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +21 -0
  45. package/.server/server/plugins/engine/components/CheckboxesField.js +30 -5
  46. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  47. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -0
  48. package/.server/server/plugins/engine/components/ComponentBase.js +2 -0
  49. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  50. package/.server/server/plugins/engine/components/ComponentCollection.js +11 -6
  51. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  52. package/.server/server/plugins/engine/components/DatePartsField.d.ts +6 -3
  53. package/.server/server/plugins/engine/components/DatePartsField.js +12 -7
  54. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  55. package/.server/server/plugins/engine/components/DeclarationField.d.ts +5 -2
  56. package/.server/server/plugins/engine/components/DeclarationField.js +6 -4
  57. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  58. package/.server/server/plugins/engine/components/Details.d.ts +1 -0
  59. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +33 -2
  60. package/.server/server/plugins/engine/components/EastingNorthingField.js +110 -54
  61. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  62. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +4 -1
  63. package/.server/server/plugins/engine/components/EmailAddressField.js +2 -1
  64. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  65. package/.server/server/plugins/engine/components/FileUploadField.d.ts +21 -0
  66. package/.server/server/plugins/engine/components/FileUploadField.js +35 -3
  67. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  68. package/.server/server/plugins/engine/components/FormComponent.d.ts +5 -1
  69. package/.server/server/plugins/engine/components/FormComponent.js +5 -2
  70. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  71. package/.server/server/plugins/engine/components/GeospatialField.d.ts +23 -0
  72. package/.server/server/plugins/engine/components/GeospatialField.js +33 -9
  73. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  74. package/.server/server/plugins/engine/components/Html.d.ts +1 -0
  75. package/.server/server/plugins/engine/components/InsetText.d.ts +1 -0
  76. package/.server/server/plugins/engine/components/LatLongField.d.ts +32 -2
  77. package/.server/server/plugins/engine/components/LatLongField.js +85 -48
  78. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  79. package/.server/server/plugins/engine/components/List.d.ts +1 -0
  80. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +1 -0
  81. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +8 -2
  82. package/.server/server/plugins/engine/components/LocationFieldBase.js +25 -6
  83. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  84. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +12 -3
  85. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +21 -13
  86. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  87. package/.server/server/plugins/engine/components/Markdown.d.ts +1 -0
  88. package/.server/server/plugins/engine/components/Markdown.js +1 -1
  89. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  90. package/.server/server/plugins/engine/components/MonthYearField.d.ts +5 -2
  91. package/.server/server/plugins/engine/components/MonthYearField.js +13 -9
  92. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  93. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +1 -0
  94. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
  95. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +4 -3
  96. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  97. package/.server/server/plugins/engine/components/NotificationBanner.d.ts +1 -0
  98. package/.server/server/plugins/engine/components/NumberField.d.ts +5 -0
  99. package/.server/server/plugins/engine/components/NumberField.js +16 -0
  100. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  101. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +103 -1
  102. package/.server/server/plugins/engine/components/OsGridRefField.js +13 -3
  103. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  104. package/.server/server/plugins/engine/components/PaymentField.d.ts +3 -0
  105. package/.server/server/plugins/engine/components/PaymentField.js +17 -6
  106. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  107. package/.server/server/plugins/engine/components/SelectField.d.ts +1 -0
  108. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +1 -0
  109. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +1 -0
  110. package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -0
  111. package/.server/server/plugins/engine/components/UkAddressField.js +23 -8
  112. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  113. package/.server/server/plugins/engine/components/YesNoField.d.ts +5 -2
  114. package/.server/server/plugins/engine/components/YesNoField.js +17 -8
  115. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  116. package/.server/server/plugins/engine/components/helpers/components.js +3 -3
  117. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  118. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +8 -1
  119. package/.server/server/plugins/engine/components/helpers/geospatial.js +56 -15
  120. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  121. package/.server/server/plugins/engine/components/helpers/index.d.ts +7 -0
  122. package/.server/server/plugins/engine/components/helpers/index.js +13 -1
  123. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  124. package/.server/server/plugins/engine/components/types.d.ts +1 -0
  125. package/.server/server/plugins/engine/components/types.js.map +1 -1
  126. package/.server/server/plugins/engine/errors.d.ts +49 -0
  127. package/.server/server/plugins/engine/errors.js +75 -0
  128. package/.server/server/plugins/engine/errors.js.map +1 -0
  129. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  130. package/.server/server/plugins/engine/form-availability.js +3 -2
  131. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  132. package/.server/server/plugins/engine/form-context.js +2 -2
  133. package/.server/server/plugins/engine/form-context.js.map +1 -1
  134. package/.server/server/plugins/engine/helpers.d.ts +22 -2
  135. package/.server/server/plugins/engine/helpers.js +44 -5
  136. package/.server/server/plugins/engine/helpers.js.map +1 -1
  137. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +4 -1
  138. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +31 -7
  139. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  140. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +4 -4
  141. package/.server/server/plugins/engine/i18n/createFormTranslator.js +33 -4
  142. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
  143. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +1 -1
  144. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +9 -0
  145. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -1
  146. package/.server/server/plugins/engine/i18n/form.d.ts +13 -0
  147. package/.server/server/plugins/engine/i18n/form.js +35 -0
  148. package/.server/server/plugins/engine/i18n/form.js.map +1 -0
  149. package/.server/server/plugins/engine/i18n/languages.d.ts +10 -0
  150. package/.server/server/plugins/engine/i18n/languages.js +19 -0
  151. package/.server/server/plugins/engine/i18n/languages.js.map +1 -0
  152. package/.server/server/plugins/engine/i18n/translations/cy.json +192 -9
  153. package/.server/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  154. package/.server/server/plugins/engine/i18n/types.d.ts +3 -0
  155. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  156. package/.server/server/plugins/engine/index.d.ts +4 -0
  157. package/.server/server/plugins/engine/index.js +4 -0
  158. package/.server/server/plugins/engine/index.js.map +1 -1
  159. package/.server/server/plugins/engine/models/FormModel.d.ts +0 -4
  160. package/.server/server/plugins/engine/models/FormModel.js +19 -17
  161. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  162. package/.server/server/plugins/engine/models/SummaryViewModel.js +9 -4
  163. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  164. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  165. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  166. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  167. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +3 -5
  168. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  169. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  170. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  171. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
  172. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
  173. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  174. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +7 -1
  175. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +56 -10
  176. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  177. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  178. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -3
  179. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  180. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +3 -4
  181. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  182. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +10 -4
  183. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  184. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  185. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  186. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +1 -1
  187. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +3 -3
  188. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  189. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  190. package/.server/server/plugins/engine/pageControllers/validationOptions.js +24 -3
  191. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  192. package/.server/server/plugins/engine/types/schema.js +1 -0
  193. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  194. package/.server/server/plugins/engine/types.d.ts +5 -0
  195. package/.server/server/plugins/engine/types.js.map +1 -1
  196. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  197. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  198. package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
  199. package/.server/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  200. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  201. package/.server/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  202. package/.server/server/plugins/engine/views/components/osgridreffield.html +4 -1
  203. package/.server/server/plugins/engine/views/components/paymentfield.html +1 -1
  204. package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  205. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  206. package/.server/server/plugins/engine/views/partials/preview-banner.html +3 -1
  207. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  208. package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
  209. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  210. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
  211. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
  212. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
  213. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  214. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  215. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  216. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  217. package/.server/server/plugins/payment/helper.d.ts +11 -1
  218. package/.server/server/plugins/payment/helper.js +18 -2
  219. package/.server/server/plugins/payment/helper.js.map +1 -1
  220. package/.server/server/plugins/payment/helper.test.js +2 -1
  221. package/.server/server/plugins/payment/helper.test.js.map +1 -1
  222. package/.server/server/plugins/payment/service.d.ts +2 -1
  223. package/.server/server/plugins/payment/service.js +5 -2
  224. package/.server/server/plugins/payment/service.js.map +1 -1
  225. package/.server/server/plugins/payment/service.test.js +7 -3
  226. package/.server/server/plugins/payment/service.test.js.map +1 -1
  227. package/.server/server/plugins/payment/types.d.ts +4 -0
  228. package/.server/server/plugins/payment/types.js +1 -0
  229. package/.server/server/plugins/payment/types.js.map +1 -1
  230. package/.server/server/plugins/postcode-lookup/models/index.d.ts +249 -3
  231. package/.server/server/plugins/postcode-lookup/models/index.js +87 -33
  232. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  233. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
  234. package/.server/server/plugins/postcode-lookup/routes/index.js +47 -15
  235. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  236. package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
  237. package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
  238. package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
  239. package/.server/server/plugins/postcode-lookup/types.js +6 -1
  240. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  241. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  242. package/.server/server/utils/type-utils.d.ts +1 -2
  243. package/.server/server/utils/type-utils.js +0 -3
  244. package/.server/server/utils/type-utils.js.map +1 -1
  245. package/.server/server/utils/utils.d.ts +6 -0
  246. package/.server/server/utils/utils.js +18 -1
  247. package/.server/server/utils/utils.js.map +1 -1
  248. package/.server/server/utils/utils.test.js +21 -1
  249. package/.server/server/utils/utils.test.js.map +1 -1
  250. package/package.json +20 -5
  251. package/src/client/javascripts/file-upload.js +11 -3
  252. package/src/client/javascripts/geospatial-map.js +176 -81
  253. package/src/client/javascripts/govuk.js +2 -0
  254. package/src/client/javascripts/location-map.js +60 -4
  255. package/src/client/javascripts/map-constants.js +3 -0
  256. package/src/client/javascripts/map.js +47 -0
  257. package/src/client/javascripts/maps.js +5 -0
  258. package/src/client/javascripts/shared.js +1 -4
  259. package/src/client/javascripts/sssi-dataset.js +39 -41
  260. package/src/client/stylesheets/maps.scss +4 -0
  261. package/src/client/stylesheets/shared.scss +1 -5
  262. package/src/server/constants.js +2 -0
  263. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  264. package/src/server/plugins/engine/components/AutocompleteField.ts +13 -0
  265. package/src/server/plugins/engine/components/CheckboxesField.ts +38 -5
  266. package/src/server/plugins/engine/components/ComponentBase.ts +2 -0
  267. package/src/server/plugins/engine/components/ComponentCollection.ts +16 -11
  268. package/src/server/plugins/engine/components/DatePartsField.ts +20 -9
  269. package/src/server/plugins/engine/components/DeclarationField.ts +7 -6
  270. package/src/server/plugins/engine/components/EastingNorthingField.ts +185 -89
  271. package/src/server/plugins/engine/components/EmailAddressField.ts +2 -1
  272. package/src/server/plugins/engine/components/FileUploadField.ts +44 -3
  273. package/src/server/plugins/engine/components/FormComponent.ts +6 -5
  274. package/src/server/plugins/engine/components/GeospatialField.ts +47 -9
  275. package/src/server/plugins/engine/components/LatLongField.ts +128 -58
  276. package/src/server/plugins/engine/components/LocationFieldBase.ts +30 -7
  277. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +28 -14
  278. package/src/server/plugins/engine/components/Markdown.ts +2 -3
  279. package/src/server/plugins/engine/components/MonthYearField.ts +20 -15
  280. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -3
  281. package/src/server/plugins/engine/components/NumberField.ts +21 -1
  282. package/src/server/plugins/engine/components/OsGridRefField.ts +22 -3
  283. package/src/server/plugins/engine/components/PaymentField.ts +15 -5
  284. package/src/server/plugins/engine/components/UkAddressField.ts +30 -7
  285. package/src/server/plugins/engine/components/YesNoField.ts +17 -9
  286. package/src/server/plugins/engine/components/helpers/components.ts +3 -3
  287. package/src/server/plugins/engine/components/helpers/geospatial.ts +84 -21
  288. package/src/server/plugins/engine/components/helpers/index.ts +19 -0
  289. package/src/server/plugins/engine/components/types.ts +1 -0
  290. package/src/server/plugins/engine/errors.ts +78 -0
  291. package/src/server/plugins/engine/form-availability.ts +8 -2
  292. package/src/server/plugins/engine/form-context.ts +4 -2
  293. package/src/server/plugins/engine/helpers.ts +52 -5
  294. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +16 -8
  295. package/src/server/plugins/engine/i18n/createFormTranslator.ts +62 -11
  296. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
  297. package/src/server/plugins/engine/i18n/form.ts +54 -0
  298. package/src/server/plugins/engine/i18n/languages.ts +19 -0
  299. package/src/server/plugins/engine/i18n/translations/cy.json +192 -9
  300. package/src/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  301. package/src/server/plugins/engine/i18n/types.ts +3 -0
  302. package/src/server/plugins/engine/index.ts +7 -0
  303. package/src/server/plugins/engine/models/FormModel.ts +21 -17
  304. package/src/server/plugins/engine/models/SummaryViewModel.ts +6 -5
  305. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  306. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +3 -6
  307. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
  308. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
  309. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +60 -9
  310. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +10 -5
  311. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +2 -2
  312. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
  313. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  314. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +6 -3
  315. package/src/server/plugins/engine/pageControllers/validationOptions.ts +29 -4
  316. package/src/server/plugins/engine/types/schema.ts +1 -0
  317. package/src/server/plugins/engine/types.ts +5 -0
  318. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  319. package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
  320. package/src/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  321. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  322. package/src/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  323. package/src/server/plugins/engine/views/components/osgridreffield.html +4 -1
  324. package/src/server/plugins/engine/views/components/paymentfield.html +1 -1
  325. package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  326. package/src/server/plugins/engine/views/file-upload.html +1 -1
  327. package/src/server/plugins/engine/views/partials/preview-banner.html +3 -1
  328. package/src/server/plugins/engine/views/unavailable.html +14 -11
  329. package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  330. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  331. package/src/server/plugins/payment/helper.js +22 -2
  332. package/src/server/plugins/payment/service.js +6 -2
  333. package/src/server/plugins/payment/types.js +1 -0
  334. package/src/server/plugins/postcode-lookup/models/index.js +75 -43
  335. package/src/server/plugins/postcode-lookup/routes/index.js +73 -16
  336. package/src/server/plugins/postcode-lookup/types.js +6 -1
  337. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  338. package/src/server/utils/type-utils.ts +1 -11
  339. package/src/server/utils/utils.js +19 -1
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","names":["ComponentType","config","ListFormComponent","escapeMarkdown","Components","markdown","designerUrl","get","hasListFormField","field","type","undefined","isListFieldType","allowedTypes","AutocompleteField","CheckboxesField","RadiosField","SelectField","YesNoField","includes","createComponent","def","options","component","DatePartsField","DeclarationField","Details","EmailAddressField","Html","InsetText","List","Markdown","NotificationBanner","MultilineTextField","NumberField","TelephoneNumberField","TextField","UkAddressField","MonthYearField","FileUploadField","EastingNorthingField","OsGridRefField","NationalGridFieldNumberField","LatLongField","HiddenField","PaymentField","GeospatialField","Error","getAnswer","state","translator","format","getAnswerMarkdown","context","getContextValueFromState","toString","parse","async","trim","getDisplayStringFromState","answer","answerEscaped","files","getFormValueFromState","length","map","status","file","form","filename","fileId","join","values","flat","items","filter","value","item","resolvedText","tListItem","text","label","line","toLowerCase","split","concat","contextValue","features","getDisplayStringFromFormValue"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/components.ts"],"sourcesContent":["import { ComponentType, type ComponentDef } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormState } from '~/src/server/plugins/engine/types.js'\n\n// All component instances\nexport type Component = InstanceType<\n (typeof Components)[keyof typeof Components]\n>\n\n// Field component instances only\nexport type Field = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.RadiosField\n | typeof Components.YesNoField\n | typeof Components.CheckboxesField\n | typeof Components.DatePartsField\n | typeof Components.DeclarationField\n | typeof Components.EastingNorthingField\n | typeof Components.EmailAddressField\n | typeof Components.LatLongField\n | typeof Components.MonthYearField\n | typeof Components.MultilineTextField\n | typeof Components.NationalGridFieldNumberField\n | typeof Components.NumberField\n | typeof Components.OsGridRefField\n | typeof Components.SelectField\n | typeof Components.TelephoneNumberField\n | typeof Components.TextField\n | typeof Components.UkAddressField\n | typeof Components.FileUploadField\n | typeof Components.HiddenField\n | typeof Components.PaymentField\n | typeof Components.GeospatialField\n>\n\n// Guidance component instances only\nexport type Guidance =\n | InstanceType<typeof Components.Details>\n | InstanceType<typeof Components.Html>\n | InstanceType<typeof Components.Markdown>\n | InstanceType<typeof Components.InsetText>\n | InstanceType<typeof Components.List>\n | InstanceType<typeof Components.NotificationBanner>\n\n// List component instances only\nexport type ListField = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.CheckboxesField\n | typeof Components.RadiosField\n | typeof Components.SelectField\n | typeof Components.YesNoField\n>\n\nexport const designerUrl = config.get('designerUrl')\n\n// Re-export markdown from its own module to avoid circular dependencies\nexport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\n\n/**\n * Filter known components with lists\n */\nexport function hasListFormField(\n field?: Partial<Component>\n): field is ListFormComponent {\n return !!field && field.type !== undefined && isListFieldType(field.type)\n}\n\nexport function isListFieldType(\n type?: ComponentType\n): type is ListField['type'] {\n const allowedTypes = [\n ComponentType.AutocompleteField,\n ComponentType.CheckboxesField,\n ComponentType.RadiosField,\n ComponentType.SelectField,\n ComponentType.YesNoField\n ]\n\n return !!type && allowedTypes.includes(type)\n}\n\n/**\n * Create field instance for each {@link ComponentDef} type\n */\nexport function createComponent(\n def: ComponentDef,\n options: ConstructorParameters<typeof ComponentBase>[1]\n): Component {\n let component: Component | undefined\n\n switch (def.type) {\n case ComponentType.AutocompleteField:\n component = new Components.AutocompleteField(def, options)\n break\n\n case ComponentType.CheckboxesField:\n component = new Components.CheckboxesField(def, options)\n break\n\n case ComponentType.DatePartsField:\n component = new Components.DatePartsField(def, options)\n break\n\n case ComponentType.DeclarationField:\n component = new Components.DeclarationField(def, options)\n break\n\n case ComponentType.Details:\n component = new Components.Details(def, options)\n break\n\n case ComponentType.EmailAddressField:\n component = new Components.EmailAddressField(def, options)\n break\n\n case ComponentType.Html:\n component = new Components.Html(def, options)\n break\n\n case ComponentType.InsetText:\n component = new Components.InsetText(def, options)\n break\n\n case ComponentType.List:\n component = new Components.List(def, options)\n break\n\n case ComponentType.Markdown:\n component = new Components.Markdown(def, options)\n break\n\n case ComponentType.NotificationBanner:\n component = new Components.NotificationBanner(def, options)\n break\n\n case ComponentType.MultilineTextField:\n component = new Components.MultilineTextField(def, options)\n break\n\n case ComponentType.NumberField:\n component = new Components.NumberField(def, options)\n break\n\n case ComponentType.RadiosField:\n component = new Components.RadiosField(def, options)\n break\n\n case ComponentType.SelectField:\n component = new Components.SelectField(def, options)\n break\n\n case ComponentType.TelephoneNumberField:\n component = new Components.TelephoneNumberField(def, options)\n break\n\n case ComponentType.TextField:\n component = new Components.TextField(def, options)\n break\n\n case ComponentType.UkAddressField:\n component = new Components.UkAddressField(def, options)\n break\n\n case ComponentType.YesNoField:\n component = new Components.YesNoField(def, options)\n break\n\n case ComponentType.MonthYearField:\n component = new Components.MonthYearField(def, options)\n break\n\n case ComponentType.FileUploadField:\n component = new Components.FileUploadField(def, options)\n break\n\n case ComponentType.EastingNorthingField:\n component = new Components.EastingNorthingField(def, options)\n break\n\n case ComponentType.OsGridRefField:\n component = new Components.OsGridRefField(def, options)\n break\n\n case ComponentType.NationalGridFieldNumberField:\n component = new Components.NationalGridFieldNumberField(def, options)\n break\n\n case ComponentType.LatLongField:\n component = new Components.LatLongField(def, options)\n break\n\n case ComponentType.HiddenField:\n component = new Components.HiddenField(def, options)\n break\n\n case ComponentType.PaymentField:\n component = new Components.PaymentField(def, options)\n break\n\n case ComponentType.GeospatialField:\n component = new Components.GeospatialField(def, options)\n break\n }\n\n if (typeof component === 'undefined') {\n throw new Error(`Component type ${def.type} does not exist`)\n }\n\n return component\n}\n\n/**\n * Get formatted answer for a field\n */\nexport function getAnswer(\n field: Field,\n state: FormState,\n translator: Translator,\n options: {\n format:\n | 'data' // Submission data\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n // Use escaped display text for GOV.UK Notify emails\n if (options.format === 'email') {\n return getAnswerMarkdown(field, state, translator, { format: 'email' })\n }\n\n // Use context value for submission data\n if (options.format === 'data') {\n const context = field.getContextValueFromState(state)\n return context?.toString() ?? ''\n }\n\n // Use display HTML for check answers summary (multi line)\n if (\n field instanceof ListFormComponent ||\n field instanceof Components.MultilineTextField ||\n field instanceof Components.UkAddressField ||\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n return markdown\n .parse(\n getAnswerMarkdown(field, state, translator, { format: 'summary' }),\n { async: false }\n )\n .trim()\n }\n\n // Use display text for check answers summary (single line)\n return field.getDisplayStringFromState(state, translator)\n}\n\n/**\n * Get formatted answer for a field (Markdown only)\n */\nexport function getAnswerMarkdown(\n field: Field,\n state: FormState,\n translator: Translator,\n options: {\n format:\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n const answer = field.getDisplayStringFromState(state, translator)\n\n // Use escaped display text\n let answerEscaped = `${escapeMarkdown(answer)}\\n`\n\n if (field instanceof Components.FileUploadField) {\n const files = field.getFormValueFromState(state)\n\n // Skip empty files\n if (!files?.length) {\n return answerEscaped\n }\n\n answerEscaped = `${escapeMarkdown(answer)}:\\n\\n`\n\n // Append bullet points\n answerEscaped += files\n .map(({ status }) => {\n const { file } = status.form\n const filename = escapeMarkdown(file.filename)\n return `* [${filename}](${designerUrl}/file-download/${file.fileId})\\n`\n })\n .join('')\n } else if (field instanceof ListFormComponent) {\n const values = [field.getContextValueFromState(state)].flat()\n const items = field.items.filter(({ value }) => values.includes(value))\n\n // Skip empty values\n if (!items.length) {\n return answerEscaped\n }\n\n answerEscaped = ''\n\n // Append bullet points\n answerEscaped += items\n .map((item) => {\n const resolvedText = translator.tListItem(item, 'text') || item.text\n const label = escapeMarkdown(resolvedText)\n const value = escapeMarkdown(`(${item.value})`)\n\n let line = label\n\n // Prepend bullet points for checkboxes only\n if (field instanceof Components.CheckboxesField) {\n line = `* ${line}`\n }\n\n // Append raw values in parentheses\n // e.g. `* None of the above (false)`\n return options.format === 'email' &&\n `${item.value}`.toLowerCase() !== item.text.toLowerCase()\n ? `${line} ${value}\\n`\n : `${line}\\n`\n })\n .join('')\n } else if (field instanceof Components.MultilineTextField) {\n // Preserve Multiline text new lines\n answerEscaped = answer\n .split(/(?:\\r?\\n)+/)\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (field instanceof Components.UkAddressField) {\n // Format UK addresses into new lines\n answerEscaped = (field.getContextValueFromState(state) ?? [])\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n const contextValue = field.getContextValueFromState(state)\n answerEscaped = contextValue ? `${contextValue}\\n` : ''\n } else if (field instanceof Components.GeospatialField) {\n const features = field.getFormValueFromState(state)\n const value = field.getDisplayStringFromFormValue(features, translator)\n\n answerEscaped = value ? `${value}\\n` : ''\n }\n\n return answerEscaped\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"components.js","names":["ComponentType","config","ListFormComponent","escapeMarkdown","Components","markdown","UnknownComponentTypeError","designerUrl","get","hasListFormField","field","type","undefined","isListFieldType","allowedTypes","AutocompleteField","CheckboxesField","RadiosField","SelectField","YesNoField","includes","createComponent","def","options","component","DatePartsField","DeclarationField","Details","EmailAddressField","Html","InsetText","List","Markdown","NotificationBanner","MultilineTextField","NumberField","TelephoneNumberField","TextField","UkAddressField","MonthYearField","FileUploadField","EastingNorthingField","OsGridRefField","NationalGridFieldNumberField","LatLongField","HiddenField","PaymentField","GeospatialField","getAnswer","state","translator","format","getAnswerMarkdown","context","getContextValueFromState","toString","parse","async","trim","getDisplayStringFromState","answer","answerEscaped","files","getFormValueFromState","length","map","status","file","form","filename","fileId","join","values","flat","items","filter","value","item","resolvedText","tListItem","text","label","line","toLowerCase","split","concat","features","getDisplayStringFromFormValue"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/components.ts"],"sourcesContent":["import { ComponentType, type ComponentDef } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\nimport { UnknownComponentTypeError } from '~/src/server/plugins/engine/errors.js'\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport { type FormState } from '~/src/server/plugins/engine/types.js'\n\n// All component instances\nexport type Component = InstanceType<\n (typeof Components)[keyof typeof Components]\n>\n\n// Field component instances only\nexport type Field = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.RadiosField\n | typeof Components.YesNoField\n | typeof Components.CheckboxesField\n | typeof Components.DatePartsField\n | typeof Components.DeclarationField\n | typeof Components.EastingNorthingField\n | typeof Components.EmailAddressField\n | typeof Components.LatLongField\n | typeof Components.MonthYearField\n | typeof Components.MultilineTextField\n | typeof Components.NationalGridFieldNumberField\n | typeof Components.NumberField\n | typeof Components.OsGridRefField\n | typeof Components.SelectField\n | typeof Components.TelephoneNumberField\n | typeof Components.TextField\n | typeof Components.UkAddressField\n | typeof Components.FileUploadField\n | typeof Components.HiddenField\n | typeof Components.PaymentField\n | typeof Components.GeospatialField\n>\n\n// Guidance component instances only\nexport type Guidance =\n | InstanceType<typeof Components.Details>\n | InstanceType<typeof Components.Html>\n | InstanceType<typeof Components.Markdown>\n | InstanceType<typeof Components.InsetText>\n | InstanceType<typeof Components.List>\n | InstanceType<typeof Components.NotificationBanner>\n\n// List component instances only\nexport type ListField = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.CheckboxesField\n | typeof Components.RadiosField\n | typeof Components.SelectField\n | typeof Components.YesNoField\n>\n\nexport const designerUrl = config.get('designerUrl')\n\n// Re-export markdown from its own module to avoid circular dependencies\nexport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\n\n/**\n * Filter known components with lists\n */\nexport function hasListFormField(\n field?: Partial<Component>\n): field is ListFormComponent {\n return !!field && field.type !== undefined && isListFieldType(field.type)\n}\n\nexport function isListFieldType(\n type?: ComponentType\n): type is ListField['type'] {\n const allowedTypes = [\n ComponentType.AutocompleteField,\n ComponentType.CheckboxesField,\n ComponentType.RadiosField,\n ComponentType.SelectField,\n ComponentType.YesNoField\n ]\n\n return !!type && allowedTypes.includes(type)\n}\n\n/**\n * Create field instance for each {@link ComponentDef} type\n */\nexport function createComponent(\n def: ComponentDef,\n options: ConstructorParameters<typeof ComponentBase>[1]\n): Component {\n let component: Component | undefined\n\n switch (def.type) {\n case ComponentType.AutocompleteField:\n component = new Components.AutocompleteField(def, options)\n break\n\n case ComponentType.CheckboxesField:\n component = new Components.CheckboxesField(def, options)\n break\n\n case ComponentType.DatePartsField:\n component = new Components.DatePartsField(def, options)\n break\n\n case ComponentType.DeclarationField:\n component = new Components.DeclarationField(def, options)\n break\n\n case ComponentType.Details:\n component = new Components.Details(def, options)\n break\n\n case ComponentType.EmailAddressField:\n component = new Components.EmailAddressField(def, options)\n break\n\n case ComponentType.Html:\n component = new Components.Html(def, options)\n break\n\n case ComponentType.InsetText:\n component = new Components.InsetText(def, options)\n break\n\n case ComponentType.List:\n component = new Components.List(def, options)\n break\n\n case ComponentType.Markdown:\n component = new Components.Markdown(def, options)\n break\n\n case ComponentType.NotificationBanner:\n component = new Components.NotificationBanner(def, options)\n break\n\n case ComponentType.MultilineTextField:\n component = new Components.MultilineTextField(def, options)\n break\n\n case ComponentType.NumberField:\n component = new Components.NumberField(def, options)\n break\n\n case ComponentType.RadiosField:\n component = new Components.RadiosField(def, options)\n break\n\n case ComponentType.SelectField:\n component = new Components.SelectField(def, options)\n break\n\n case ComponentType.TelephoneNumberField:\n component = new Components.TelephoneNumberField(def, options)\n break\n\n case ComponentType.TextField:\n component = new Components.TextField(def, options)\n break\n\n case ComponentType.UkAddressField:\n component = new Components.UkAddressField(def, options)\n break\n\n case ComponentType.YesNoField:\n component = new Components.YesNoField(def, options)\n break\n\n case ComponentType.MonthYearField:\n component = new Components.MonthYearField(def, options)\n break\n\n case ComponentType.FileUploadField:\n component = new Components.FileUploadField(def, options)\n break\n\n case ComponentType.EastingNorthingField:\n component = new Components.EastingNorthingField(def, options)\n break\n\n case ComponentType.OsGridRefField:\n component = new Components.OsGridRefField(def, options)\n break\n\n case ComponentType.NationalGridFieldNumberField:\n component = new Components.NationalGridFieldNumberField(def, options)\n break\n\n case ComponentType.LatLongField:\n component = new Components.LatLongField(def, options)\n break\n\n case ComponentType.HiddenField:\n component = new Components.HiddenField(def, options)\n break\n\n case ComponentType.PaymentField:\n component = new Components.PaymentField(def, options)\n break\n\n case ComponentType.GeospatialField:\n component = new Components.GeospatialField(def, options)\n break\n }\n\n if (typeof component === 'undefined') {\n throw new UnknownComponentTypeError(def.type)\n }\n\n return component\n}\n\n/**\n * Get formatted answer for a field\n */\nexport function getAnswer(\n field: Field,\n state: FormState,\n translator: Translator,\n options: {\n format:\n | 'data' // Submission data\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n // Use escaped display text for GOV.UK Notify emails\n if (options.format === 'email') {\n return getAnswerMarkdown(field, state, translator, { format: 'email' })\n }\n\n // Use context value for submission data\n if (options.format === 'data') {\n const context = field.getContextValueFromState(state)\n return context?.toString() ?? ''\n }\n\n // Use display HTML for check answers summary (multi line)\n if (\n field instanceof ListFormComponent ||\n field instanceof Components.MultilineTextField ||\n field instanceof Components.UkAddressField ||\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n return markdown\n .parse(\n getAnswerMarkdown(field, state, translator, { format: 'summary' }),\n { async: false }\n )\n .trim()\n }\n\n // Use display text for check answers summary (single line)\n return field.getDisplayStringFromState(state, translator)\n}\n\n/**\n * Get formatted answer for a field (Markdown only)\n */\nexport function getAnswerMarkdown(\n field: Field,\n state: FormState,\n translator: Translator,\n options: {\n format:\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n const answer = field.getDisplayStringFromState(state, translator)\n\n // Use escaped display text\n let answerEscaped = `${escapeMarkdown(answer)}\\n`\n\n if (field instanceof Components.FileUploadField) {\n const files = field.getFormValueFromState(state)\n\n // Skip empty files\n if (!files?.length) {\n return answerEscaped\n }\n\n answerEscaped = `${escapeMarkdown(answer)}:\\n\\n`\n\n // Append bullet points\n answerEscaped += files\n .map(({ status }) => {\n const { file } = status.form\n const filename = escapeMarkdown(file.filename)\n return `* [${filename}](${designerUrl}/file-download/${file.fileId})\\n`\n })\n .join('')\n } else if (field instanceof ListFormComponent) {\n const values = [field.getContextValueFromState(state)].flat()\n const items = field.items.filter(({ value }) => values.includes(value))\n\n // Skip empty values\n if (!items.length) {\n return answerEscaped\n }\n\n answerEscaped = ''\n\n // Append bullet points\n answerEscaped += items\n .map((item) => {\n const resolvedText = translator.tListItem(item, 'text') || item.text\n const label = escapeMarkdown(resolvedText)\n const value = escapeMarkdown(`(${item.value})`)\n\n let line = label\n\n // Prepend bullet points for checkboxes only\n if (field instanceof Components.CheckboxesField) {\n line = `* ${line}`\n }\n\n // Append raw values in parentheses\n // e.g. `* None of the above (false)`\n return options.format === 'email' &&\n `${item.value}`.toLowerCase() !== item.text.toLowerCase()\n ? `${line} ${value}\\n`\n : `${line}\\n`\n })\n .join('')\n } else if (field instanceof Components.MultilineTextField) {\n // Preserve Multiline text new lines\n answerEscaped = answer\n .split(/(?:\\r?\\n)+/)\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (field instanceof Components.UkAddressField) {\n // Format UK addresses into new lines\n answerEscaped = (field.getContextValueFromState(state) ?? [])\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n answerEscaped = answer ? `${answer}\\n` : ''\n } else if (field instanceof Components.GeospatialField) {\n const features = field.getFormValueFromState(state)\n const value = field.getDisplayStringFromFormValue(features, translator)\n\n answerEscaped = value ? `${value}\\n` : ''\n }\n\n return answerEscaped\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,4 +1,11 @@
1
- import { type GeospatialFieldComponent } from '@defra/forms-model';
1
+ import { type GeospatialFieldComponent, type GeospatialFieldOptionsCountry } from '@defra/forms-model';
2
2
  import JoiBase from 'joi';
3
+ import { type EastingNorthingField } from '../../../../../server/plugins/engine/components/EastingNorthingField.js';
3
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
+ };
4
10
  export declare function getGeospatialSchema(def: GeospatialFieldComponent): JoiBase.ArraySchema<Feature[]>;
11
+ export declare function getEastingNorthingCountryValidator(field: EastingNorthingField, country: GeospatialFieldOptionsCountry): JoiBase.CustomValidator;
@@ -1,14 +1,10 @@
1
- import { GeospatialFieldOptionsCountryEnum } from '@defra/forms-model';
2
1
  import Bourne from '@hapi/bourne';
3
2
  import { booleanWithin } from '@turf/boolean-within';
3
+ import { point } from '@turf/helpers';
4
+ // @ts-expect-error - no types
5
+ import OsGridRef from 'geodesy/osgridref.js';
4
6
  import JoiBase from 'joi';
5
7
  import { countries } from "../../../map/routes/index.js";
6
- const countriesDesc = {
7
- [GeospatialFieldOptionsCountryEnum.England]: 'England',
8
- [GeospatialFieldOptionsCountryEnum.NorthernIreland]: 'Northern Ireland',
9
- [GeospatialFieldOptionsCountryEnum.Scotland]: 'Scotland',
10
- [GeospatialFieldOptionsCountryEnum.Wales]: 'Wales'
11
- };
12
8
  const Joi = JoiBase.extend({
13
9
  type: 'array',
14
10
  base: JoiBase.array(),
@@ -73,21 +69,37 @@ const featureSchema = Joi.object().keys({
73
69
  properties: featurePropertiesSchema,
74
70
  geometry: featureGeometrySchema
75
71
  });
76
- function applySchemaConstraints(schema, def) {
72
+ export function determineLimits(def, isOptional) {
77
73
  const {
78
- options,
79
74
  schema: constraints
80
75
  } = def;
81
- const isOptional = options.required === false;
76
+ const limits = {};
82
77
  if (typeof constraints?.length === 'number') {
83
- schema = schema.length(constraints.length);
78
+ limits.length = constraints.length;
84
79
  } else {
85
80
  if (typeof constraints?.min === 'number') {
86
- schema = schema.min(constraints.min);
81
+ limits.min = constraints.min;
87
82
  } else if (!isOptional) {
88
- schema = schema.min(1);
83
+ limits.min = 1;
89
84
  }
90
- 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);
91
103
  }
92
104
  if (isOptional) {
93
105
  schema = schema.optional();
@@ -113,7 +125,7 @@ export function getGeospatialSchema(def) {
113
125
  const result = booleanWithin(value, countryFeature);
114
126
  if (!result) {
115
127
  return helpers.error('any.custom', {
116
- country: countriesDesc[country]
128
+ country
117
129
  });
118
130
  }
119
131
 
@@ -122,4 +134,33 @@ export function getGeospatialSchema(def) {
122
134
  };
123
135
  return applySchemaConstraints(Joi.array().items(featureSchema.custom(validateCountryBounds)).unique('id'), def);
124
136
  }
137
+ export function getEastingNorthingCountryValidator(field, country) {
138
+ const validateCountryBounds = (value, helpers) => {
139
+ const countryFeature = countries.features.find(feature => feature.id === country);
140
+ if (!countryFeature) {
141
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
142
+ return value;
143
+ }
144
+ const eastingNorthing = field.getFormValueFromState(value);
145
+ if (!eastingNorthing) {
146
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
147
+ return value;
148
+ }
149
+ const gridRef = new OsGridRef(eastingNorthing.easting, eastingNorthing.northing);
150
+ const latLong = gridRef.toLatLon();
151
+ const coord = point([latLong.longitude, latLong.latitude]);
152
+ const within = booleanWithin(coord, countryFeature);
153
+ if (!within) {
154
+ return helpers.error('any.custom', {
155
+ country
156
+ }, {
157
+ path: [field.name]
158
+ });
159
+ }
160
+
161
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
162
+ return value;
163
+ };
164
+ return validateCountryBounds;
165
+ }
125
166
  //# sourceMappingURL=geospatial.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"geospatial.js","names":["GeospatialFieldOptionsCountryEnum","Bourne","booleanWithin","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"],"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 JoiBase, { type CustomValidator } from 'joi'\n\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"],"mappings":"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","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,5 +1,6 @@
1
1
  import { type ComponentDef } from '@defra/forms-model';
2
2
  import { type JoiExpression, type ReferenceOptions } from 'joi';
3
+ import { type Translator } from '../../../../../server/plugins/engine/i18n/types.js';
3
4
  /**
4
5
  * Prevent Markdown formatting
5
6
  * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}
@@ -24,3 +25,9 @@ export declare const lowerFirstExpressionOptions: ReferenceOptions;
24
25
  * Used for error messages in location field components
25
26
  */
26
27
  export declare const createLowerFirstExpression: (template: string) => JoiExpression;
28
+ /**
29
+ * Translate a component label, in precedence order of errorDescription, then shortDesciption, then title
30
+ * @param {ComponentDef} component
31
+ * @param {Translator} translator
32
+ */
33
+ export declare function getTranslatedLabel(component: ComponentDef, translator: Translator): string;
@@ -1,6 +1,5 @@
1
1
  import joi from 'joi';
2
2
  import lowerFirst from 'lodash/lowerFirst.js';
3
-
4
3
  /**
5
4
  * Prevent Markdown formatting
6
5
  * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}
@@ -39,4 +38,17 @@ export const lowerFirstExpressionOptions = {
39
38
  * Used for error messages in location field components
40
39
  */
41
40
  export const createLowerFirstExpression = template => joi.expression(template, lowerFirstExpressionOptions);
41
+
42
+ /**
43
+ * Translate a component label, in precedence order of errorDescription, then shortDesciption, then title
44
+ * @param {ComponentDef} component
45
+ * @param {Translator} translator
46
+ */
47
+ export function getTranslatedLabel(component, translator) {
48
+ const {
49
+ tComponent
50
+ } = translator;
51
+ const translatedLabel = tComponent(component, 'errorDescription') || tComponent(component, 'shortDescription') || tComponent(component, 'title');
52
+ return translatedLabel;
53
+ }
42
54
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["joi","lowerFirst","escapeMarkdown","answer","punctuation","character","replaceAll","addClassOptionIfNone","options","className","classes","lowerFirstPreserveProperNouns","text","result","replace","lowerFirstExpressionOptions","functions","createLowerFirstExpression","template","expression"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/index.ts"],"sourcesContent":["import { type ComponentDef } from '@defra/forms-model'\nimport joi, { type JoiExpression, type ReferenceOptions } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\n/**\n * Prevent Markdown formatting\n * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}\n */\nexport function escapeMarkdown(answer: string) {\n const punctuation = [\n '`',\n \"'\",\n '*',\n '_',\n '{',\n '}',\n '[',\n ']',\n '(',\n ')',\n '#',\n '+',\n '-',\n '.',\n '!'\n ]\n\n for (const character of punctuation) {\n answer = answer.replaceAll(character, `\\\\${character}`)\n }\n\n return answer\n}\n\nexport const addClassOptionIfNone = (\n options: Extract<ComponentDef, { options: { classes?: string } }>['options'],\n className: string\n) => {\n options.classes ??= className\n}\n\n/**\n * Applies lowerFirst but preserves capitalisation of proper nouns\n * like \"National Grid\", \"Ordnance Survey\" and \"OS\".\n */\nexport function lowerFirstPreserveProperNouns(text: string): string {\n const result = lowerFirst(text)\n return result\n .replace(/\\bnational [Gg]rid\\b/g, 'National Grid')\n .replace(/\\bordnance [Ss]urvey\\b/g, 'Ordnance Survey')\n .replace(/\\b[oO][sS]\\b/g, 'OS')\n}\n\n/**\n * Configuration for Joi expressions that use lowerFirst function\n */\nexport const lowerFirstExpressionOptions = {\n functions: {\n lowerFirst: lowerFirstPreserveProperNouns\n }\n} as ReferenceOptions\n\n/**\n * Creates a Joi expression with lowerFirst function support\n * Used for error messages in location field components\n */\nexport const createLowerFirstExpression = (template: string): JoiExpression =>\n joi.expression(template, lowerFirstExpressionOptions) as JoiExpression\n"],"mappings":"AACA,OAAOA,GAAG,MAAqD,KAAK;AACpE,OAAOC,UAAU,MAAM,sBAAsB;;AAE7C;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,MAAc,EAAE;EAC7C,MAAMC,WAAW,GAAG,CAClB,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACJ;EAED,KAAK,MAAMC,SAAS,IAAID,WAAW,EAAE;IACnCD,MAAM,GAAGA,MAAM,CAACG,UAAU,CAACD,SAAS,EAAE,KAAKA,SAAS,EAAE,CAAC;EACzD;EAEA,OAAOF,MAAM;AACf;AAEA,OAAO,MAAMI,oBAAoB,GAAGA,CAClCC,OAA4E,EAC5EC,SAAiB,KACd;EACHD,OAAO,CAACE,OAAO,KAAKD,SAAS;AAC/B,CAAC;;AAED;AACA;AACA;AACA;AACA,OAAO,SAASE,6BAA6BA,CAACC,IAAY,EAAU;EAClE,MAAMC,MAAM,GAAGZ,UAAU,CAACW,IAAI,CAAC;EAC/B,OAAOC,MAAM,CACVC,OAAO,CAAC,uBAAuB,EAAE,eAAe,CAAC,CACjDA,OAAO,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CACrDA,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC;AACnC;;AAEA;AACA;AACA;AACA,OAAO,MAAMC,2BAA2B,GAAG;EACzCC,SAAS,EAAE;IACTf,UAAU,EAAEU;EACd;AACF,CAAqB;;AAErB;AACA;AACA;AACA;AACA,OAAO,MAAMM,0BAA0B,GAAIC,QAAgB,IACzDlB,GAAG,CAACmB,UAAU,CAACD,QAAQ,EAAEH,2BAA2B,CAAkB","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["joi","lowerFirst","escapeMarkdown","answer","punctuation","character","replaceAll","addClassOptionIfNone","options","className","classes","lowerFirstPreserveProperNouns","text","result","replace","lowerFirstExpressionOptions","functions","createLowerFirstExpression","template","expression","getTranslatedLabel","component","translator","tComponent","translatedLabel"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/index.ts"],"sourcesContent":["import { type ComponentDef } from '@defra/forms-model'\nimport joi, { type JoiExpression, type ReferenceOptions } from 'joi'\nimport lowerFirst from 'lodash/lowerFirst.js'\n\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\n\n/**\n * Prevent Markdown formatting\n * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}\n */\nexport function escapeMarkdown(answer: string) {\n const punctuation = [\n '`',\n \"'\",\n '*',\n '_',\n '{',\n '}',\n '[',\n ']',\n '(',\n ')',\n '#',\n '+',\n '-',\n '.',\n '!'\n ]\n\n for (const character of punctuation) {\n answer = answer.replaceAll(character, `\\\\${character}`)\n }\n\n return answer\n}\n\nexport const addClassOptionIfNone = (\n options: Extract<ComponentDef, { options: { classes?: string } }>['options'],\n className: string\n) => {\n options.classes ??= className\n}\n\n/**\n * Applies lowerFirst but preserves capitalisation of proper nouns\n * like \"National Grid\", \"Ordnance Survey\" and \"OS\".\n */\nexport function lowerFirstPreserveProperNouns(text: string): string {\n const result = lowerFirst(text)\n return result\n .replace(/\\bnational [Gg]rid\\b/g, 'National Grid')\n .replace(/\\bordnance [Ss]urvey\\b/g, 'Ordnance Survey')\n .replace(/\\b[oO][sS]\\b/g, 'OS')\n}\n\n/**\n * Configuration for Joi expressions that use lowerFirst function\n */\nexport const lowerFirstExpressionOptions = {\n functions: {\n lowerFirst: lowerFirstPreserveProperNouns\n }\n} as ReferenceOptions\n\n/**\n * Creates a Joi expression with lowerFirst function support\n * Used for error messages in location field components\n */\nexport const createLowerFirstExpression = (template: string): JoiExpression =>\n joi.expression(template, lowerFirstExpressionOptions) as JoiExpression\n\n/**\n * Translate a component label, in precedence order of errorDescription, then shortDesciption, then title\n * @param {ComponentDef} component\n * @param {Translator} translator\n */\nexport function getTranslatedLabel(\n component: ComponentDef,\n translator: Translator\n) {\n const { tComponent } = translator\n const translatedLabel =\n tComponent(component, 'errorDescription') ||\n tComponent(component, 'shortDescription') ||\n tComponent(component, 'title')\n return translatedLabel\n}\n"],"mappings":"AACA,OAAOA,GAAG,MAAqD,KAAK;AACpE,OAAOC,UAAU,MAAM,sBAAsB;AAI7C;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,MAAc,EAAE;EAC7C,MAAMC,WAAW,GAAG,CAClB,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACJ;EAED,KAAK,MAAMC,SAAS,IAAID,WAAW,EAAE;IACnCD,MAAM,GAAGA,MAAM,CAACG,UAAU,CAACD,SAAS,EAAE,KAAKA,SAAS,EAAE,CAAC;EACzD;EAEA,OAAOF,MAAM;AACf;AAEA,OAAO,MAAMI,oBAAoB,GAAGA,CAClCC,OAA4E,EAC5EC,SAAiB,KACd;EACHD,OAAO,CAACE,OAAO,KAAKD,SAAS;AAC/B,CAAC;;AAED;AACA;AACA;AACA;AACA,OAAO,SAASE,6BAA6BA,CAACC,IAAY,EAAU;EAClE,MAAMC,MAAM,GAAGZ,UAAU,CAACW,IAAI,CAAC;EAC/B,OAAOC,MAAM,CACVC,OAAO,CAAC,uBAAuB,EAAE,eAAe,CAAC,CACjDA,OAAO,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CACrDA,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC;AACnC;;AAEA;AACA;AACA;AACA,OAAO,MAAMC,2BAA2B,GAAG;EACzCC,SAAS,EAAE;IACTf,UAAU,EAAEU;EACd;AACF,CAAqB;;AAErB;AACA;AACA;AACA;AACA,OAAO,MAAMM,0BAA0B,GAAIC,QAAgB,IACzDlB,GAAG,CAACmB,UAAU,CAACD,QAAQ,EAAEH,2BAA2B,CAAkB;;AAExE;AACA;AACA;AACA;AACA;AACA,OAAO,SAASK,kBAAkBA,CAChCC,SAAuB,EACvBC,UAAsB,EACtB;EACA,MAAM;IAAEC;EAAW,CAAC,GAAGD,UAAU;EACjC,MAAME,eAAe,GACnBD,UAAU,CAACF,SAAS,EAAE,kBAAkB,CAAC,IACzCE,UAAU,CAACF,SAAS,EAAE,kBAAkB,CAAC,IACzCE,UAAU,CAACF,SAAS,EAAE,OAAO,CAAC;EAChC,OAAOG,eAAe;AACxB","ignoreList":[]}
@@ -104,6 +104,7 @@ export interface ViewModel extends Record<string, unknown> {
104
104
  upload?: {
105
105
  count: number;
106
106
  summaryList: SummaryList;
107
+ uploadingLabel?: string;
107
108
  };
108
109
  }
109
110
  export interface ComponentViewModel {
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/components/types.ts"],"sourcesContent":["import { type ComponentType, type Item } from '@defra/forms-model'\n\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue,\n type SummaryList\n} from '~/src/server/plugins/engine/types.js'\n\nexport interface RenderContext {\n payload: FormPayload\n errors: FormSubmissionError[] | undefined\n translator: Translator\n isForceAccess?: boolean\n}\n\nexport type ComponentText = {\n classes?: string\n attributes?: string | Record<string, string>\n} & (\n | {\n text: string\n html?: string\n }\n | {\n text?: string\n html: string\n }\n)\n\nexport interface Label {\n text: string\n classes?: string\n html?: string\n isPageHeading?: boolean\n}\n\nexport interface Content {\n title?: string\n text: string\n condition?: string\n}\n\nexport interface BackLink {\n text: string\n href: string\n}\n\nexport type ListItemLabel = Omit<Label, 'text' | 'isPageHeading'>\n\nexport interface ListItem {\n text?: string\n value?: Item['value']\n hint?: {\n id?: string\n text: string\n }\n checked?: boolean\n selected?: boolean\n label?: ListItemLabel\n condition?: string\n}\n\nexport interface DateInputItem {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: Item['value']\n classes?: string\n // Prefix/suffix are used by location fields (e.g., LatLong, EastingNorthing) for units like \"°\"\n // but not by date fields. This interface is reused by both component types.\n prefix?: ComponentText\n suffix?: ComponentText\n errorMessage?: {\n text: string\n }\n condition?: undefined\n}\n\nexport interface ViewModel extends Record<string, unknown> {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: FormValue\n hint?: {\n id?: string\n text: string\n }\n prefix?: ComponentText\n suffix?: ComponentText\n classes?: string\n condition?: string\n errors?: FormSubmissionError[]\n errorMessage?: {\n text: string\n }\n summaryHtml?: string\n html?: string\n attributes: {\n autocomplete?: string\n maxlength?: number\n multiple?: string\n accept?: string\n inputmode?: string\n }\n content?: Content | Content[] | string\n maxlength?: number\n maxwords?: number\n rows?: number\n items?: ListItem[] | DateInputItem[]\n fieldset?: {\n attributes?: string | Record<string, string>\n legend?: Label\n }\n formGroup?: {\n classes?: string\n attributes?: string | Record<string, string>\n }\n showFieldsetError?: boolean\n components?: ComponentViewModel[]\n upload?: {\n count: number\n summaryList: SummaryList\n }\n}\n\nexport interface ComponentViewModel {\n type: ComponentType\n isFormComponent: boolean\n model: ViewModel\n}\n\nexport interface DatePartsState extends Record<string, number> {\n day: number\n month: number\n year: number\n}\n\nexport interface MonthYearState extends Record<string, number> {\n month: number\n year: number\n}\n\nexport interface EastingNorthingState extends Record<string, number> {\n easting: number\n northing: number\n}\n\nexport interface LatLongState extends Record<string, number> {\n latitude: number\n longitude: number\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/components/types.ts"],"sourcesContent":["import { type ComponentType, type Item } from '@defra/forms-model'\n\nimport { type Translator } from '~/src/server/plugins/engine/i18n/types.js'\nimport {\n type FormPayload,\n type FormSubmissionError,\n type FormValue,\n type SummaryList\n} from '~/src/server/plugins/engine/types.js'\n\nexport interface RenderContext {\n payload: FormPayload\n errors: FormSubmissionError[] | undefined\n translator: Translator\n isForceAccess?: boolean\n}\n\nexport type ComponentText = {\n classes?: string\n attributes?: string | Record<string, string>\n} & (\n | {\n text: string\n html?: string\n }\n | {\n text?: string\n html: string\n }\n)\n\nexport interface Label {\n text: string\n classes?: string\n html?: string\n isPageHeading?: boolean\n}\n\nexport interface Content {\n title?: string\n text: string\n condition?: string\n}\n\nexport interface BackLink {\n text: string\n href: string\n}\n\nexport type ListItemLabel = Omit<Label, 'text' | 'isPageHeading'>\n\nexport interface ListItem {\n text?: string\n value?: Item['value']\n hint?: {\n id?: string\n text: string\n }\n checked?: boolean\n selected?: boolean\n label?: ListItemLabel\n condition?: string\n}\n\nexport interface DateInputItem {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: Item['value']\n classes?: string\n // Prefix/suffix are used by location fields (e.g., LatLong, EastingNorthing) for units like \"°\"\n // but not by date fields. This interface is reused by both component types.\n prefix?: ComponentText\n suffix?: ComponentText\n errorMessage?: {\n text: string\n }\n condition?: undefined\n}\n\nexport interface ViewModel extends Record<string, unknown> {\n label?: Label\n type?: string\n id?: string\n name?: string\n value?: FormValue\n hint?: {\n id?: string\n text: string\n }\n prefix?: ComponentText\n suffix?: ComponentText\n classes?: string\n condition?: string\n errors?: FormSubmissionError[]\n errorMessage?: {\n text: string\n }\n summaryHtml?: string\n html?: string\n attributes: {\n autocomplete?: string\n maxlength?: number\n multiple?: string\n accept?: string\n inputmode?: string\n }\n content?: Content | Content[] | string\n maxlength?: number\n maxwords?: number\n rows?: number\n items?: ListItem[] | DateInputItem[]\n fieldset?: {\n attributes?: string | Record<string, string>\n legend?: Label\n }\n formGroup?: {\n classes?: string\n attributes?: string | Record<string, string>\n }\n showFieldsetError?: boolean\n components?: ComponentViewModel[]\n upload?: {\n count: number\n summaryList: SummaryList\n uploadingLabel?: string\n }\n}\n\nexport interface ComponentViewModel {\n type: ComponentType\n isFormComponent: boolean\n model: ViewModel\n}\n\nexport interface DatePartsState extends Record<string, number> {\n day: number\n month: number\n year: number\n}\n\nexport interface MonthYearState extends Record<string, number> {\n month: number\n year: number\n}\n\nexport interface EastingNorthingState extends Record<string, number> {\n easting: number\n northing: number\n}\n\nexport interface LatLongState extends Record<string, number> {\n latitude: number\n longitude: number\n}\n"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,49 @@
1
+ import { type ValidationError } from 'joi';
2
+ /**
3
+ * Base class for errors caused by an invalid form definition — whether it
4
+ * fails schema validation or passes the schema but cannot be used by the
5
+ * engine. Never thrown directly — throw a subclass. Consumers (e.g.
6
+ * forms-runner error pages) detect the family with
7
+ * `instanceof InvalidFormDefinitionError`.
8
+ *
9
+ * When subclassing, remember that the message and the messages of any
10
+ * `cause` chain are shown to form authors on preview error pages. Only
11
+ * describe the problem with the form itself — never include sensitive
12
+ * details such as file paths, configuration values or secrets.
13
+ */
14
+ export declare class InvalidFormDefinitionError extends Error {
15
+ constructor(message: string, options?: ErrorOptions);
16
+ }
17
+ /**
18
+ * Thrown when a condition in the definition cannot be compiled into an
19
+ * evaluatable expression (e.g. it references the wrong component or list).
20
+ */
21
+ export declare class ConditionBuildError extends InvalidFormDefinitionError {
22
+ readonly conditionName: string;
23
+ constructor(conditionName: string, options?: ErrorOptions);
24
+ }
25
+ /**
26
+ * Thrown when a page names a controller that is neither built in nor
27
+ * registered by the host application.
28
+ */
29
+ export declare class UnknownPageControllerError extends InvalidFormDefinitionError {
30
+ readonly controllerName: string;
31
+ constructor(controllerName: string);
32
+ }
33
+ /**
34
+ * Thrown when a component declares a type with no registered implementation.
35
+ */
36
+ export declare class UnknownComponentTypeError extends InvalidFormDefinitionError {
37
+ readonly componentType: string;
38
+ constructor(componentType: string);
39
+ }
40
+ /**
41
+ * Thrown when a form definition fails Joi schema validation. The raw
42
+ * ValidationError is preserved as `cause` so consumers can reach the
43
+ * per-field details; the message carries Joi's own summary so log lines
44
+ * remain diagnostic for consumers that only read `error.message`.
45
+ */
46
+ export declare class SchemaValidationError extends InvalidFormDefinitionError {
47
+ cause: ValidationError;
48
+ constructor(cause: ValidationError);
49
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Base class for errors caused by an invalid form definition — whether it
3
+ * fails schema validation or passes the schema but cannot be used by the
4
+ * engine. Never thrown directly — throw a subclass. Consumers (e.g.
5
+ * forms-runner error pages) detect the family with
6
+ * `instanceof InvalidFormDefinitionError`.
7
+ *
8
+ * When subclassing, remember that the message and the messages of any
9
+ * `cause` chain are shown to form authors on preview error pages. Only
10
+ * describe the problem with the form itself — never include sensitive
11
+ * details such as file paths, configuration values or secrets.
12
+ */
13
+ export class InvalidFormDefinitionError extends Error {
14
+ constructor(message, options) {
15
+ super(message, options);
16
+ this.name = 'InvalidFormDefinitionError';
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Thrown when a condition in the definition cannot be compiled into an
22
+ * evaluatable expression (e.g. it references the wrong component or list).
23
+ */
24
+ export class ConditionBuildError extends InvalidFormDefinitionError {
25
+ conditionName;
26
+ constructor(conditionName, options) {
27
+ super(`Failed to build condition '${conditionName}'`, options);
28
+ this.name = 'ConditionBuildError';
29
+ this.conditionName = conditionName;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Thrown when a page names a controller that is neither built in nor
35
+ * registered by the host application.
36
+ */
37
+ export class UnknownPageControllerError extends InvalidFormDefinitionError {
38
+ controllerName;
39
+ constructor(controllerName) {
40
+ super(`Page controller ${controllerName} does not exist`);
41
+ this.name = 'UnknownPageControllerError';
42
+ this.controllerName = controllerName;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Thrown when a component declares a type with no registered implementation.
48
+ */
49
+ export class UnknownComponentTypeError extends InvalidFormDefinitionError {
50
+ componentType;
51
+ constructor(componentType) {
52
+ super(`Component type ${componentType} does not exist`);
53
+ this.name = 'UnknownComponentTypeError';
54
+ this.componentType = componentType;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Thrown when a form definition fails Joi schema validation. The raw
60
+ * ValidationError is preserved as `cause` so consumers can reach the
61
+ * per-field details; the message carries Joi's own summary so log lines
62
+ * remain diagnostic for consumers that only read `error.message`.
63
+ */
64
+ export class SchemaValidationError extends InvalidFormDefinitionError {
65
+ // Error types `cause` as unknown; this constructor only accepts a Joi
66
+ // ValidationError, so narrow the declaration for consumers (type-only).
67
+
68
+ constructor(cause) {
69
+ super(`Invalid form definition: ${cause.message}`, {
70
+ cause
71
+ });
72
+ this.name = 'SchemaValidationError';
73
+ }
74
+ }
75
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":["InvalidFormDefinitionError","Error","constructor","message","options","name","ConditionBuildError","conditionName","UnknownPageControllerError","controllerName","UnknownComponentTypeError","componentType","SchemaValidationError","cause"],"sources":["../../../../src/server/plugins/engine/errors.ts"],"sourcesContent":["import { type ValidationError } from 'joi'\n\n/**\n * Base class for errors caused by an invalid form definition — whether it\n * fails schema validation or passes the schema but cannot be used by the\n * engine. Never thrown directly — throw a subclass. Consumers (e.g.\n * forms-runner error pages) detect the family with\n * `instanceof InvalidFormDefinitionError`.\n *\n * When subclassing, remember that the message and the messages of any\n * `cause` chain are shown to form authors on preview error pages. Only\n * describe the problem with the form itself — never include sensitive\n * details such as file paths, configuration values or secrets.\n */\nexport class InvalidFormDefinitionError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'InvalidFormDefinitionError'\n }\n}\n\n/**\n * Thrown when a condition in the definition cannot be compiled into an\n * evaluatable expression (e.g. it references the wrong component or list).\n */\nexport class ConditionBuildError extends InvalidFormDefinitionError {\n public readonly conditionName: string\n\n constructor(conditionName: string, options?: ErrorOptions) {\n super(`Failed to build condition '${conditionName}'`, options)\n this.name = 'ConditionBuildError'\n this.conditionName = conditionName\n }\n}\n\n/**\n * Thrown when a page names a controller that is neither built in nor\n * registered by the host application.\n */\nexport class UnknownPageControllerError extends InvalidFormDefinitionError {\n public readonly controllerName: string\n\n constructor(controllerName: string) {\n super(`Page controller ${controllerName} does not exist`)\n this.name = 'UnknownPageControllerError'\n this.controllerName = controllerName\n }\n}\n\n/**\n * Thrown when a component declares a type with no registered implementation.\n */\nexport class UnknownComponentTypeError extends InvalidFormDefinitionError {\n public readonly componentType: string\n\n constructor(componentType: string) {\n super(`Component type ${componentType} does not exist`)\n this.name = 'UnknownComponentTypeError'\n this.componentType = componentType\n }\n}\n\n/**\n * Thrown when a form definition fails Joi schema validation. The raw\n * ValidationError is preserved as `cause` so consumers can reach the\n * per-field details; the message carries Joi's own summary so log lines\n * remain diagnostic for consumers that only read `error.message`.\n */\nexport class SchemaValidationError extends InvalidFormDefinitionError {\n // Error types `cause` as unknown; this constructor only accepts a Joi\n // ValidationError, so narrow the declaration for consumers (type-only).\n declare cause: ValidationError\n\n constructor(cause: ValidationError) {\n super(`Invalid form definition: ${cause.message}`, { cause })\n this.name = 'SchemaValidationError'\n }\n}\n"],"mappings":"AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,0BAA0B,SAASC,KAAK,CAAC;EACpDC,WAAWA,CAACC,OAAe,EAAEC,OAAsB,EAAE;IACnD,KAAK,CAACD,OAAO,EAAEC,OAAO,CAAC;IACvB,IAAI,CAACC,IAAI,GAAG,4BAA4B;EAC1C;AACF;;AAEA;AACA;AACA;AACA;AACA,OAAO,MAAMC,mBAAmB,SAASN,0BAA0B,CAAC;EAClDO,aAAa;EAE7BL,WAAWA,CAACK,aAAqB,EAAEH,OAAsB,EAAE;IACzD,KAAK,CAAC,8BAA8BG,aAAa,GAAG,EAAEH,OAAO,CAAC;IAC9D,IAAI,CAACC,IAAI,GAAG,qBAAqB;IACjC,IAAI,CAACE,aAAa,GAAGA,aAAa;EACpC;AACF;;AAEA;AACA;AACA;AACA;AACA,OAAO,MAAMC,0BAA0B,SAASR,0BAA0B,CAAC;EACzDS,cAAc;EAE9BP,WAAWA,CAACO,cAAsB,EAAE;IAClC,KAAK,CAAC,mBAAmBA,cAAc,iBAAiB,CAAC;IACzD,IAAI,CAACJ,IAAI,GAAG,4BAA4B;IACxC,IAAI,CAACI,cAAc,GAAGA,cAAc;EACtC;AACF;;AAEA;AACA;AACA;AACA,OAAO,MAAMC,yBAAyB,SAASV,0BAA0B,CAAC;EACxDW,aAAa;EAE7BT,WAAWA,CAACS,aAAqB,EAAE;IACjC,KAAK,CAAC,kBAAkBA,aAAa,iBAAiB,CAAC;IACvD,IAAI,CAACN,IAAI,GAAG,2BAA2B;IACvC,IAAI,CAACM,aAAa,GAAGA,aAAa;EACpC;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,qBAAqB,SAASZ,0BAA0B,CAAC;EACpE;EACA;;EAGAE,WAAWA,CAACW,KAAsB,EAAE;IAClC,KAAK,CAAC,4BAA4BA,KAAK,CAACV,OAAO,EAAE,EAAE;MAAEU;IAAM,CAAC,CAAC;IAC7D,IAAI,CAACR,IAAI,GAAG,uBAAuB;EACrC;AACF","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) {