@defra/forms-engine-plugin 4.0.5 → 4.0.7

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 (83) hide show
  1. package/.public/stylesheets/application.min.css +2 -2
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/_location-input.scss +60 -0
  4. package/.server/client/stylesheets/application.scss +1 -6
  5. package/.server/client/stylesheets/shared.scss +8 -0
  6. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +28 -0
  7. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -1
  8. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  9. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +121 -0
  10. package/.server/server/plugins/engine/components/EastingNorthingField.js +166 -0
  11. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -0
  12. package/.server/server/plugins/engine/components/LatLongField.d.ts +121 -0
  13. package/.server/server/plugins/engine/components/LatLongField.js +164 -0
  14. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -0
  15. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +134 -0
  16. package/.server/server/plugins/engine/components/LocationFieldBase.js +85 -0
  17. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -0
  18. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +108 -0
  19. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +96 -0
  20. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -0
  21. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +19 -0
  22. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +40 -0
  23. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -0
  24. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +19 -0
  25. package/.server/server/plugins/engine/components/OsGridRefField.js +56 -0
  26. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -0
  27. package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -4
  28. package/.server/server/plugins/engine/components/helpers/components.js +21 -29
  29. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  30. package/.server/server/plugins/engine/components/index.d.ts +4 -0
  31. package/.server/server/plugins/engine/components/index.js +4 -0
  32. package/.server/server/plugins/engine/components/index.js.map +1 -1
  33. package/.server/server/plugins/engine/components/markdownParser.d.ts +2 -0
  34. package/.server/server/plugins/engine/components/markdownParser.js +28 -0
  35. package/.server/server/plugins/engine/components/markdownParser.js.map +1 -0
  36. package/.server/server/plugins/engine/components/types.d.ts +10 -0
  37. package/.server/server/plugins/engine/components/types.js.map +1 -1
  38. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +7 -0
  39. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  40. package/.server/server/plugins/engine/types/index.d.ts +1 -1
  41. package/.server/server/plugins/engine/types/index.js.map +1 -1
  42. package/.server/server/plugins/engine/types.d.ts +2 -2
  43. package/.server/server/plugins/engine/types.js.map +1 -1
  44. package/.server/server/plugins/engine/views/components/_location-field-base.html +53 -0
  45. package/.server/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  46. package/.server/server/plugins/engine/views/components/latlongfield.html +5 -0
  47. package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  48. package/.server/server/plugins/engine/views/components/osgridreffield.html +13 -0
  49. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  50. package/package.json +3 -3
  51. package/src/client/stylesheets/_location-input.scss +60 -0
  52. package/src/client/stylesheets/application.scss +1 -6
  53. package/src/client/stylesheets/shared.scss +8 -0
  54. package/src/server/forms/register-as-a-unicorn-breeder.yaml +28 -0
  55. package/src/server/plugins/engine/components/ComponentBase.ts +1 -1
  56. package/src/server/plugins/engine/components/EastingNorthingField.test.ts +665 -0
  57. package/src/server/plugins/engine/components/EastingNorthingField.ts +224 -0
  58. package/src/server/plugins/engine/components/LatLongField.test.ts +700 -0
  59. package/src/server/plugins/engine/components/LatLongField.ts +213 -0
  60. package/src/server/plugins/engine/components/LocationFieldBase.test.ts +253 -0
  61. package/src/server/plugins/engine/components/LocationFieldBase.ts +152 -0
  62. package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +338 -0
  63. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +123 -0
  64. package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +438 -0
  65. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +52 -0
  66. package/src/server/plugins/engine/components/OsGridRefField.test.ts +469 -0
  67. package/src/server/plugins/engine/components/OsGridRefField.ts +71 -0
  68. package/src/server/plugins/engine/components/helpers/components.test.ts +270 -0
  69. package/src/server/plugins/engine/components/helpers/components.ts +39 -47
  70. package/src/server/plugins/engine/components/helpers/helpers.test.ts +71 -1
  71. package/src/server/plugins/engine/components/index.ts +4 -0
  72. package/src/server/plugins/engine/components/markdownParser.ts +40 -0
  73. package/src/server/plugins/engine/components/types.ts +14 -0
  74. package/src/server/plugins/engine/outputFormatters/adapter/v1.location.test.ts +356 -0
  75. package/src/server/plugins/engine/pageControllers/helpers/helpers.test.ts +4 -0
  76. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +8 -0
  77. package/src/server/plugins/engine/types/index.ts +2 -0
  78. package/src/server/plugins/engine/types.ts +4 -0
  79. package/src/server/plugins/engine/views/components/_location-field-base.html +53 -0
  80. package/src/server/plugins/engine/views/components/eastingnorthingfield.html +5 -0
  81. package/src/server/plugins/engine/views/components/latlongfield.html +5 -0
  82. package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +13 -0
  83. package/src/server/plugins/engine/views/components/osgridreffield.html +13 -0
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page,\n type UkAddressFieldComponent\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n h: FormResponseToolkit,\n context: FormContext\n) =>\n | ResponseObject\n | FormResponseToolkit['continue']\n | Promise<ResponseObject | FormResponseToolkit['continue']>\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface ExternalArgs {\n component: ComponentDef\n controller: QuestionPageController\n sourceUrl: string\n actionArgs: Record<string, string>\n}\n\nexport interface PostcodeLookupExternalArgs extends ExternalArgs {\n component: UkAddressFieldComponent\n actionArgs: { step: string }\n}\n\nexport interface ExternalStateAppendage {\n component: string\n data: FormStateValue | FormState\n}\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n ordnanceSurveyApiKey?: string\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAoDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AAmPd;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page,\n type UkAddressFieldComponent\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type EastingNorthingState,\n type LatLongState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/index.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n h: FormResponseToolkit,\n context: FormContext\n) =>\n | ResponseObject\n | FormResponseToolkit['continue']\n | Promise<ResponseObject | FormResponseToolkit['continue']>\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface ExternalArgs {\n component: ComponentDef\n controller: QuestionPageController\n sourceUrl: string\n actionArgs: Record<string, string>\n}\n\nexport interface PostcodeLookupExternalArgs extends ExternalArgs {\n component: UkAddressFieldComponent\n actionArgs: { step: string }\n}\n\nexport interface ExternalStateAppendage {\n component: string\n data: FormStateValue | FormState\n}\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n ordnanceSurveyApiKey?: string\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n custom?: Record<string, unknown>\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n | EastingNorthingState\n | LatLongState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue | null>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAsDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AAmPd;AACA;AACA","ignoreList":[]}
@@ -0,0 +1,53 @@
1
+ {% from "govuk/components/input/macro.njk" import govukInput %}
2
+ {% from "govuk/components/fieldset/macro.njk" import govukFieldset %}
3
+ {% from "govuk/components/details/macro.njk" import govukDetails %}
4
+ {% from "govuk/components/hint/macro.njk" import govukHint %}
5
+
6
+ {% macro LocationFieldBase(component, inputType, inputMode) %}
7
+ {% set fieldsetHtml %}
8
+ {% if component.model.hint %}
9
+ {{ govukHint({
10
+ id: component.model.name + "-hint",
11
+ text: component.model.hint.text
12
+ }) }}
13
+ {% endif %}
14
+
15
+ <div class="app-location-input">
16
+ {% for item in component.model.items %}
17
+ <div class="app-location-input__item">
18
+ {{ govukInput({
19
+ id: item.id,
20
+ name: item.name,
21
+ label: {
22
+ text: item.label,
23
+ classes: "govuk-label--s"
24
+ },
25
+ classes: item.classes,
26
+ value: item.value,
27
+ type: inputType,
28
+ inputmode: inputMode,
29
+ prefix: item.prefix,
30
+ suffix: item.suffix
31
+ }) }}
32
+ </div>
33
+ {% endfor %}
34
+ </div>
35
+
36
+ {% if component.model.instructionText %}
37
+ {{ govukDetails({
38
+ summaryText: "How to find location details",
39
+ html: component.model.instructionText | safe
40
+ }) }}
41
+ {% endif %}
42
+ {% endset %}
43
+
44
+ {{ govukFieldset({
45
+ legend: {
46
+ text: component.model.fieldset.legend.text,
47
+ classes: component.model.fieldset.legend.classes,
48
+ isPageHeading: false
49
+ },
50
+ html: fieldsetHtml
51
+ }) }}
52
+ {% endmacro %}
53
+
@@ -0,0 +1,5 @@
1
+ {% from "./_location-field-base.html" import LocationFieldBase %}
2
+
3
+ {% macro EastingNorthingField(component) %}
4
+ {{ LocationFieldBase(component, "number", "numeric") }}
5
+ {% endmacro %}
@@ -0,0 +1,5 @@
1
+ {% from "./_location-field-base.html" import LocationFieldBase %}
2
+
3
+ {% macro LatLongField(component) %}
4
+ {{ LocationFieldBase(component, "text", "decimal") }}
5
+ {% endmacro %}
@@ -0,0 +1,13 @@
1
+ {% from "components/textfield.html" import TextField %}
2
+ {% from "govuk/components/details/macro.njk" import govukDetails %}
3
+
4
+ {% macro NationalGridFieldNumberField(component) %}
5
+ {{ TextField(component) }}
6
+
7
+ {% if component.model.instructionText %}
8
+ {{ govukDetails({
9
+ summaryText: "How to find location details",
10
+ html: component.model.instructionText | safe
11
+ }) }}
12
+ {% endif %}
13
+ {% endmacro %}
@@ -0,0 +1,13 @@
1
+ {% from "components/textfield.html" import TextField %}
2
+ {% from "govuk/components/details/macro.njk" import govukDetails %}
3
+
4
+ {% macro OsGridRefField(component) %}
5
+ {{ TextField(component) }}
6
+
7
+ {% if component.model.instructionText %}
8
+ {{ govukDetails({
9
+ summaryText: "How to find location details",
10
+ html: component.model.instructionText | safe
11
+ }) }}
12
+ {% endif %}
13
+ {% endmacro %}
@@ -3,5 +3,5 @@
3
3
  * @this {NunjucksContext}
4
4
  * @param {string} name - The name of the component
5
5
  */
6
- export function field(this: NunjucksContext, name: string): import("../../engine/components/TextField.js").TextField | import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/UkAddressField.js").UkAddressField | import("../../engine/components/FileUploadField.js").FileUploadField | import("../../engine/components/Details.js").Details | import("../../engine/components/Html.js").Html | import("../../engine/components/InsetText.js").InsetText | import("../../engine/components/List.js").List | import("../../engine/components/Markdown.js").Markdown | undefined;
6
+ export function field(this: NunjucksContext, name: string): import("../../engine/components/TextField.js").TextField | import("../../engine/components/SelectField.js").SelectField | import("../../engine/components/RadiosField.js").RadiosField | import("../../engine/components/YesNoField.js").YesNoField | import("../../engine/components/CheckboxesField.js").CheckboxesField | import("../../engine/components/DatePartsField.js").DatePartsField | import("../../engine/components/EastingNorthingField.js").EastingNorthingField | import("../../engine/components/EmailAddressField.js").EmailAddressField | import("../../engine/components/LatLongField.js").LatLongField | import("../../engine/components/MonthYearField.js").MonthYearField | import("../../engine/components/MultilineTextField.js").MultilineTextField | import("../../engine/components/NationalGridFieldNumberField.js").NationalGridFieldNumberField | import("../../engine/components/NumberField.js").NumberField | import("../../engine/components/OsGridRefField.js").OsGridRefField | import("../../engine/components/TelephoneNumberField.js").TelephoneNumberField | import("../../engine/components/UkAddressField.js").UkAddressField | import("../../engine/components/FileUploadField.js").FileUploadField | import("../../engine/components/Details.js").Details | import("../../engine/components/Html.js").Html | import("../../engine/components/InsetText.js").InsetText | import("../../engine/components/List.js").List | import("../../engine/components/Markdown.js").Markdown | undefined;
7
7
  import type { NunjucksContext } from '~/src/server/plugins/nunjucks/types.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -27,7 +27,7 @@
27
27
  "./components": "./.server/server/plugins/engine/components/index.js",
28
28
  "./services/*": "./.server/server/plugins/engine/services/*",
29
29
  "./engine/*": "./.server/server/plugins/engine/*",
30
- "./helpers.js": "./.server/server/plugins/engine/components/helpers.js",
30
+ "./helpers.js": "./.server/server/plugins/engine/components/helpers/index.js",
31
31
  "./schema.js": "./.server/server/schemas/index.js",
32
32
  "./templates/*": "./.server/server/plugins/engine/views/*",
33
33
  "./cache-service.js": "./.server/server/services/cacheService.js",
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "license": "SEE LICENSE IN LICENSE",
72
72
  "dependencies": {
73
- "@defra/forms-model": "^3.0.560",
73
+ "@defra/forms-model": "^3.0.569",
74
74
  "@defra/hapi-tracing": "^1.26.0",
75
75
  "@elastic/ecs-pino-format": "^1.5.0",
76
76
  "@hapi/boom": "^10.0.1",
@@ -0,0 +1,60 @@
1
+ @use "govuk-frontend" as *;
2
+
3
+ .app-location-input {
4
+ @include govuk-clearfix;
5
+ font-size: 0; // removes whitespace caused by inline-block
6
+ margin-bottom: govuk-spacing(6);
7
+
8
+ &:has(.govuk-input--error) {
9
+ border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
10
+ padding-left: govuk-spacing(3);
11
+ margin-top: 0;
12
+ }
13
+ }
14
+
15
+ .govuk-hint:has(+ .app-location-input .govuk-input--error) {
16
+ border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
17
+ padding-left: govuk-spacing(3);
18
+ margin-bottom: 0;
19
+ }
20
+
21
+ .govuk-fieldset:has(.app-location-input .govuk-input--error) {
22
+ .govuk-fieldset__legend {
23
+ border-left: $govuk-border-width-form-group-error solid $govuk-error-colour;
24
+ padding-left: govuk-spacing(3);
25
+ margin-bottom: 0;
26
+ }
27
+
28
+ .govuk-fieldset__legend + .govuk-hint {
29
+ margin-top: 0;
30
+ }
31
+ }
32
+
33
+ .app-location-input__item {
34
+ display: inline-block;
35
+ margin-right: govuk-spacing(4);
36
+ margin-bottom: govuk-spacing(4);
37
+
38
+ &:last-child {
39
+ margin-right: 0;
40
+ }
41
+
42
+ @include govuk-media-query($from: tablet) {
43
+ margin-bottom: 0;
44
+ }
45
+
46
+ .govuk-form-group {
47
+ margin-bottom: 0;
48
+ display: inline-block;
49
+ width: auto;
50
+ }
51
+
52
+ .govuk-label {
53
+ display: block;
54
+ }
55
+
56
+ .govuk-input {
57
+ margin-bottom: 0;
58
+ width: auto;
59
+ }
60
+ }
@@ -2,6 +2,7 @@
2
2
  @use "shared";
3
3
  @use "code";
4
4
  @use "tag-env";
5
+ @use "location-input";
5
6
 
6
7
  // An example of some user-supplied styling
7
8
  // Not great practice but it illustrates the point
@@ -12,9 +13,3 @@
12
13
  .govuk-header__container {
13
14
  border-bottom: 10px solid #003d16;
14
15
  }
15
-
16
- @media print {
17
- .govuk-link[href]::after {
18
- content: none;
19
- }
20
- }
@@ -2,6 +2,7 @@
2
2
  @use "pkg:accessible-autocomplete";
3
3
  @use "prose";
4
4
  @use "summary-list";
5
+ @use "location-input";
5
6
 
6
7
  // Use default GDS Transport font for autocomplete
7
8
  .autocomplete__hint,
@@ -24,3 +25,10 @@
24
25
  background-color: transparent;
25
26
  @include govuk-font($size: 19);
26
27
  }
28
+
29
+ // Hide urls for hyperlinks when in print mode
30
+ @media print {
31
+ .govuk-link[href]::after {
32
+ content: none;
33
+ }
34
+ }
@@ -163,6 +163,34 @@ pages:
163
163
  next:
164
164
  - path: '/how-many-members-of-staff-will-look-after-the-unicorns'
165
165
  components:
166
+ - name: dfGYuk
167
+ options: {}
168
+ schema: {}
169
+ type: EastingNorthingField
170
+ title: Easting and northing
171
+ hint:
172
+ This is an Easting and Northing component
173
+ - name: seTThb
174
+ options: {}
175
+ schema: {}
176
+ type: LatLongField
177
+ title: Latitute and longitude
178
+ hint:
179
+ This is an Latitute and Longitude component
180
+ - name: bhjloS
181
+ options: {}
182
+ schema: {}
183
+ type: NationalGridFieldNumberField
184
+ title: National grid field number
185
+ hint:
186
+ This is an National Grid Field Number component
187
+ - name: dfQQws
188
+ options: {}
189
+ schema: {}
190
+ type: OsGridRefField
191
+ title: Ordnance survey grid reference
192
+ hint:
193
+ This is an Ordnance survey Grid Reference component
166
194
  - name: bClCvo
167
195
  options: {}
168
196
  schema: {}
@@ -22,7 +22,7 @@ export class ComponentBase {
22
22
  type: ComponentDef['type']
23
23
  name: ComponentDef['name']
24
24
  title: ComponentDef['title']
25
- schema?: Extract<ComponentDef, { schema: object }>['schema']
25
+ schema?: Extract<ComponentDef, { schema?: object }>['schema']
26
26
  options?: Extract<ComponentDef, { options: object }>['options']
27
27
 
28
28
  isFormComponent = false