@defra/forms-engine-plugin 4.0.12 → 4.0.14
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.
- package/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/application.scss +0 -1
- package/.server/client/stylesheets/shared.scss +0 -1
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +4 -2
- package/.server/server/plugins/engine/components/EastingNorthingField.js +13 -12
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
- package/.server/server/plugins/engine/components/EmailAddressField.d.ts +7 -1
- package/.server/server/plugins/engine/components/LatLongField.js +7 -6
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +3 -9
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +5 -5
- package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/OsGridRefField.js +8 -4
- package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
- package/.server/server/plugins/engine/components/types.d.ts +3 -0
- package/.server/server/plugins/engine/components/types.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +2 -2
- package/.server/server/plugins/engine/models/FormModel.js +1 -1
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/types.d.ts +1 -1
- package/.server/server/plugins/engine/models/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/_location-field-base.html +22 -14
- package/package.json +2 -2
- package/src/client/stylesheets/application.scss +0 -1
- package/src/client/stylesheets/shared.scss +0 -1
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +4 -2
- package/src/server/plugins/engine/components/EastingNorthingField.test.ts +9 -3
- package/src/server/plugins/engine/components/EastingNorthingField.ts +13 -12
- package/src/server/plugins/engine/components/LatLongField.test.ts +9 -3
- package/src/server/plugins/engine/components/LatLongField.ts +7 -6
- package/src/server/plugins/engine/components/LocationFieldHelpers.test.ts +14 -5
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +3 -9
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.test.ts +9 -12
- package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +5 -5
- package/src/server/plugins/engine/components/OsGridRefField.test.ts +19 -6
- package/src/server/plugins/engine/components/OsGridRefField.ts +8 -4
- package/src/server/plugins/engine/components/types.ts +3 -0
- package/src/server/plugins/engine/models/FormModel.ts +1 -1
- package/src/server/plugins/engine/models/types.ts +1 -1
- package/src/server/plugins/engine/views/components/_location-field-base.html +22 -14
- package/.server/client/stylesheets/_location-input.scss +0 -60
- package/src/client/stylesheets/_location-input.scss +0 -60
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n ![FormAction.Validate, FormAction.SaveAndExit].includes(action) &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"AAAA,SACEA,aAAa,EACbC,eAAe,EACfC,cAAc,EACdC,cAAc,EACdC,aAAa,EACbC,6BAA6B,EAC7BC,oBAAoB,EACpBC,sBAAsB,EACtBC,sBAAsB,EACtBC,aAAa,EACbC,WAAW,EACXC,oBAAoB,EACpBC,WAAW,EACXC,aAAa,QAUR,oBAAoB;AAC3B,SAASC,GAAG,EAAEC,MAAM,QAAQ,UAAU;AACtC,SAASC,MAAM,QAAoB,WAAW;AAC9C,OAAOC,GAAG,MAAM,KAAK;AAErB,SAASC,YAAY;AAErB;AACA,SACEC,gBAAgB;AAGlB,SAASC,eAAe;AACxB,SACEC,QAAQ,EACRC,QAAQ,EACRC,OAAO,EACPC,aAAa;AAIf,SACEC,UAAU;AAGZ,SAASC,iBAAiB,IAAIC,IAAI;AAClC,OAAO,KAAKC,eAAe;AAQ3B,SAASC,UAAU;AACnB,SAASC,KAAK;AAGd,MAAMC,MAAM,GAAGb,YAAY,CAAC,CAAC;AAE7B,OAAO,MAAMc,SAAS,CAAC;EACrB;EACAC,MAAM;EAENC,aAAa;;EAEb;EACAC,GAAG;EAEHC,KAAK;EACLC,QAAQ,GAA+B,EAAE;EACzCC,IAAI;EACJC,MAAM;EACNC,QAAQ;EACRC,aAAa;EACbC,oBAAoB;EACpBC,UAAU;EACVC,KAAK;EACLC,QAAQ;EAERC,WAAW;EACXC,UAAU;EAEVC,UAAU;EACVC,YAAY;EAEZC,eAAe;EACfC,iBAAiB;EAEjBC,OAAO;EACPC,YAAY;EAEZC,WAAWA,CACTnB,GAAoB,EACpBoB,OAIC,EACDV,QAAkB,GAAGjB,eAAe,EACpCkB,WAAmD,EACnD;IACA,IAAIU,MAAM,GAAGjD,sBAAsB;IAEnC,IAAI,CAAC4B,GAAG,CAACqB,MAAM,IAAIrB,GAAG,CAACqB,MAAM,KAAKpD,aAAa,CAACqD,EAAE,EAAE;MAClD1B,MAAM,CAAC2B,IAAI,CACT,8BAA8BvB,GAAG,CAACG,IAAI,2HACxC,CAAC;MACDkB,MAAM,GAAGlD,oBAAoB;IAC/B;IAEA,MAAMqD,MAAM,GAAGH,MAAM,CAACI,QAAQ,CAACzB,GAAG,EAAE;MAAE0B,UAAU,EAAE;IAAM,CAAC,CAAC;IAE1D,IAAIF,MAAM,CAACG,KAAK,EAAE;MAChB,MAAMH,MAAM,CAACG,KAAK;IACpB;;IAEA;IACA;IACA3B,GAAG,GAAG4B,eAAe,CAACJ,MAAM,CAACK,KAAK,CAAC;;IAEnC;IACA7B,GAAG,CAACC,KAAK,CAAC6B,IAAI,CAAC;MACbC,EAAE,EAAE/B,GAAG,CAACqB,MAAM,KAAKpD,aAAa,CAACqD,EAAE,GAAG5C,aAAa,GAAGD,WAAW;MACjE0B,IAAI,EAAE,SAAS;MACf6B,KAAK,EAAE,QAAQ;MACfC,IAAI,EAAE,SAAS;MACfC,KAAK,EAAE,CACL;QACEH,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,KAAK;QACXN,KAAK,EAAE;MACT,CAAC,EACD;QACEE,EAAE,EAAE,sCAAsC;QAC1CI,IAAI,EAAE,IAAI;QACVN,KAAK,EAAE;MACT,CAAC;IAEL,CAAC,CAAC;;IAEF;IACAxC,aAAa,CAACW,GAAG,CAAC;IAElB,IAAI,CAACF,MAAM,GAAGE,GAAG,CAACF,MAAM;IACxB,IAAI,CAACC,aAAa,GAAGC,GAAG,CAACqB,MAAM,IAAIpD,aAAa,CAACqD,EAAE;IACnD,IAAI,CAACtB,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,KAAK,GAAGD,GAAG,CAACC,KAAK;IACtB,IAAI,CAACC,QAAQ,GAAGF,GAAG,CAACE,QAAQ;IAC5B,IAAI,CAACC,IAAI,GAAGH,GAAG,CAACG,IAAI,IAAI,EAAE;IAC1B,IAAI,CAACC,MAAM,GAAGoB,MAAM,CAACK,KAAK;IAC1B,IAAI,CAACxB,QAAQ,GAAGe,OAAO,CAACf,QAAQ;IAChC,IAAI,CAACC,aAAa,GAAGc,OAAO,CAACd,aAAa;IAC1C,IAAI,CAACC,oBAAoB,GAAGa,OAAO,CAACb,oBAAoB;IACxD,IAAI,CAACC,UAAU,GAAG,CAAC,CAAC;IACpB,IAAI,CAACE,QAAQ,GAAGA,QAAQ;IACxB,IAAI,CAACC,WAAW,GAAGA,WAAW;IAE9B,IAAI,CAACC,UAAU,GAAG,IAAIwB,GAAG,CAACpC,GAAG,CAACS,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAACzB,UAAU,GAAG,IAAIuB,GAAG,CAACpC,GAAG,CAACC,KAAK,CAACoC,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACrC,IAAI,EAAEqC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC1B,YAAY,GAAG,IAAIsB,GAAG,CACzBpC,GAAG,CAACC,KAAK,CACNwC,MAAM,CAAED,IAAI,IAAKA,IAAI,CAACT,EAAE,CAAC,CAAC;IAC3B;IAAA,CACCM,GAAG,CAAEG,IAAI,IAAK,CAACA,IAAI,CAACT,EAAE,EAAYS,IAAI,CAAC,CAC5C,CAAC;IACD,IAAI,CAACzB,eAAe,GAAG,IAAIqB,GAAG,CAC5BpC,GAAG,CAACS,KAAK,CACNgC,MAAM,CAACnE,aAAa,CAAC,CACrBoE,OAAO,CAAEJ,IAAI,IACZA,IAAI,CAACK,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAACA,SAAS,CAACzC,IAAI,EAAEyC,SAAS,CAAC,CAChE,CACJ,CAAC;IACD,IAAI,CAAC5B,iBAAiB,GAAG,IAAIoB,GAAG,CAC9BpC,GAAG,CAACS,KAAK,CAACgC,MAAM,CAACnE,aAAa,CAAC,CAACoE,OAAO,CAAEJ,IAAI,IAC3CA,IAAI,CAACK,UAAU,CACZF,MAAM,CAAEG,SAAS,IAAKA,SAAS,CAACb,EAAE,CAAC,CAAC;IAAA,CACpCM,GAAG,CAAEO,SAAS,IAAK;MAClB;MACA,OAAO,CAACA,SAAS,CAACb,EAAE,EAAYa,SAAS,CAAC;IAC5C,CAAC,CACL,CACF,CAAC;IAED5C,GAAG,CAACQ,UAAU,CAACqC,OAAO,CAAEC,YAAY,IAAK;MACvC,MAAMC,SAAS,GAAG,IAAI,CAACC,aAAa,CAClCxE,oBAAoB,CAACsE,YAAY,CAAC,GAC9B5E,6BAA6B,CAAC4E,YAAY,EAAE,IAAI,CAAC,GACjDA,YACN,CAAC;MACD,IAAI,CAACtC,UAAU,CAACuC,SAAS,CAAC5C,IAAI,CAAC,GAAG4C,SAAS;IAC7C,CAAC,CAAC;IAEF,IAAI,CAACtC,KAAK,GAAGT,GAAG,CAACS,KAAK,CAAC4B,GAAG,CAAEY,OAAO,IAAK3D,UAAU,CAAC,IAAI,EAAE2D,OAAO,CAAC,CAAC;IAElE,IACE,CAACjD,GAAG,CAACS,KAAK,CAACyC,IAAI,CACb,CAAC;MAAEC;IAAW,CAAC;IACb;IACAA,UAAU,KAAKnF,cAAc,CAACoF,MAClC,CAAC,EACD;MACA,IAAI,CAAC3C,KAAK,CAACqB,IAAI,CACbxC,UAAU,CAAC,IAAI,EAAE;QACf0C,KAAK,EAAE,gBAAgB;QACvBO,IAAI,EAAExE,cAAc,CAACqF,MAAM;QAC3BD,UAAU,EAAEnF,cAAc,CAACoF;MAC7B,CAAC,CACH,CAAC;IACH;IAEA,IAAI,CAACnC,OAAO,GAAG,IAAImB,GAAG,CAAC,IAAI,CAAC3B,KAAK,CAAC4B,GAAG,CAAEC,IAAI,IAAK,CAACA,IAAI,CAACC,IAAI,EAAED,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,CAACpB,YAAY,GAAG,IAAIkB,GAAG,CACzB,IAAI,CAAC3B,KAAK,CAACiC,OAAO,CAAEJ,IAAI,IACtBA,IAAI,CAACe,UAAU,CAACV,UAAU,CAACN,GAAG,CAAEO,SAAS,IAAK,CAC5CA,SAAS,CAACzC,IAAI,EACdyC,SAAS,CACV,CACH,CACF,CAAC;EACH;;EAEA;AACF;AACA;AACA;EACEU,kBAAkBA,CAACC,aAAoC,EAAE;IACvD;IACA;IACA,IAAIlC,MAAM,GAAGvC,GAAG,CAAC0E,MAAM,CAAsB,CAAC,CAACC,QAAQ,CAAC,CAAC;IAEzDF,aAAa,CAACV,OAAO,CAAEP,IAAI,IAAK;MAC9BjB,MAAM,GAAGA,MAAM,CAACqC,MAAM,CAACpB,IAAI,CAACe,UAAU,CAACM,WAAW,CAAC;IACrD,CAAC,CAAC;IAEF,OAAOtC,MAAM;EACf;;EAEA;AACF;AACA;AACA;EACE2B,aAAaA,CAACD,SAA2B,EAAuB;IAC9D,MAAMa,MAAM,GAAG,IAAI/E,MAAM,CAAC;MACxBgF,SAAS,EAAE;QACTC,OAAO,EAAE;MACX;IACF,CAAC,CAAC;IAEFC,MAAM,CAACC,MAAM,CAACJ,MAAM,CAACK,SAAS,EAAE;MAC9BC,iBAAiBA,CAACC,UAAkB,EAAEC,QAAmB,EAAE;QACzD;QACA;QACA;QACA;QACA,OAAOxF,MAAM,CACXD,GAAG,CAACM,eAAe,CAAC,CAAC,EAAE;UAAE,CAACmF,QAAQ,GAAGD;QAAW,CAAC,CAAC,EAClD,YACF,CAAC;MACH;IACF,CAAC,CAAC;IAEF,MAAM;MAAEhE,IAAI;MAAEkE,WAAW;MAAExC;IAAM,CAAC,GAAGkB,SAAS;IAC9C,MAAMuB,IAAI,GAAG,IAAI,CAACC,qBAAqB,CAAC1C,KAAK,EAAE+B,MAAM,CAAC;IAEtD,MAAMY,EAAE,GAAIC,eAA0B,IAAK;MACzC,MAAMC,GAAG,GAAG,IAAI,CAACC,kBAAkB,CAACF,eAAe,EAAE,IAAI,CAACjE,UAAU,CAAC;MACrE,IAAI;QACF,OAAO8D,IAAI,CAACM,QAAQ,CAACF,GAAG,CAAC;MAC3B,CAAC,CAAC,MAAM;QACN,OAAO,KAAK;MACd;IACF,CAAC;IAED,OAAO;MACLvE,IAAI;MACJkE,WAAW;MACXxC,KAAK;MACLyC,IAAI;MACJE;IACF,CAAC;EACH;EAEAG,kBAAkBA,CAChBF,eAA0B,EAC1BjE,UAAwD,EACxD;IACA,MAAMqE,OAAO,GAAG;MAAE,GAAGJ;IAAgB,CAAC;IAEtC,KAAK,MAAMK,WAAW,IAAItE,UAAU,EAAE;MACpC,MAAMuE,YAAY,GAChB,IAAI,CAAChF,aAAa,KAAK9B,aAAa,CAAC+G,EAAE,GACnC3G,sBAAsB,CAACyG,WAAW,CAAC,GACnCA,WAAW;MAEjBf,MAAM,CAACkB,cAAc,CAACJ,OAAO,EAAEE,YAAY,EAAE;QAC3CG,GAAGA,CAAA,EAAG;UACJ,OAAO1E,UAAU,CAACsE,WAAW,CAAC,EAAEN,EAAE,CAACC,eAAe,CAAC;QACrD;MACF,CAAC,CAAC;IACJ;IAEA,OAAOI,OAAO;EAChB;EAEAN,qBAAqBA,CAAC1C,KAA0B,EAAE+B,MAAc,EAAE;IAChE,MAAMpD,UAAU,GAAG1C,eAAe,CAACqH,IAAI,CAACtD,KAAK,CAAC;IAC9C,OAAO+B,MAAM,CAACwB,KAAK,CAAC5E,UAAU,CAAC6E,YAAY,CAAC,CAAC,CAAC;EAChD;EAEAC,OAAOA,CAACC,QAAgB,EAAoB;IAC1C,OAAO,IAAI,CAACxF,aAAa,KAAK9B,aAAa,CAACqD,EAAE,GAC1C,IAAI,CAACrB,KAAK,CAACuF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACrC,IAAI,KAAKoF,QAAQ,CAAC,GACjD,IAAI,CAACtF,KAAK,CAACuF,IAAI,CAAEhD,IAAI,IAAKA,IAAI,CAACT,EAAE,KAAKwD,QAAQ,CAAC;EACrD;;EAEA;AACF;AACA;EACEE,cAAcA,CACZC,OAA2B,EAC3BC,KAAgB,EAChBC,MAA8B,EACjB;IACb,MAAM;MAAEC;IAAM,CAAC,GAAGH,OAAO;IAEzB,MAAMpD,IAAI,GAAGlD,OAAO,CAAC,IAAI,EAAEsG,OAAO,CAAC;;IAEnC;IACA,MAAMI,WAAW,GAAGxD,IAAI,CAACC,IAAI;IAC7B,MAAMwD,SAAS,GAAGzD,IAAI,CAAC0D,YAAY,CAAC,CAAC;;IAErC;IACA,MAAMC,aAAa,GAAG,OAAO,IAAIJ,KAAK;IAEtC,IAAIhB,OAAoB,GAAG;MACzBJ,eAAe,EAAE,CAAC,CAAC;MACnByB,aAAa,EAAE,CAAC,CAAC;MACjB3C,aAAa,EAAE,EAAE;MACjB4C,OAAO,EAAE7D,IAAI,CAAC8D,oBAAoB,CAACV,OAAO,EAAEC,KAAK,CAAC;MAClDA,KAAK;MACLU,KAAK,EAAE,EAAE;MACTT,MAAM;MACNK,aAAa;MACbK,IAAI,EAAE,CAAC,CAAC;MACR1F,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BC,UAAU,EAAE,IAAI,CAACA,UAAU;MAC3BE,eAAe,EAAE,IAAI,CAACA,eAAe;MACrCE,OAAO,EAAE,IAAI,CAACA,OAAO;MACrBC,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BqF,eAAe,EAAEC,kBAAkB,CAACb,KAAK,CAAC;MAC1Cc,sBAAsB,EAAE,IAAI,CAACnG;IAC/B,CAAC;;IAED;IACAuE,OAAO,GAAG6B,mBAAmB,CAAChB,OAAO,EAAEpD,IAAI,EAAEuC,OAAO,CAAC;;IAErD;IACA,IAAI8B,QAAQ,GAAGzH,QAAQ,CAAC,IAAI,EAAE6G,SAAS,CAAC;IAExC,IAAI,CAACa,iBAAiB,CAAC/B,OAAO,CAAC;;IAE/B;IACA,OAAO8B,QAAQ,EAAE;MACf;MACA9B,OAAO,CAACtB,aAAa,CAACzB,IAAI,CAAC6E,QAAQ,CAAC;MAEpC,IAAI,CAACE,qBAAqB,CAAChC,OAAO,EAAE8B,QAAQ,CAAC;MAE7C,IAAI,CAACG,mBAAmB,CAACjC,OAAO,EAAE8B,QAAQ,CAAC;;MAE3C;MACA,IACE,IAAI,CAACI,kBAAkB,CAAClC,OAAO,EAAE8B,QAAQ,CAAC,IAC1CA,QAAQ,CAACpE,IAAI,KAAKuD,WAAW,EAC7B;QACA;MACF;;MAEA;MACAa,QAAQ,GAAGzH,QAAQ,CAAC,IAAI,EAAEyH,QAAQ,CAACK,WAAW,CAACnC,OAAO,CAAC,CAAC;IAC1D;;IAEA;IACAA,OAAO,GAAGoC,iBAAiB,CAACvB,OAAO,EAAEpD,IAAI,EAAEuC,OAAO,CAAC;;IAEnD;IACA,IAAI,CAACqC,WAAW,CAACrC,OAAO,CAAC;IAEzB,OAAOA,OAAO;EAChB;EAEQ+B,iBAAiBA,CAAC/B,OAAoB,EAAE;IAC9C;IACA;IACA;IACA,MAAMsC,UAAU,GAAGpD,MAAM,CAACqD,MAAM,CAAC,CAAC,CAAC,CAAC;IAEpC,KAAK,MAAM9E,IAAI,IAAI,IAAI,CAAC7B,KAAK,EAAE;MAC7B,MAAM;QAAE4C,UAAU;QAAEJ;MAAQ,CAAC,GAAGX,IAAI;MAEpC,IAAI,CAAC/D,WAAW,CAAC0E,OAAO,CAAC,EAAE;QACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAACgE,wBAAwB,CAACF,UAAU,CAChD,CAAC;MACH;IACF;EACF;EAEQN,qBAAqBA,CAC3BhC,OAAoB,EACpBvC,IAAyB,EACzB;IACA,MAAM;MAAEe,UAAU;MAAEJ;IAAQ,CAAC,GAAGX,IAAI;IACpC;;IAEA,IAAI,CAAC/D,WAAW,CAAC0E,OAAO,CAAC,EAAE;MACzBc,MAAM,CAACC,MAAM,CACXa,OAAO,CAACJ,eAAe,EACvBpB,UAAU,CAACgE,wBAAwB,CAACxC,OAAO,CAACc,KAAK,CACnD,CAAC;IACH;EACF;EAEQmB,mBAAmBA,CAACjC,OAAoB,EAAEvC,IAAyB,EAAE;IAC3E;IACA,KAAK,MAAMgF,GAAG,IAAIhF,IAAI,CAACiF,IAAI,EAAE;MAC3B,IAAI,OAAO1C,OAAO,CAACc,KAAK,CAAC2B,GAAG,CAAC,KAAK,WAAW,EAAE;QAC7CzC,OAAO,CAACqB,aAAa,CAACoB,GAAG,CAAC,GAAGzC,OAAO,CAACc,KAAK,CAAC2B,GAAG,CAAC;MACjD;IACF;EACF;EAEQP,kBAAkBA,CAAClC,OAAoB,EAAEvC,IAAyB,EAAE;IAC1E;IACA,MAAMkF,UAAU,GAAGlF,IAAI,CAACe,UAAU,CAACoE,MAAM,CAAChF,MAAM,CAACzD,gBAAgB,CAAC;;IAElE;IACA;IACA;IACA,KAAK,MAAM0I,KAAK,IAAIF,UAAU,EAAE;MAC9B,MAAMhF,IAAI,GAAGkF,KAAK,CAAClF,IAAI;;MAEvB;MACA,IAAIA,IAAI,KAAKmF,SAAS,IAAID,KAAK,CAACzF,IAAI,KAAKpE,aAAa,CAAC+J,UAAU,EAAE;QACjE,MAAMC,gBAAgB,GACpBrF,IAAI,CAACN,KAAK,CAACO,MAAM,CAAEqF,IAAI,IAAKA,IAAI,CAAC/E,SAAS,CAAC,CAACgF,MAAM,GAAG,CAAC;QAExD,IAAIF,gBAAgB,EAAE;UACpB,OAAO,IAAI,CAACG,mBAAmB,CAACnD,OAAO,EAAE6C,KAAK,EAAElF,IAAI,CAAC;QACvD;MACF;IACF;EACF;EAEQwF,mBAAmBA,CACzBnD,OAAoB,EACpB6C,KAAwB,EACxBlF,IAAU,EACV;IACA,MAAM;MAAEiC,eAAe;MAAEkB;IAAM,CAAC,GAAGd,OAAO;IAE1C,MAAMoD,WAAW,GAAGzF,IAAI,CAACN,KAAK,CAC3BO,MAAM,CAAEqF,IAAI,IACXA,IAAI,CAAC/E,SAAS,GACV,IAAI,CAACvC,UAAU,CAACsH,IAAI,CAAC/E,SAAS,CAAC,EAAEyB,EAAE,CAACC,eAAe,CAAC,GACpD,IACN,CAAC,CACApC,GAAG,CAAEyF,IAAI,IAAKA,IAAI,CAACjG,KAAK,CAAC;;IAE5B;IACA,MAAMqG,UAAU,GAAGR,KAAK,CAACS,qBAAqB,CAACxC,KAAK,CAAC;IAErD,IAAIuC,UAAU,KAAKP,SAAS,EAAE;MAC5B,IAAIS,SAAS,GAAG,KAAK;MACrB,MAAMC,OAAO,GAAGC,KAAK,CAACD,OAAO,CAACH,UAAU,CAAC;;MAEzC;MACA;MACA,IAAIG,OAAO,EAAE;QACXD,SAAS,GAAG,CAACF,UAAU,CAACK,KAAK,CAAET,IAAI,IAAKG,WAAW,CAACO,QAAQ,CAACV,IAAI,CAAC,CAAC;MACrE,CAAC,MAAM;QACLM,SAAS,GAAG,CAACH,WAAW,CAACO,QAAQ,CAACN,UAAU,CAAC;MAC/C;MAEA,IAAIE,SAAS,EAAE;QACbvD,OAAO,CAACe,MAAM,KAAK,EAAE;QAErB,MAAMzD,IAAI,GACR,6DAA6D;QAE/D0C,OAAO,CAACe,MAAM,CAAC9D,IAAI,CAAC;UAClBK,IAAI;UACJhC,IAAI,EAAEuH,KAAK,CAACvH,IAAI;UAChBsI,IAAI,EAAE,IAAIf,KAAK,CAACvH,IAAI,EAAE;UACtBoC,IAAI,EAAE,CAAC,IAAImF,KAAK,CAACvH,IAAI,EAAE;QACzB,CAAC,CAAC;MACJ;MAEA,OAAOiI,SAAS;IAClB;EACF;EAEQlB,WAAWA,CAACrC,OAAoB,EAAE;IACxC,KAAK,MAAM;MAAE0C,IAAI;MAAEhF;IAAK,CAAC,IAAIsC,OAAO,CAACtB,aAAa,EAAE;MAClDsB,OAAO,CAACwB,KAAK,CAACvE,IAAI,CAACS,IAAI,CAAC;;MAExB;MACA,IACEsC,OAAO,CAACe,MAAM,EAAE1C,IAAI,CAAC,CAAC;QAAE/C,IAAI;QAAEoC;MAAK,CAAC,KAAK;QACvC,OAAOgF,IAAI,CAACiB,QAAQ,CAACrI,IAAI,CAAC,IAAIoH,IAAI,CAACrE,IAAI,CAAEoE,GAAG,IAAK/E,IAAI,CAACiG,QAAQ,CAAClB,GAAG,CAAC,CAAC;MACtE,CAAC,CAAC,EACF;QACA;MACF;IACF;EACF;EAEAoB,gBAAgBA,CAACC,WAAmB,EAA4B;IAC9D,OAAO,IAAI,CAAC3H,iBAAiB,CAACkE,GAAG,CAACyD,WAAW,CAAC;EAChD;EAEAC,WAAWA,CAACC,MAAc,EAAoB;IAC5C,OAAO,IAAI,CAAC/H,YAAY,CAACoE,GAAG,CAAC2D,MAAM,CAAC;EACtC;;EAEA;AACF;AACA;AACA;AACA;EACEC,gBAAgBA,CAAChE,WAAmB,EAAkC;IACpE,OAAO,IAAI,CAAC9E,GAAG,CAACQ,UAAU,CACvBiC,MAAM,CAACjE,oBAAoB,CAAC,CAC5BgH,IAAI,CAAEzC,SAAS,IAAKA,SAAS,CAAChB,EAAE,KAAK+C,WAAW,CAAC;EACtD;AACF;;AAEA;AACA;AACA;AACA,SAAS4B,mBAAmBA,CAC1BhB,OAA2B,EAC3BpD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAExB;EAAW,CAAC,GAAGf,IAAI;EAC3B,MAAM;IAAE6D,OAAO;IAAER;EAAM,CAAC,GAAGd,OAAO;EAElC,MAAM;IAAEkE;EAAO,CAAC,GAAGzG,IAAI,CAAC0G,aAAa,CAACtD,OAAO,CAAC;;EAE9C;EACA,IACE,CAACA,OAAO,CAACS,OAAO,IACf4C,MAAM,IACL,CAAC,CAACrJ,UAAU,CAACuJ,QAAQ,EAAEvJ,UAAU,CAACwJ,WAAW,CAAC,CAACV,QAAQ,CAACO,MAAM,CAAC,IAC/D,CAACA,MAAM,CAACI,UAAU,CAACzJ,UAAU,CAAC0J,QAAQ,CAAE,EAC1C;IACA,OAAOvE,OAAO;EAChB;;EAEA;EACA;EACA;EACA,MAAMwE,MAAM,GAAG;IAAE,GAAG3D,OAAO,CAACS;EAAQ,CAAC;EACrC9C,UAAU,CAACoE,MAAM,CAAC5E,OAAO,CAAE6E,KAAK,IAAK;IACnC,IACEA,KAAK,CAACzF,IAAI,KAAKpE,aAAa,CAACyL,eAAe,IAC5C,EAAE5B,KAAK,CAACvH,IAAI,IAAIkJ,MAAM,CAAC,EACvB;MACAA,MAAM,CAAC3B,KAAK,CAACvH,IAAI,CAAC,GAAGwH,SAAS;IAChC;EACF,CAAC,CAAC;EAEF,MAAM;IAAE9F,KAAK;IAAE+D;EAAO,CAAC,GAAGvC,UAAU,CAAC5B,QAAQ,CAAC;IAC5C,GAAG0E,OAAO;IACV,GAAGkD;EACL,CAAC,CAAC;;EAEF;EACA,MAAME,SAAS,GAAGjH,IAAI,CAACkH,qBAAqB,CAAC9D,OAAO,EAAEC,KAAK,EAAE9D,KAAK,CAAC;EAEnE,OAAO;IACL,GAAGgD,OAAO;IACVsB,OAAO,EAAExG,KAAK,CAACwG,OAAO,EAAEtE,KAAK,CAAC;IAC9B8D,KAAK,EAAEhG,KAAK,CAACgG,KAAK,EAAE4D,SAAS,CAAC;IAC9B3D;EACF,CAAC;AACH;;AAEA;AACA;AACA;AACA,SAASqB,iBAAiBA,CACxBvB,OAA2B,EAC3BpD,IAAyB,EACzBuC,OAAoB,EACP;EACb,MAAM;IAAEe,MAAM,GAAG,EAAE;IAAErC,aAAa;IAAE2C;EAAc,CAAC,GAAGrB,OAAO;;EAE7D;EACA,MAAM4E,aAAa,GAAGlG,aAAa,CAACd,MAAM,CACvCiH,YAAY,IAAKA,YAAY,KAAKpH,IACrC,CAAC;;EAED;EACA,MAAM;IAAEX;EAAM,CAAC,GAAGW,IAAI,CAACqH,KAAK,CACzBrG,kBAAkB,CAACmG,aAAa,CAAC,CACjChI,QAAQ,CAACyE,aAAa,EAAE;IAAE,GAAG1G,IAAI;IAAEoK,YAAY,EAAE;EAAK,CAAC,CAAC;;EAE3D;EACA,IAAIjI,KAAK,EAAE;IACT,MAAMkI,WAAW,GAAGlI,KAAK,CAACmI,OAAO,CAACzH,GAAG,CAAClD,QAAQ,CAAC;IAC/C,OAAO;MAAE,GAAG0F,OAAO;MAAEe,MAAM,EAAEA,MAAM,CAAClC,MAAM,CAACmG,WAAW;IAAE,CAAC;EAC3D;EAEA,OAAOhF,OAAO;AAChB;AAEA,SAAS2B,kBAAkBA,CAACb,KAAgB,EAAU;EACpD,IACE,CAACA,KAAK,CAACoE,mBAAmB,IAC1B,OAAOpE,KAAK,CAACoE,mBAAmB,KAAK,QAAQ,EAC7C;IACA,MAAMC,KAAK,CAAC,0CAA0C,CAAC;EACzD;EAEA,OAAOrE,KAAK,CAACoE,mBAAmB;AAClC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","SchemaVersion","convertConditionWrapperFromV2","formDefinitionSchema","formDefinitionV2Schema","generateConditionAlias","hasComponents","hasRepeater","isConditionWrapperV2","yesNoListId","yesNoListName","add","format","Parser","joi","createLogger","hasListFormField","todayAsDateOnly","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","logger","FormModel","engine","schemaVersion","def","lists","sections","name","values","basePath","versionNumber","ordnanceSurveyApiKey","conditions","pages","services","controllers","pageDefMap","listDefMap","listDefIdMap","componentDefMap","componentDefIdMap","pageMap","componentMap","constructor","options","schema","V1","warn","result","validate","abortEarly","error","structuredClone","value","push","id","title","type","items","text","Map","map","page","path","list","filter","flatMap","components","component","forEach","conditionDef","condition","makeCondition","pageDef","some","controller","Status","collection","makeFilteredSchema","relevantPages","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","conditionId","propertyName","V2","defineProperty","get","from","parse","toExpression","getList","nameOrId","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","submittedVersionNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","emptyState","freeze","getContextValueFromState","key","keys","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","getComponentById","componentId","getListById","listId","getConditionById","action","getFormParams","Validate","SaveAndExit","startsWith","External","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n SchemaVersion,\n convertConditionWrapperFromV2,\n formDefinitionSchema,\n formDefinitionV2Schema,\n generateConditionAlias,\n hasComponents,\n hasRepeater,\n isConditionWrapperV2,\n yesNoListId,\n yesNoListName,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionWrapperV2,\n type ConditionsModelData,\n type DateUnits,\n type Engine,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add, format } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval-fork'\nimport joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {} from '~/src/server/plugins/engine/components/YesNoField.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nconst logger = createLogger()\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n schemaVersion: SchemaVersion\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\n pageDefMap: Map<string, Page>\n\n listDefMap: Map<string, List>\n listDefIdMap: Map<string, List>\n\n componentDefMap: Map<string, ComponentDef>\n componentDefIdMap: Map<string, ComponentDef>\n\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n\n constructor(\n def: typeof this.def,\n options: {\n basePath: string\n versionNumber?: number\n ordnanceSurveyApiKey?: string\n },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n let schema = formDefinitionV2Schema\n\n if (!def.schema || def.schema === SchemaVersion.V1) {\n logger.warn(\n `[DEPRECATION NOTICE] Form \"${def.name}\" constructed with legacy V1 schema. See https://defra.github.io/forms-engine-plugin/schemas/form-definition-schema.html.`\n )\n schema = formDefinitionSchema\n }\n\n const result = schema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n id: '02900d42-83d1-4c72-a719-c4e8228952fa',\n text: 'Yes',\n value: true\n },\n {\n id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.schemaVersion = def.schema ?? SchemaVersion.V1\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.versionNumber = options.versionNumber\n this.ordnanceSurveyApiKey = options.ordnanceSurveyApiKey\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.listDefIdMap = new Map(\n def.lists\n .filter((list) => list.id) // Skip lists without an ID\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n .map((list) => [list.id as string, list])\n )\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n this.componentDefIdMap = new Map(\n def.pages.filter(hasComponents).flatMap((page) =>\n page.components\n .filter((component) => component.id) // Skip components without an ID\n .map((component) => {\n // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style\n return [component.id as string, component]\n })\n )\n )\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(\n isConditionWrapperV2(conditionDef)\n ? convertConditionWrapperFromV2(conditionDef, this)\n : conditionDef\n )\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n // The time element must be stripped (hence using startOfDay() which has no time element),\n // then formatted as YYYY-MM-DD otherwise we can hit time element and BST issues giving the\n // wrong date to compare against.\n // Do not use .toISOString() to format the date as that introduces BST errors.\n return format(\n add(todayAsDateOnly(), { [timeUnit]: timePeriod }),\n 'yyyy-MM-dd'\n )\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const conditionId in conditions) {\n const propertyName =\n this.schemaVersion === SchemaVersion.V2\n ? generateConditionAlias(conditionId)\n : conditionId\n\n Object.defineProperty(context, propertyName, {\n get() {\n return conditions[conditionId]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(nameOrId: string): List | undefined {\n return this.schemaVersion === SchemaVersion.V1\n ? this.lists.find((list) => list.name === nameOrId)\n : this.lists.find((list) => list.id === nameOrId)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state),\n submittedVersionNumber: this.versionNumber\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // Initialise `evaluationState` for all keys using empty state.\n // This is because the current condition evaluation library (eval-expr)\n // will throw if an expression uses a key that is undefined.\n const emptyState = Object.freeze({})\n\n for (const page of this.pages) {\n const { collection, pageDef } = page\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(emptyState)\n )\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n context.errors ??= []\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n\n getComponentById(componentId: string): ComponentDef | undefined {\n return this.componentDefIdMap.get(componentId)\n }\n\n getListById(listId: string): List | undefined {\n return this.listDefIdMap.get(listId)\n }\n\n /**\n * Returns a condition by its ID. O(n) lookup time.\n * @param conditionId\n * @returns\n */\n getConditionById(conditionId: string): ConditionWrapperV2 | undefined {\n return this.def.conditions\n .filter(isConditionWrapperV2)\n .find((condition) => condition.id === conditionId)\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (\n !request.payload ||\n (action &&\n ![FormAction.Validate, FormAction.SaveAndExit].includes(action) &&\n !action.startsWith(FormAction.External))\n ) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ConditionWrapper, type Section } from '@defra/forms-model';
|
|
2
|
-
import { type Expression } from 'expr-eval';
|
|
2
|
+
import { type Expression } from 'expr-eval-fork';
|
|
3
3
|
import { type Field } from '~/src/server/plugins/engine/components/helpers/components.js';
|
|
4
4
|
import { type RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js';
|
|
5
5
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/models/types.ts"],"sourcesContent":["import {\n type ConditionWrapper,\n type FormComponentsDef,\n type Section\n} from '@defra/forms-model'\nimport { type Expression } from 'expr-eval'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FormState,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\n\nexport type ExecutableCondition = ConditionWrapper & {\n expr: Expression\n fn: (evaluationState: FormState) => boolean\n}\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface DetailItemBase {\n /**\n * Name of the component defined in the JSON\n * @see {@link FormComponentsDef.name}\n */\n name: string\n\n /**\n * Field label, used for change link visually hidden text\n * @see {@link FormComponentsDef.title}\n */\n label: string\n\n /**\n * Field change link\n */\n href: string\n\n /**\n * Form submission state (or repeat state for sub items)\n */\n state: FormState\n\n /**\n * Field submission state error, used to flag unanswered questions\n * Shown as 'Complete all unanswered questions before submitting the form'\n */\n error?: FormSubmissionError\n}\n\nexport interface DetailItemField extends DetailItemBase {\n /**\n * Field page controller instance\n */\n page: Exclude<PageControllerClass, RepeatPageController>\n\n /**\n * Check answers summary list key\n * For example, 'Date of birth'\n */\n title: string\n\n /**\n * Check answers summary list value, formatted by {@link getAnswer}\n * For example, date fields formatted as '25 December 2022'\n */\n value: string\n\n /**\n * Field component instance\n */\n field: Field\n}\n\nexport interface DetailItemRepeat extends DetailItemBase {\n /**\n * Repeat page controller instance\n */\n page: RepeatPageController\n\n /**\n * Check answers summary list key\n * For example, 'Pizza' or 'Pizza added'\n */\n title: string\n\n /**\n * Check answers summary list value\n * For example, 'You added 2 Pizzas'\n */\n value: string\n\n /**\n * Repeater field detail items\n */\n subItems: DetailItemField[][]\n}\n\nexport type DetailItem = DetailItemField | DetailItemRepeat\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface Detail {\n name?: Section['name']\n title?: Section['title']\n items: DetailItem[]\n}\n"],"mappings":"","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../../src/server/plugins/engine/models/types.ts"],"sourcesContent":["import {\n type ConditionWrapper,\n type FormComponentsDef,\n type Section\n} from '@defra/forms-model'\nimport { type Expression } from 'expr-eval-fork'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FormState,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\n\nexport type ExecutableCondition = ConditionWrapper & {\n expr: Expression\n fn: (evaluationState: FormState) => boolean\n}\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface DetailItemBase {\n /**\n * Name of the component defined in the JSON\n * @see {@link FormComponentsDef.name}\n */\n name: string\n\n /**\n * Field label, used for change link visually hidden text\n * @see {@link FormComponentsDef.title}\n */\n label: string\n\n /**\n * Field change link\n */\n href: string\n\n /**\n * Form submission state (or repeat state for sub items)\n */\n state: FormState\n\n /**\n * Field submission state error, used to flag unanswered questions\n * Shown as 'Complete all unanswered questions before submitting the form'\n */\n error?: FormSubmissionError\n}\n\nexport interface DetailItemField extends DetailItemBase {\n /**\n * Field page controller instance\n */\n page: Exclude<PageControllerClass, RepeatPageController>\n\n /**\n * Check answers summary list key\n * For example, 'Date of birth'\n */\n title: string\n\n /**\n * Check answers summary list value, formatted by {@link getAnswer}\n * For example, date fields formatted as '25 December 2022'\n */\n value: string\n\n /**\n * Field component instance\n */\n field: Field\n}\n\nexport interface DetailItemRepeat extends DetailItemBase {\n /**\n * Repeat page controller instance\n */\n page: RepeatPageController\n\n /**\n * Check answers summary list key\n * For example, 'Pizza' or 'Pizza added'\n */\n title: string\n\n /**\n * Check answers summary list value\n * For example, 'You added 2 Pizzas'\n */\n value: string\n\n /**\n * Repeater field detail items\n */\n subItems: DetailItemField[][]\n}\n\nexport type DetailItem = DetailItemField | DetailItemRepeat\n\n/**\n * Used to render a row on a Summary List (check your answers)\n */\nexport interface Detail {\n name?: Section['name']\n title?: Section['title']\n items: DetailItem[]\n}\n"],"mappings":"","ignoreList":[]}
|
|
@@ -12,22 +12,22 @@
|
|
|
12
12
|
}) }}
|
|
13
13
|
{% endif %}
|
|
14
14
|
|
|
15
|
-
<div class="
|
|
15
|
+
<div class="govuk-grid-row">
|
|
16
16
|
{% for item in component.model.items %}
|
|
17
|
-
<div class="
|
|
17
|
+
<div class="govuk-grid-column-one-third">
|
|
18
18
|
{{ govukInput({
|
|
19
19
|
id: item.id,
|
|
20
20
|
name: item.name,
|
|
21
21
|
label: {
|
|
22
|
-
text: item.label
|
|
23
|
-
classes: "govuk-label--s"
|
|
22
|
+
text: item.label
|
|
24
23
|
},
|
|
25
24
|
classes: item.classes,
|
|
26
25
|
value: item.value,
|
|
27
26
|
type: inputType,
|
|
28
27
|
inputmode: inputMode,
|
|
29
28
|
prefix: item.prefix,
|
|
30
|
-
suffix: item.suffix
|
|
29
|
+
suffix: item.suffix,
|
|
30
|
+
errorMessage: item.errorMessage
|
|
31
31
|
}) }}
|
|
32
32
|
</div>
|
|
33
33
|
{% endfor %}
|
|
@@ -41,13 +41,21 @@
|
|
|
41
41
|
{% endif %}
|
|
42
42
|
{% endset %}
|
|
43
43
|
|
|
44
|
-
{
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
html: fieldsetHtml
|
|
51
|
-
}) }}
|
|
52
|
-
{% endmacro %}
|
|
44
|
+
{% set hasErrors = false %}
|
|
45
|
+
{% for item in component.model.items %}
|
|
46
|
+
{% if item.errorMessage %}
|
|
47
|
+
{% set hasErrors = true %}
|
|
48
|
+
{% endif %}
|
|
49
|
+
{% endfor %}
|
|
53
50
|
|
|
51
|
+
<div class="govuk-form-group {{ "govuk-form-group--error" if (hasErrors or component.model.errors) }}">
|
|
52
|
+
{{ govukFieldset({
|
|
53
|
+
legend: {
|
|
54
|
+
text: component.model.fieldset.legend.text,
|
|
55
|
+
classes: component.model.fieldset.legend.classes,
|
|
56
|
+
isPageHeading: false
|
|
57
|
+
},
|
|
58
|
+
html: fieldsetHtml
|
|
59
|
+
}) }}
|
|
60
|
+
</div>
|
|
61
|
+
{% endmacro %}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.14",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"convict": "^6.2.4",
|
|
96
96
|
"date-fns": "^4.1.0",
|
|
97
97
|
"dotenv": "^17.2.1",
|
|
98
|
-
"expr-eval": "^
|
|
98
|
+
"expr-eval-fork": "^3.0.0",
|
|
99
99
|
"govuk-frontend": "^5.11.1",
|
|
100
100
|
"hapi-pino": "^12.1.0",
|
|
101
101
|
"hapi-pulse": "^3.0.1",
|
|
@@ -168,15 +168,17 @@ pages:
|
|
|
168
168
|
schema: {}
|
|
169
169
|
type: EastingNorthingField
|
|
170
170
|
title: Easting and northing
|
|
171
|
+
shortDescription: Location
|
|
171
172
|
hint:
|
|
172
173
|
This is an Easting and Northing component
|
|
173
174
|
- name: seTThb
|
|
174
175
|
options: {}
|
|
175
176
|
schema: {}
|
|
176
177
|
type: LatLongField
|
|
177
|
-
title:
|
|
178
|
+
title: Latitude and longitude
|
|
179
|
+
shortDescription: Position
|
|
178
180
|
hint:
|
|
179
|
-
This is an
|
|
181
|
+
This is an Latitude and Longitude component
|
|
180
182
|
- name: bhjloS
|
|
181
183
|
options: {}
|
|
182
184
|
schema: {}
|
|
@@ -335,7 +335,7 @@ describe('EastingNorthingField', () => {
|
|
|
335
335
|
expect(instructionText).toContain('meters')
|
|
336
336
|
})
|
|
337
337
|
|
|
338
|
-
it('
|
|
338
|
+
it('handles errors when component has validation errors', () => {
|
|
339
339
|
const payload = getFormData({
|
|
340
340
|
easting: '',
|
|
341
341
|
northing: ''
|
|
@@ -352,15 +352,21 @@ describe('EastingNorthingField', () => {
|
|
|
352
352
|
|
|
353
353
|
const viewModel = field.getViewModel(payload, errors)
|
|
354
354
|
|
|
355
|
+
// Check that error is passed to the viewModel
|
|
356
|
+
expect(viewModel.errors).toEqual(errors)
|
|
357
|
+
|
|
358
|
+
// Items should be present with their basic structure
|
|
355
359
|
expect(viewModel.items?.[0]).toEqual(
|
|
356
360
|
expect.objectContaining({
|
|
357
|
-
|
|
361
|
+
id: 'myComponent__easting',
|
|
362
|
+
name: 'myComponent__easting'
|
|
358
363
|
})
|
|
359
364
|
)
|
|
360
365
|
|
|
361
366
|
expect(viewModel.items?.[1]).toEqual(
|
|
362
367
|
expect.objectContaining({
|
|
363
|
-
|
|
368
|
+
id: 'myComponent__northing',
|
|
369
|
+
name: 'myComponent__northing'
|
|
364
370
|
})
|
|
365
371
|
)
|
|
366
372
|
})
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type EastingNorthingFieldComponent
|
|
4
4
|
} from '@defra/forms-model'
|
|
5
5
|
import { type LanguageMessages, type ObjectSchema } from 'joi'
|
|
6
|
+
import lowerFirst from 'lodash/lowerFirst.js'
|
|
6
7
|
|
|
7
8
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
8
9
|
import {
|
|
@@ -57,22 +58,22 @@ export class EastingNorthingField extends FormComponent {
|
|
|
57
58
|
convertToLanguageMessages({
|
|
58
59
|
'any.required': messageTemplate.objectMissing,
|
|
59
60
|
'number.base': messageTemplate.objectMissing,
|
|
60
|
-
'number.min': `{{#label}} for ${this.
|
|
61
|
-
'number.max': `{{#label}} for ${this.
|
|
62
|
-
'number.precision': `{{#label}} for ${this.
|
|
63
|
-
'number.integer': `{{#label}} for ${this.
|
|
64
|
-
'number.unsafe': `{{#label}} for ${this.
|
|
61
|
+
'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${eastingMax}`,
|
|
62
|
+
'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${eastingMin} and {{#limit}}`,
|
|
63
|
+
'number.precision': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`,
|
|
64
|
+
'number.integer': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`,
|
|
65
|
+
'number.unsafe': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`
|
|
65
66
|
})
|
|
66
67
|
|
|
67
68
|
const northingValidationMessages: LanguageMessages =
|
|
68
69
|
convertToLanguageMessages({
|
|
69
70
|
'any.required': messageTemplate.objectMissing,
|
|
70
71
|
'number.base': messageTemplate.objectMissing,
|
|
71
|
-
'number.min': `{{#label}} for ${this.
|
|
72
|
-
'number.max': `{{#label}} for ${this.
|
|
73
|
-
'number.precision': `{{#label}} for ${this.
|
|
74
|
-
'number.integer': `{{#label}} for ${this.
|
|
75
|
-
'number.unsafe': `{{#label}} for ${this.
|
|
72
|
+
'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${northingMax}`,
|
|
73
|
+
'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${northingMin} and {{#limit}}`,
|
|
74
|
+
'number.precision': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`,
|
|
75
|
+
'number.integer': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`,
|
|
76
|
+
'number.unsafe': `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`
|
|
76
77
|
})
|
|
77
78
|
|
|
78
79
|
this.collection = new ComponentCollection(
|
|
@@ -198,11 +199,11 @@ export class EastingNorthingField extends FormComponent {
|
|
|
198
199
|
advancedSettingsErrors: [
|
|
199
200
|
{
|
|
200
201
|
type: 'eastingMin',
|
|
201
|
-
template: `Easting for [short description] must be between
|
|
202
|
+
template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
|
|
202
203
|
},
|
|
203
204
|
{
|
|
204
205
|
type: 'eastingMax',
|
|
205
|
-
template: `Easting for [short description] must be between
|
|
206
|
+
template: `Easting for [short description] must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
|
|
206
207
|
},
|
|
207
208
|
{
|
|
208
209
|
type: 'northingMin',
|
|
@@ -324,7 +324,7 @@ describe('LatLongField', () => {
|
|
|
324
324
|
expect(instructionText).toContain('decimal')
|
|
325
325
|
})
|
|
326
326
|
|
|
327
|
-
it('
|
|
327
|
+
it('handles errors when component has validation errors', () => {
|
|
328
328
|
const payload = getFormData({
|
|
329
329
|
latitude: '',
|
|
330
330
|
longitude: ''
|
|
@@ -341,15 +341,21 @@ describe('LatLongField', () => {
|
|
|
341
341
|
|
|
342
342
|
const viewModel = field.getViewModel(payload, errors)
|
|
343
343
|
|
|
344
|
+
// Check that error is passed to the viewModel
|
|
345
|
+
expect(viewModel.errors).toEqual(errors)
|
|
346
|
+
|
|
347
|
+
// Items should be present with their basic structure
|
|
344
348
|
expect(viewModel.items?.[0]).toEqual(
|
|
345
349
|
expect.objectContaining({
|
|
346
|
-
|
|
350
|
+
id: 'myComponent__latitude',
|
|
351
|
+
name: 'myComponent__latitude'
|
|
347
352
|
})
|
|
348
353
|
)
|
|
349
354
|
|
|
350
355
|
expect(viewModel.items?.[1]).toEqual(
|
|
351
356
|
expect.objectContaining({
|
|
352
|
-
|
|
357
|
+
id: 'myComponent__longitude',
|
|
358
|
+
name: 'myComponent__longitude'
|
|
353
359
|
})
|
|
354
360
|
)
|
|
355
361
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ComponentType, type LatLongFieldComponent } from '@defra/forms-model'
|
|
2
2
|
import { type LanguageMessages, type ObjectSchema } from 'joi'
|
|
3
|
+
import lowerFirst from 'lodash/lowerFirst.js'
|
|
3
4
|
|
|
4
5
|
import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'
|
|
5
6
|
import {
|
|
@@ -60,16 +61,16 @@ export class LatLongField extends FormComponent {
|
|
|
60
61
|
|
|
61
62
|
const latitudeMessages: LanguageMessages = convertToLanguageMessages({
|
|
62
63
|
...customValidationMessages,
|
|
63
|
-
'number.base': `Enter a valid latitude for ${this.
|
|
64
|
-
'number.min': `Latitude for ${this.
|
|
65
|
-
'number.max': `Latitude for ${this.
|
|
64
|
+
'number.base': `Enter a valid latitude for ${lowerFirst(this.label)} like 51.519450`,
|
|
65
|
+
'number.min': `Latitude for ${lowerFirst(this.label)} must be between ${latitudeMin} and ${latitudeMax}`,
|
|
66
|
+
'number.max': `Latitude for ${lowerFirst(this.label)} must be between ${latitudeMin} and ${latitudeMax}`
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
const longitudeMessages: LanguageMessages = convertToLanguageMessages({
|
|
69
70
|
...customValidationMessages,
|
|
70
|
-
'number.base': `Enter a valid longitude for ${this.
|
|
71
|
-
'number.min': `Longitude for ${this.
|
|
72
|
-
'number.max': `Longitude for ${this.
|
|
71
|
+
'number.base': `Enter a valid longitude for ${lowerFirst(this.label)} like -0.127758`,
|
|
72
|
+
'number.min': `Longitude for ${lowerFirst(this.label)} must be between ${longitudeMin} and ${longitudeMax}`,
|
|
73
|
+
'number.max': `Longitude for ${lowerFirst(this.label)} must be between ${longitudeMin} and ${longitudeMax}`
|
|
73
74
|
})
|
|
74
75
|
|
|
75
76
|
this.collection = new ComponentCollection(
|
|
@@ -71,7 +71,7 @@ describe('LocationFieldHelpers', () => {
|
|
|
71
71
|
expect(instructionText).toContain('decimal format')
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
-
it('should
|
|
74
|
+
it('should handle component-level errors correctly', () => {
|
|
75
75
|
const def: LatLongFieldComponent = {
|
|
76
76
|
title: 'Example lat long',
|
|
77
77
|
name: 'myComponent',
|
|
@@ -99,20 +99,26 @@ describe('LocationFieldHelpers', () => {
|
|
|
99
99
|
|
|
100
100
|
const viewModel = field.getViewModel(payload, errors)
|
|
101
101
|
|
|
102
|
+
// Check that errors are passed to the viewModel
|
|
103
|
+
expect(viewModel.errors).toEqual(errors)
|
|
104
|
+
|
|
105
|
+
// Items should still have their structure
|
|
102
106
|
expect(viewModel.items[0]).toEqual(
|
|
103
107
|
expect.objectContaining({
|
|
104
|
-
|
|
108
|
+
id: 'myComponent__latitude',
|
|
109
|
+
name: 'myComponent__latitude'
|
|
105
110
|
})
|
|
106
111
|
)
|
|
107
112
|
|
|
108
113
|
expect(viewModel.items[1]).toEqual(
|
|
109
114
|
expect.objectContaining({
|
|
110
|
-
|
|
115
|
+
id: 'myComponent__longitude',
|
|
116
|
+
name: 'myComponent__longitude'
|
|
111
117
|
})
|
|
112
118
|
)
|
|
113
119
|
})
|
|
114
120
|
|
|
115
|
-
it('should
|
|
121
|
+
it('should pass error messages to individual items when subfield has errors', () => {
|
|
116
122
|
const def: LatLongFieldComponent = {
|
|
117
123
|
title: 'Example lat long',
|
|
118
124
|
name: 'myComponent',
|
|
@@ -140,9 +146,12 @@ describe('LocationFieldHelpers', () => {
|
|
|
140
146
|
|
|
141
147
|
const viewModel = field.getViewModel(payload, errors)
|
|
142
148
|
|
|
149
|
+
// Check that errorMessage is passed through to the item
|
|
143
150
|
expect(viewModel.items[0]).toEqual(
|
|
144
151
|
expect.objectContaining({
|
|
145
|
-
|
|
152
|
+
errorMessage: {
|
|
153
|
+
text: 'Invalid latitude'
|
|
154
|
+
}
|
|
146
155
|
})
|
|
147
156
|
)
|
|
148
157
|
})
|
|
@@ -30,12 +30,9 @@ export function getLocationFieldViewModel(
|
|
|
30
30
|
payload: FormPayload,
|
|
31
31
|
errors?: FormSubmissionError[]
|
|
32
32
|
) {
|
|
33
|
-
const { collection
|
|
33
|
+
const { collection } = component
|
|
34
34
|
const { fieldset: existingFieldset, label } = viewModel
|
|
35
35
|
|
|
36
|
-
// Check for component errors only
|
|
37
|
-
const hasError = errors?.some((error) => error.name === name)
|
|
38
|
-
|
|
39
36
|
// Use the component collection to generate the subitems
|
|
40
37
|
const items: DateInputItem[] = collection
|
|
41
38
|
.getViewModel(payload, errors)
|
|
@@ -46,10 +43,6 @@ export function getLocationFieldViewModel(
|
|
|
46
43
|
label.toString = () => label.text // Use string labels
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
if (hasError || errorMessage) {
|
|
50
|
-
classes = `${classes ?? ''} govuk-input--error`.trim()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
46
|
// Allow any `toString()`-able value so non-numeric
|
|
54
47
|
// values are shown alongside their error messages
|
|
55
48
|
if (!isFormValue(value)) {
|
|
@@ -64,7 +57,8 @@ export function getLocationFieldViewModel(
|
|
|
64
57
|
value,
|
|
65
58
|
classes,
|
|
66
59
|
prefix,
|
|
67
|
-
suffix
|
|
60
|
+
suffix,
|
|
61
|
+
errorMessage
|
|
68
62
|
}
|
|
69
63
|
})
|
|
70
64
|
|