diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b92e7592..dba540a2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,22 @@ If you aren't using the Nunjucks macro, locate the SVG code for the existing log ``` +#### Add attributes to component form group wrappers + +You can now add attributes to the form group wrapper for all components with form fields. + +```njk +govukRadios({ + formGroup: { + attributes: { + "data-attribute": "value" + } + } +}) +``` + +This change was introduced in [pull request #4565: Allow `attributes` option on form groups](https://github.com/alphagov/govuk-frontend/pull/4565). + #### Use tabular numbers with the `govuk-font-tabular-numbers` mixin You can now use tabular numbers in your authored Sass by including the new `govuk-font-tabular-numbers` mixin. @@ -88,6 +104,8 @@ We've made fixes to GOV.UK Frontend in the following pull requests: - [#4157: Dynamically position text within input prefixes and suffixes](https://github.com/alphagov/govuk-frontend/pull/4157) - [#4150: Header menu button position refactor](https://github.com/alphagov/govuk-frontend/pull/4150) +- [#4093: Refactor positioning of radios and checkboxes](https://github.com/alphagov/govuk-frontend/pull/4093) +- [#4562: Use CSS custom properties for component `matchMedia()`](https://github.com/alphagov/govuk-frontend/pull/4562) ## 5.0.0 (Breaking release) diff --git a/package-lock.json b/package-lock.json index 5a530104cb..cdd6a50ca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,8 +49,8 @@ "postcss-scss": "^4.0.9", "prettier": "^3.1.1", "standard": "^17.1.0", - "stylelint": "^15.11.0", - "stylelint-config-gds": "^1.1.1", + "stylelint": "^16.1.0", + "stylelint-config-gds": "^2.0.0", "stylelint-order": "^6.0.4", "typescript": "^5.3.3" }, @@ -1887,9 +1887,9 @@ "dev": true }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz", - "integrity": "sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz", + "integrity": "sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==", "dev": true, "funding": [ { @@ -1905,13 +1905,13 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.0" + "@csstools/css-tokenizer": "^2.2.3" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz", - "integrity": "sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz", + "integrity": "sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==", "dev": true, "funding": [ { @@ -1928,9 +1928,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", - "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz", + "integrity": "sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ==", "dev": true, "funding": [ { @@ -1946,14 +1946,14 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" + "@csstools/css-parser-algorithms": "^2.5.0", + "@csstools/css-tokenizer": "^2.2.3" } }, "node_modules/@csstools/selector-specificity": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", - "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz", + "integrity": "sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==", "dev": true, "funding": [ { @@ -6885,13 +6885,13 @@ } }, "node_modules/browser-sync": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.29.3.tgz", - "integrity": "sha512-NiM38O6XU84+MN+gzspVmXV2fTOoe+jBqIBx3IBdhZrdeURr6ZgznJr/p+hQ+KzkKEiGH/GcC4SQFSL0jV49bg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-3.0.2.tgz", + "integrity": "sha512-PC9c7aWJFVR4IFySrJxOqLwB9ENn3/TaXCXtAa0SzLwocLN3qMjN+IatbjvtCX92BjNXsY6YWg9Eb7F3Wy255g==", "dev": true, "dependencies": { - "browser-sync-client": "^2.29.3", - "browser-sync-ui": "^2.29.3", + "browser-sync-client": "^3.0.2", + "browser-sync-ui": "^3.0.2", "bs-recipes": "1.3.4", "chalk": "4.1.2", "chokidar": "^3.5.1", @@ -6905,7 +6905,6 @@ "fs-extra": "3.0.1", "http-proxy": "^1.18.1", "immutable": "^3", - "localtunnel": "^2.0.1", "micromatch": "^4.0.2", "opn": "5.3.0", "portscanner": "2.2.0", @@ -6928,9 +6927,9 @@ } }, "node_modules/browser-sync-client": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.29.3.tgz", - "integrity": "sha512-4tK5JKCl7v/3aLbmCBMzpufiYLsB1+UI+7tUXCCp5qF0AllHy/jAqYu6k7hUF3hYtlClKpxExWaR+rH+ny07wQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-3.0.2.tgz", + "integrity": "sha512-tBWdfn9L0wd2Pjuz/NWHtNEKthVb1Y67vg8/qyGNtCqetNz5lkDkFnrsx5UhPNPYUO8vci50IWC/BhYaQskDiQ==", "dev": true, "dependencies": { "etag": "1.8.1", @@ -6948,9 +6947,9 @@ "dev": true }, "node_modules/browser-sync-ui": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.29.3.tgz", - "integrity": "sha512-kBYOIQjU/D/3kYtUIJtj82e797Egk1FB2broqItkr3i4eF1qiHbFCG6srksu9gWhfmuM/TNG76jMfzAdxEPakg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-3.0.2.tgz", + "integrity": "sha512-V3FwWAI+abVbFLTyJjXJlCMBwjc3GXf/BPGfwO2fMFACWbIGW9/4SrBOFYEOOtqzCjQE0Di+U3VIb7eES4omNA==", "dev": true, "dependencies": { "async-each-series": "0.1.1", @@ -10482,9 +10481,9 @@ } }, "node_modules/engine.io-client": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz", - "integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -12347,9 +12346,9 @@ "devOptional": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "devOptional": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -13070,9 +13069,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { @@ -14094,6 +14093,21 @@ "node": ">=6" } }, + "node_modules/govuk-prototype-kit/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/govuk-prototype-kit/node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -14118,6 +14132,141 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/govuk-prototype-kit/node_modules/browser-sync": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.29.3.tgz", + "integrity": "sha512-NiM38O6XU84+MN+gzspVmXV2fTOoe+jBqIBx3IBdhZrdeURr6ZgznJr/p+hQ+KzkKEiGH/GcC4SQFSL0jV49bg==", + "dev": true, + "dependencies": { + "browser-sync-client": "^2.29.3", + "browser-sync-ui": "^2.29.3", + "bs-recipes": "1.3.4", + "chalk": "4.1.2", + "chokidar": "^3.5.1", + "connect": "3.6.6", + "connect-history-api-fallback": "^1", + "dev-ip": "^1.0.1", + "easy-extender": "^2.3.4", + "eazy-logger": "^4.0.1", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "fs-extra": "3.0.1", + "http-proxy": "^1.18.1", + "immutable": "^3", + "localtunnel": "^2.0.1", + "micromatch": "^4.0.2", + "opn": "5.3.0", + "portscanner": "2.2.0", + "raw-body": "^2.3.2", + "resp-modifier": "6.0.2", + "rx": "4.1.0", + "send": "0.16.2", + "serve-index": "1.9.1", + "serve-static": "1.13.2", + "server-destroy": "1.0.1", + "socket.io": "^4.4.1", + "ua-parser-js": "^1.0.33", + "yargs": "^17.3.1" + }, + "bin": { + "browser-sync": "dist/bin.js" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/govuk-prototype-kit/node_modules/browser-sync-client": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.29.3.tgz", + "integrity": "sha512-4tK5JKCl7v/3aLbmCBMzpufiYLsB1+UI+7tUXCCp5qF0AllHy/jAqYu6k7hUF3hYtlClKpxExWaR+rH+ny07wQ==", + "dev": true, + "dependencies": { + "etag": "1.8.1", + "fresh": "0.5.2", + "mitt": "^1.1.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/govuk-prototype-kit/node_modules/browser-sync-ui": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.29.3.tgz", + "integrity": "sha512-kBYOIQjU/D/3kYtUIJtj82e797Egk1FB2broqItkr3i4eF1qiHbFCG6srksu9gWhfmuM/TNG76jMfzAdxEPakg==", + "dev": true, + "dependencies": { + "async-each-series": "0.1.1", + "chalk": "4.1.2", + "connect-history-api-fallback": "^1", + "immutable": "^3", + "server-destroy": "1.0.1", + "socket.io-client": "^4.4.1", + "stream-throttle": "^0.1.3" + } + }, + "node_modules/govuk-prototype-kit/node_modules/browser-sync/node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/govuk-prototype-kit/node_modules/browser-sync/node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/govuk-prototype-kit/node_modules/browser-sync/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/govuk-prototype-kit/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/govuk-prototype-kit/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/govuk-prototype-kit/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/govuk-prototype-kit/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -14172,6 +14321,15 @@ "node": ">= 4.2.0" } }, + "node_modules/govuk-prototype-kit/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/govuk-prototype-kit/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -14184,6 +14342,15 @@ "node": ">=0.10.0" } }, + "node_modules/govuk-prototype-kit/node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/govuk-prototype-kit/node_modules/is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -14226,6 +14393,12 @@ "node": ">= 12" } }, + "node_modules/govuk-prototype-kit/node_modules/mitt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", + "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", + "dev": true + }, "node_modules/govuk-prototype-kit/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -14271,6 +14444,18 @@ "node": ">=8" } }, + "node_modules/govuk-prototype-kit/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/govuk-prototype-kit/node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -14972,9 +15157,9 @@ } }, "node_modules/html-validate": { - "version": "8.7.4", - "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.7.4.tgz", - "integrity": "sha512-f/s2z4hAmXY2nI001vje7s1lhV4PnyzOc0CkH5akwyydZgdq3umx3/kumdnGZQbsXYIED8nzK24vxJu9t/UO1w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.9.0.tgz", + "integrity": "sha512-3stj3eoISqSghbOyhWzhHWYybAWlJMULDRVDH/ec45vmKK2JS4lgA09/ZM6VRLQxc4EtZhGl2kcyjFEGXBW3kA==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.0", @@ -15377,15 +15562,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -18865,9 +19041,9 @@ "dev": true }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -26488,9 +26664,9 @@ } }, "node_modules/socket.io-client": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", - "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.3.tgz", + "integrity": "sha512-nU+ywttCyBitXIl9Xe0RSEfek4LneYkJxCeNnKCuhwoH4jGXO1ipIUw/VA/+Vvv2G1MTym11fzFC0SxkrcfXDw==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -27306,12 +27482,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/style-search": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", - "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", - "dev": true - }, "node_modules/stylehacks": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", @@ -27329,47 +27499,45 @@ } }, "node_modules/stylelint": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz", - "integrity": "sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.1.0.tgz", + "integrity": "sha512-Sh1rRV0lN1qxz/QsuuooLWsIZ/ona7NKw/fRZd6y6PyXYdD2W0EAzJ8yJcwSx4Iw/muz0CF09VZ+z4EiTAcKmg==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4", - "@csstools/selector-specificity": "^3.0.0", + "@csstools/css-parser-algorithms": "^2.4.0", + "@csstools/css-tokenizer": "^2.2.2", + "@csstools/media-query-list-parser": "^2.1.6", + "@csstools/selector-specificity": "^3.0.1", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^8.2.0", + "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.1", "css-tree": "^2.3.1", "debug": "^4.3.4", - "fast-glob": "^3.3.1", + "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^7.0.0", + "file-entry-cache": "^8.0.0", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^5.2.4", - "import-lazy": "^4.0.0", + "ignore": "^5.3.0", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.29.0", "mathml-tag-names": "^2.1.3", - "meow": "^10.1.5", + "meow": "^13.0.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.28", + "postcss": "^8.4.32", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", + "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", + "strip-ansi": "^7.1.0", "supports-hyperlinks": "^3.0.0", "svg-tags": "^1.0.0", "table": "^6.8.1", @@ -27379,7 +27547,7 @@ "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=18.12.0" }, "funding": { "type": "opencollective", @@ -27387,43 +27555,46 @@ } }, "node_modules/stylelint-config-gds": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stylelint-config-gds/-/stylelint-config-gds-1.1.1.tgz", - "integrity": "sha512-MKz0zojmXwEdjV7FG0OSGH8SXA9MxoKo566cGo4EIkx0nPg8t+5GzaJvBgtxn6T6m9pq5DO6DshGbg91Y6BSaQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-gds/-/stylelint-config-gds-2.0.0.tgz", + "integrity": "sha512-uloFaElSPR25oJ+tTCO0oLmuiq6qpFMPUwKRz90dGA9eEE+37ljd718P3GFwk5dNNtC3hr3KtNqW8kQOJvgugg==", "dev": true, "dependencies": { - "stylelint-config-standard": "^34.0.0", - "stylelint-config-standard-scss": "^11.1.0" + "stylelint-config-standard": "^36.0.0", + "stylelint-config-standard-scss": "^13.0.0" }, "peerDependencies": { - "stylelint": "^15.11.0" + "stylelint": "^16.0.2" } }, "node_modules/stylelint-config-recommended": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz", - "integrity": "sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.0.tgz", + "integrity": "sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==", "dev": true, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^15.10.0" + "stylelint": "^16.0.0" } }, "node_modules/stylelint-config-recommended-scss": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.1.0.tgz", - "integrity": "sha512-8L5nDfd+YH6AOoBGKmhH8pLWF1dpfY816JtGMePcBqqSsLU+Ysawx44fQSlMOJ2xTfI9yTGpup5JU77c17w1Ww==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.0.0.tgz", + "integrity": "sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==", "dev": true, "dependencies": { "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^13.0.0", - "stylelint-scss": "^5.3.0" + "stylelint-config-recommended": "^14.0.0", + "stylelint-scss": "^6.0.0" + }, + "engines": { + "node": ">=18.12.0" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^15.10.0" + "stylelint": "^16.0.2" }, "peerDependenciesMeta": { "postcss": { @@ -27432,32 +27603,35 @@ } }, "node_modules/stylelint-config-standard": { - "version": "34.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz", - "integrity": "sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ==", + "version": "36.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.0.tgz", + "integrity": "sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug==", "dev": true, "dependencies": { - "stylelint-config-recommended": "^13.0.0" + "stylelint-config-recommended": "^14.0.0" }, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^15.10.0" + "stylelint": "^16.1.0" } }, "node_modules/stylelint-config-standard-scss": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-11.1.0.tgz", - "integrity": "sha512-5gnBgeNTgRVdchMwiFQPuBOtj9QefYtfXiddrOMJA2pI22zxt6ddI2s+e5Oh7/6QYl7QLJujGnaUR5YyGq72ow==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-13.0.0.tgz", + "integrity": "sha512-WaLvkP689qSYUpJQPCo30TFJSSc3VzvvoWnrgp+7PpVby5o8fRUY1cZcP0sePZfjrFl9T8caGhcKg0GO34VDiQ==", "dev": true, "dependencies": { - "stylelint-config-recommended-scss": "^13.1.0", - "stylelint-config-standard": "^34.0.0" + "stylelint-config-recommended-scss": "^14.0.0", + "stylelint-config-standard": "^36.0.0" + }, + "engines": { + "node": ">=18.12.0" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^15.10.0" + "stylelint": "^16.1.0" }, "peerDependenciesMeta": { "postcss": { @@ -27479,33 +27653,56 @@ } }, "node_modules/stylelint-scss": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.0.tgz", - "integrity": "sha512-Sc7S1uWqStMc99NREsHNxpxHHFRvjo2pWILNl/UCwWO8PxhODK8qbJH0GHWIALxl6BD5rwJL4cSm4jk36hi6fg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.0.0.tgz", + "integrity": "sha512-N1xV/Ef5PNRQQt9E45unzGvBUN1KZxCI8B4FgN/pMfmyRYbZGVN4y9qWlvOMdScU17c8VVCnjIHTVn38Bb6qSA==", "dev": true, "dependencies": { - "known-css-properties": "^0.28.0", + "known-css-properties": "^0.29.0", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-selector-parser": "^6.0.13", "postcss-value-parser": "^4.2.0" }, + "engines": { + "node": ">=18.12.0" + }, "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" + "stylelint": "^16.0.2" } }, - "node_modules/stylelint-scss/node_modules/known-css-properties": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.28.0.tgz", - "integrity": "sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==", - "dev": true - }, "node_modules/stylelint/node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", "dev": true }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/stylelint/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -27513,15 +27710,29 @@ "dev": true }, "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.1.tgz", - "integrity": "sha512-uLfFktPmRetVCbHe5UPuekWrQ6hENufnA46qEGbfACkK5drjTTdQYUragRgMjHldcbYG+nslUerqMPjbBSHXjQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "dependencies": { - "flat-cache": "^3.1.1" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.0.0" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.0.tgz", + "integrity": "sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">=16" } }, "node_modules/stylelint/node_modules/global-modules": { @@ -27568,6 +27779,44 @@ "node": ">=0.10.0" } }, + "node_modules/stylelint/node_modules/meow": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.1.0.tgz", + "integrity": "sha512-o5R/R3Tzxq0PJ3v3qcQJtSvSE9nKOLSAaDuuoMzDVuGTwHdccMWcYomh9Xolng2tjT6O/Y83d+0coVGof6tqmA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/postcss-safe-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz", + "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/stylelint/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -27577,6 +27826,24 @@ "node": ">=8" } }, + "node_modules/stylelint/node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/stylelint/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -27591,6 +27858,45 @@ "node": ">=8" } }, + "node_modules/stylelint/node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/stylelint/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -30527,7 +30833,7 @@ "govuk-prototype-kit": "^13.16.0", "gulp": "^4.0.2", "gulp-cli": "^2.3.0", - "html-validate": "8.7.4", + "html-validate": "8.9.0", "nunjucks": "^3.2.4", "outdent": "^0.8.0", "postcss": "^8.4.33", @@ -30575,7 +30881,7 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "autoprefixer": "^10.4.16", - "browser-sync": "^2.29.3", + "browser-sync": "^3.0.2", "cssnano": "^6.0.3", "gulp": "^4.0.2", "gulp-cli": "^2.3.0", diff --git a/package.json b/package.json index dcfd7852aa..bade8ce057 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,8 @@ "postcss-scss": "^4.0.9", "prettier": "^3.1.1", "standard": "^17.1.0", - "stylelint": "^15.11.0", - "stylelint-config-gds": "^1.1.1", + "stylelint": "^16.1.0", + "stylelint-config-gds": "^2.0.0", "stylelint-order": "^6.0.4", "typescript": "^5.3.3" }, diff --git a/packages/govuk-frontend-review/package.json b/packages/govuk-frontend-review/package.json index e02159296d..437f403f5b 100644 --- a/packages/govuk-frontend-review/package.json +++ b/packages/govuk-frontend-review/package.json @@ -51,7 +51,7 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "autoprefixer": "^10.4.16", - "browser-sync": "^2.29.3", + "browser-sync": "^3.0.2", "cssnano": "^6.0.3", "gulp": "^4.0.2", "gulp-cli": "^2.3.0", diff --git a/packages/govuk-frontend-review/src/routes/full-page-examples.puppeteer.test.mjs b/packages/govuk-frontend-review/src/routes/full-page-examples.puppeteer.test.mjs index 00cca60bd4..6ce5f81b45 100644 --- a/packages/govuk-frontend-review/src/routes/full-page-examples.puppeteer.test.mjs +++ b/packages/govuk-frontend-review/src/routes/full-page-examples.puppeteer.test.mjs @@ -125,7 +125,7 @@ describe('Full page examples (with form submit)', () => { // Check the results are correct await expect(getProperty($summary, 'textContent')).resolves.toContain( - '482,211 results' + '221,418 results' ) }) @@ -141,7 +141,7 @@ describe('Full page examples (with form submit)', () => { // Check the results are correct await expect(getProperty($summary, 'textContent')).resolves.toContain( - '221,418 results' + '181,224 results' ) }) @@ -158,7 +158,7 @@ describe('Full page examples (with form submit)', () => { // Check the results are correct await expect(getProperty($summary, 'textContent')).resolves.toContain( - '128,421 results' + '211,248 results' ) }) }) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.mjs index c37800f1db..03750ea1cc 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -9,17 +9,13 @@ router.post( '/feedback', body('what-were-you-trying-to-do') - .exists() - .not() - .isEmpty() + .notEmpty() .withMessage('Enter what you were trying to do') .isLength({ max: 100 }) .withMessage('What were you trying to do must be 100 characters or less'), body('detail') - .exists() - .not() - .isEmpty() + .notEmpty() .withMessage('Enter details of your question, problem or feedback') .isLength({ max: 300 }) .withMessage( @@ -27,38 +23,22 @@ router.post( ), body('do-you-want-a-reply') - .not() - .isEmpty() + .notEmpty() .withMessage('Select yes if you want a reply'), - body('name').custom((value, { req }) => { - // See https://github.com/express-validator/express-validator/pull/658 - const wantsReply = req.body['do-you-want-a-reply'] === 'yes' - if (!wantsReply) { - return true - } - if (!value) { - throw new Error('Enter your name') - } - return true - }), + body('name') + .if(body('do-you-want-a-reply').equals('yes')) + .notEmpty() + .withMessage('Enter your name'), - body('email').custom((value, { req }) => { - // See https://github.com/express-validator/express-validator/pull/658 - const wantsReply = req.body['do-you-want-a-reply'] === 'yes' - if (!wantsReply) { - return true - } - if (!value) { - throw new Error('Enter your email address') - } - if (!value.includes('@')) { - throw new Error( - 'Enter an email address in the correct format, like name@example.com' - ) - } - return true - }), + body('email') + .if(body('do-you-want-a-reply').equals('yes')) + .notEmpty() + .withMessage('Enter your email address') + .isEmail() + .withMessage( + 'Enter an email address in the correct format, like name@example.com' + ), (req, res) => { const viewPath = './full-page-examples/feedback' @@ -71,7 +51,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.njk index 8422eb2182..e5eccdf208 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/feedback/index.njk @@ -29,7 +29,7 @@ notes: Based on https://www.signin.service.gov.uk/feedback {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block header %} {{ govukHeader({ @@ -41,11 +41,11 @@ notes: Based on https://www.signin.service.gov.uk/feedback
- {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} {% endif %}

{{ pageTitle }}

@@ -126,17 +126,16 @@ notes: Based on https://www.signin.service.gov.uk/feedback id: "do-you-want-a-reply", value: "yes", text: "Yes", - checked: values["do-you-want-a-reply"] === "yes", conditional: { html: yesHTML } }, { value: "no", - text: "No", - checked: values["do-you-want-a-reply"] === "no" + text: "No" } ], + value: values["do-you-want-a-reply"], errorMessage: errors["do-you-want-a-reply"] }) }} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.mjs index c9b6b52a1d..b68d67d6c0 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -9,8 +9,7 @@ router.post( '/have-you-changed-your-name', body('changed-name') - .not() - .isEmpty() + .notEmpty() .withMessage('Select if you have changed your name'), (req, res) => { @@ -24,7 +23,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.njk index 4c4bb74ebe..17a5582eb2 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/have-you-changed-your-name/index.njk @@ -18,7 +18,7 @@ scenario: | {% from "govuk/components/button/macro.njk" import govukButton %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -30,14 +30,13 @@ scenario: | {% block content %} - {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} {% endif %} - {{ govukRadios({ classes: "govuk-radios--inline", idPrefix: "changed-name", @@ -62,12 +61,13 @@ scenario: | text: "No" } ], + value: values["changed-name"], errorMessage: errors["changed-name"] }) }} {{ govukButton({ - text: "Continue" - }) }} + text: "Continue" + }) }}
{% endblock %} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.mjs index fde27f4921..ec2710f5a4 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -8,7 +8,7 @@ const router = express.Router() router.post( '/how-do-you-want-to-sign-in', - body('sign-in').not().isEmpty().withMessage('Select how you want to sign in'), + body('sign-in').notEmpty().withMessage('Select how you want to sign in'), (req, res) => { const viewPath = './full-page-examples/how-do-you-want-to-sign-in' @@ -21,7 +21,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.njk index 31f622108a..4572d34c5f 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/how-do-you-want-to-sign-in/index.njk @@ -19,7 +19,7 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -33,12 +33,12 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i
- {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} - {% endif %} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} + {% endif %} {{ govukRadios({ idPrefix: "sign-in", @@ -57,7 +57,6 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i { value: "government-gateway", text: "Sign in with Government Gateway", - checked: values["sign-in"] === "government-gateway", label: { classes: "govuk-label--s" }, @@ -68,7 +67,6 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i { value: "govuk-verify", text: "Sign in with GOV.UK Verify", - checked: values["sign-in"] === "govuk-verify", label: { classes: "govuk-label--s" }, @@ -79,7 +77,6 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i { value: "sign-in-with-a-digital-identity-from-another-european-country", text: "Sign in with a digital identity from another European country", - checked: values["sign-in"] === "sign-in-with-a-digital-identity-from-another-european-country", label: { classes: "govuk-label--s" }, @@ -93,7 +90,6 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i { value: "register-for-self-assessment", text: "Register for Self Assessment", - checked: values["sign-in"] === "register-for-self-assessment", label: { classes: "govuk-label--s" }, @@ -102,6 +98,7 @@ notes: Based on https://www.gov.uk/log-in-file-self-assessment-tax-return/sign-i } } ], + value: values["sign-in"], errorMessage: errors["sign-in"] }) }} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.mjs index e3eaeac83f..7a20032008 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -8,29 +8,10 @@ const router = express.Router() router.post( '/passport-details', - body('passport-number') - .exists() - .not() - .isEmpty() - .withMessage('Enter your passport number'), - - body('expiry-day') - .exists() - .not() - .isEmpty() - .withMessage('Enter your expiry day'), - - body('expiry-month') - .exists() - .not() - .isEmpty() - .withMessage('Enter your expiry month'), - - body('expiry-year') - .exists() - .not() - .isEmpty() - .withMessage('Enter your expiry year'), + body('passport-number').notEmpty().withMessage('Enter your passport number'), + body('expiry-day').notEmpty().withMessage('Enter your expiry day'), + body('expiry-month').notEmpty().withMessage('Enter your expiry month'), + body('expiry-year').notEmpty().withMessage('Enter your expiry year'), (req, res) => { const viewPath = './full-page-examples/passport-details' @@ -78,7 +59,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary, - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.njk index d6a8eb3bb0..7223ba036e 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/passport-details/index.njk @@ -19,7 +19,7 @@ scenario: | {% from "govuk/components/button/macro.njk" import govukButton %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -30,12 +30,13 @@ scenario: | {% block content %}
- {% if errorSummary.length > 0 %} + {% if errorSummary | length %} {{ govukErrorSummary({ titleText: "There is a problem", errorList: errorSummary }) }} {% endif %} +

{{ pageTitle }}

diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/search/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/search/index.mjs index 42c007a65b..d432e159f2 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/search/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/search/index.mjs @@ -3,8 +3,11 @@ import { join } from 'path' import { paths } from '@govuk-frontend/config' import express from 'express' +import { matchedData, query, validationResult } from 'express-validator' import shuffleSeed from 'shuffle-seed' +import { formatValidationErrors } from '../../../utils.mjs' + const { documents } = JSON.parse( readFileSync( join(paths.app, 'src/views/full-page-examples/search/data.json'), @@ -14,33 +17,38 @@ const { documents } = JSON.parse( const router = express.Router() -router.get('/search', (req, res) => { - const { query } = req +router.get( + '/search', + + query('search').default('driving'), + query('order').default('most-viewed'), + query('organisation').toArray(), - query.search ??= 'driving' - query.order ??= 'most-viewed' + (req, res) => { + const errors = formatValidationErrors(validationResult(req)) - // Shuffle the documents based on the query string, to simulate different responses. - const seed = JSON.stringify(query) - const shuffledDocuments = shuffleSeed.shuffle(documents, seed) + // Shuffle the documents based on the query string, to simulate different responses. + const seed = JSON.stringify(req.query) + const shuffledDocuments = shuffleSeed.shuffle(documents, seed) - const total = '128124' + const total = '128124' - // Shuffle the total based on the query string - const randomizedTotal = shuffleSeed.shuffle(total.split(''), seed).join('') + // Shuffle the total based on the query string + const randomizedTotal = shuffleSeed.shuffle(total.split(''), seed).join('') - res.render('./full-page-examples/search/index', { - documents: shuffledDocuments, - order: query.order, + res.render('./full-page-examples/search/index', { + documents: shuffledDocuments, - // Make the total more readable - total: Number(randomizedTotal).toLocaleString('en', { - useGrouping: true - }), + // Make the total more readable + total: Number(randomizedTotal).toLocaleString('en', { + useGrouping: true + }), - // In production this should be sanitized - values: query - }) -}) + errors, + errorSummary: Object.values(errors), + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. + }) + } +) export default router diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/search/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/search/index.njk index d9489dcf13..198c211314 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/search/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/search/index.njk @@ -57,7 +57,8 @@ notes: | id: "search", name: "search", classes: "govuk-!-width-two-thirds", - value: values.search + error: errors["search"], + value: values["search"] }) }}
@@ -92,6 +93,7 @@ notes: | text: "Ministry of Defence" } ], + error: errors["organisation"], values: values["organisation"] }) }} @@ -110,7 +112,6 @@ notes: | {{ govukSelect({ id: "order", name: "order", - value: values.order, classes: "app-search-result-sort", label: { text: "Sort by" @@ -132,7 +133,9 @@ notes: | value: "updated-oldest", text: "Updated (oldest)" } - ] + ], + error: errors["order"], + value: values["order"] }) }}
    diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.mjs index 3d786b76ad..6f3095bc8e 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -9,16 +9,14 @@ router.post( '/update-your-account-details', body('email') - .exists() + .notEmpty() + .withMessage('Enter your email address') .isEmail() .withMessage( 'Enter an email address in the correct format, like name@example.com' - ) - .not() - .isEmpty() - .withMessage('Enter your email address'), + ), - body('password').exists().not().isEmpty().withMessage('Enter your password'), + body('password').notEmpty().withMessage('Enter your password'), (req, res) => { const viewPath = './full-page-examples/update-your-account-details' @@ -31,7 +29,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.njk index da03e4dd7d..cd8a000a92 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/update-your-account-details/index.njk @@ -16,7 +16,7 @@ scenario: | {% from "govuk/components/button/macro.njk" import govukButton %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -27,12 +27,13 @@ scenario: | {% block content %}
    - {% if errorSummary.length > 0 %} + {% if errorSummary | length %} {{ govukErrorSummary({ titleText: "There is a problem", errorList: errorSummary }) }} {% endif %} +

    {{ pageTitle }}

    diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo-success/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo-success/index.njk index e9852e9bcc..0e711cf37a 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo-success/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo-success/index.njk @@ -78,10 +78,10 @@ scenario: | items: [ { value: "true", - html: 'I accept the terms and conditions', - checked: values["terms-and-conditions"] + html: 'I accept the terms and conditions' } ], + values: values["terms-and-conditions"], errorMessage: errors["terms-and-conditions"] }) }} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.mjs index 76f3e17bab..8ae7f7fabd 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -8,11 +8,10 @@ const router = express.Router() router.post( '/upload-your-photo', - body('photo').exists().not().isEmpty().withMessage('Select a photo'), + body('photo').notEmpty().withMessage('Select a photo'), body('terms-and-conditions') - .not() - .isEmpty() + .notEmpty() .withMessage('Select I accept the terms and conditions'), (req, res) => { @@ -26,7 +25,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.njk index 8292061a98..1fa79c1c54 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/upload-your-photo/index.njk @@ -20,7 +20,7 @@ scenario: | {% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block header %} {{ govukHeader({ @@ -56,11 +56,11 @@ scenario: |
    - {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} {% endif %}

    {{ pageTitle }}

    @@ -91,10 +91,10 @@ scenario: | items: [ { value: "true", - html: 'I accept the terms and conditions', - checked: values["terms-and-conditions"] + html: 'I accept the terms and conditions' } ], + values: values["terms-and-conditions"], errorMessage: errors["terms-and-conditions"] }) }} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.mjs index d2961d5338..5806a550f0 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -9,28 +9,13 @@ router.post( '/what-is-your-address', body('address-line-1') - .exists() - .not() - .isEmpty() + .notEmpty() .withMessage('Enter your building and street'), - body('address-town') - .exists() - .not() - .isEmpty() - .withMessage('Enter your town and city'), - - body('address-county') - .exists() - .not() - .isEmpty() - .withMessage('Enter your county'), - - body('address-postcode') - .exists() - .not() - .isEmpty() - .withMessage('Enter your postcode'), + body('address-line-2').optional(), + body('address-town').notEmpty().withMessage('Enter your town and city'), + body('address-county').notEmpty().withMessage('Enter your county'), + body('address-postcode').notEmpty().withMessage('Enter your postcode'), (req, res) => { const viewPath = './full-page-examples/what-is-your-address' @@ -43,7 +28,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.njk index 3a6a6b9b27..175cec62ec 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-address/index.njk @@ -19,7 +19,7 @@ scenario: | {% from "govuk/components/button/macro.njk" import govukButton %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -32,11 +32,11 @@ scenario: |
    - {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} {% endif %} {% call govukFieldset({ diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.mjs index 5a08f52474..3b9fed5ffc 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -8,30 +8,19 @@ const router = express.Router() router.post( '/what-is-your-nationality', - body('confirm-nationality').custom((value, { req }) => { - // See https://github.com/express-validator/express-validator/pull/658 - const cannotProvideNationality = - !!req.body['details-cannot-provide-nationality'] - // If the user cannot provide their nationality and has given us separate details - // then do not show an error for the nationality fields. - if (cannotProvideNationality) { - return true - } - if (!value) { - throw new Error('Select your nationality or nationalities') - } - return true - }), - - body('country-name').custom((value, { req }) => { - // See https://github.com/express-validator/express-validator/pull/658 - const confirmedNationality = req.body['confirm-nationality'] || [] - // If the other country option is selected and there's no value. - if (confirmedNationality.includes('other-country-nationality') && !value) { - throw new Error('Enter your country') - } - return true - }), + body('confirm-nationality') + .if(body('other-nationality').not().equals('yes')) + .if(body('details-cannot-provide-nationality').isEmpty()) + .notEmpty() + .withMessage('Select your nationality or nationalities'), + + body('country-name') + .if(body('other-nationality').equals('yes')) + .notEmpty() + .withMessage('Enter your country'), + + body('details-cannot-provide-nationality'), + body('other-nationality'), (req, res) => { const viewPath = './full-page-examples/what-is-your-nationality' @@ -44,7 +33,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.njk index 9ef78173d8..b6fd8e1154 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-nationality/index.njk @@ -26,7 +26,7 @@ scenario: | {% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -40,12 +40,12 @@ scenario: |
    - {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} - {% endif %} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} + {% endif %} {% set otherCountryHtml %} @@ -57,8 +57,9 @@ scenario: | label: { text: "Country name" }, + value: values["country-name"], errorMessage: errors["country-name"] - }) }} + }) }} {% endset %} @@ -79,7 +80,6 @@ scenario: | { value: "british-nationality", text: "British", - checked: values["confirm-nationality"]|length and "british-nationality" in values["confirm-nationality"], hint: { text: "including English, Scottish, Welsh and Northern Irish" } @@ -87,20 +87,21 @@ scenario: | { value: "irish-nationality", text: "Irish", - checked: values["confirm-nationality"]|length and "irish-nationality" in values["confirm-nationality"], hint: { text: "including from Northern Ireland" } }, { - value: "other-country-nationality", + name: "other-nationality", + value: "yes", text: "Citizen of a different country", - checked: values["confirm-nationality"]|length and "other-country-nationality" in values["confirm-nationality"], conditional: { html: otherCountryHtml - } + }, + checked: values["other-nationality"] === "yes" } ], + values: values["confirm-nationality"], errorMessage: errors["confirm-nationality"] }) }} @@ -117,17 +118,19 @@ scenario: | label: { text: "Can you provide more detail on why you cannot confirm your nationality?" }, - value: values["details-cannot-provide-nationality"] + value: values["details-cannot-provide-nationality"], + errorMessage: errors["details-cannot-provide-nationality"] }) }} {% endset %} {{ govukDetails({ summaryText: "Help with nationality", - html: helpWithNationalityHtml + html: helpWithNationalityHtml, + open: values["details-cannot-provide-nationality"] | length }) }} - {{ govukButton({ + {{ govukButton({ text: "Continue" }) }} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.mjs index f4715c0100..1bfb704427 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -8,11 +8,7 @@ const router = express.Router() router.post( '/what-is-your-postcode', - body('address-postcode') - .exists() - .not() - .isEmpty() - .withMessage('Enter your home postcode'), + body('address-postcode').notEmpty().withMessage('Enter your home postcode'), (req, res) => { const viewPath = './full-page-examples/what-is-your-postcode' @@ -25,7 +21,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.njk index 714ec5a5af..b3f28fa4af 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-is-your-postcode/index.njk @@ -17,7 +17,7 @@ scenario: | {% from "govuk/components/input/macro.njk" import govukInput %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -29,11 +29,11 @@ scenario: | {% block content %}
    - {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} {% endif %} {{ govukInput({ @@ -46,12 +46,13 @@ scenario: | id: "address-postcode", name: "address-postcode", autocomplete: "postal-code", + value: values["address-postcode"], errorMessage: errors["address-postcode"] }) }} {{ govukButton({ - text: "Continue" - }) }} + text: "Continue" + }) }}
    {% endblock %} diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.mjs b/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.mjs index 8c072ae4ca..8d990eef15 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.mjs +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.mjs @@ -1,5 +1,5 @@ import express from 'express' -import { body, validationResult } from 'express-validator' +import { body, matchedData, validationResult } from 'express-validator' import { formatValidationErrors } from '../../../utils.mjs' @@ -9,9 +9,7 @@ router.post( '/what-was-the-last-country-you-visited', body('last-visited-country') - .exists() - .not() - .isEmpty() + .notEmpty() .withMessage('Enter the last country you visited'), (req, res) => { @@ -26,7 +24,7 @@ router.post( res.render(`${viewPath}/index`, { errors, errorSummary: Object.values(errors), - values: req.body // In production this should sanitized. + values: matchedData(req, { onlyValidData: false }) // In production this should sanitized. }) } ) diff --git a/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.njk b/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.njk index bf3f8d626b..146695d299 100644 --- a/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.njk +++ b/packages/govuk-frontend-review/src/views/full-page-examples/what-was-the-last-country-you-visited/index.njk @@ -18,7 +18,7 @@ scenario: | {% from "govuk/components/input/macro.njk" import govukInput %} {% set pageTitle = example.title %} -{% block pageTitle %}{{ "Error: " if errors }}{{ pageTitle }} - GOV.UK{% endblock %} +{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %} {% block beforeContent %} {{ govukBackLink({ @@ -30,11 +30,11 @@ scenario: | {% block content %}
    - {% if errorSummary.length > 0 %} - {{ govukErrorSummary({ - titleText: "There is a problem", - errorList: errorSummary - }) }} + {% if errorSummary | length %} + {{ govukErrorSummary({ + titleText: "There is a problem", + errorList: errorSummary + }) }} {% endif %} {{ govukSelect({ @@ -66,12 +66,13 @@ scenario: | text: "Other" } ], + value: values["last-visited-country"], errorMessage: errors["last-visited-country"] }) }} {{ govukButton({ - text: "Continue" - }) }} + text: "Continue" + }) }}
    {% endblock %} diff --git a/packages/govuk-frontend-review/tasks/watch.mjs b/packages/govuk-frontend-review/tasks/watch.mjs index 967e8c0655..daf9c565b2 100644 --- a/packages/govuk-frontend-review/tasks/watch.mjs +++ b/packages/govuk-frontend-review/tasks/watch.mjs @@ -9,9 +9,14 @@ import { scripts, styles } from './index.mjs' /** * Watch task + * * During development, this task will: + * * - lint and run `gulp styles` when `.scss` files change - * - lint and run `gulp scripts` when `.mjs` files change + * - lint and run `gulp scripts` when `.{cjs,js,mjs}` files change + * + * These tasks respond to `gulp watch` output from GOV.UK Frontend: + * {@link file://./../../govuk-frontend/tasks/watch.mjs} * * @type {import('@govuk-frontend/tasks').TaskFunction} */ @@ -38,7 +43,14 @@ export const watch = (options) => task.name('compile:scss watch', () => gulp.watch( ['**/*.scss', join(paths.package, 'dist/govuk/all.scss')], - { cwd: options.srcPath }, + { + awaitWriteFinish: true, + cwd: options.srcPath, + + // Prevent early Sass compile by ignoring delete (unlink) event + // when GOV.UK Frontend runs the `clean` script before build + events: ['add', 'change'] + }, // Run Sass compile styles(options) diff --git a/packages/govuk-frontend/.htmlvalidate.js b/packages/govuk-frontend/.htmlvalidate.js index e73bbd7e38..841d01fba5 100644 --- a/packages/govuk-frontend/.htmlvalidate.js +++ b/packages/govuk-frontend/.htmlvalidate.js @@ -1,11 +1,11 @@ +const { defineConfig } = require('html-validate') + /** * HTML validation config * * {@link https://html-validate.org/rules/} - * - * @satisfies {import('html-validate').ConfigData} */ -module.exports = { +module.exports = defineConfig({ extends: ['html-validate:recommended'], rules: { // Allow for multiple buttons in the same form to have the same name @@ -71,4 +71,4 @@ module.exports = { } } ] -} +}) diff --git a/packages/govuk-frontend/package.json b/packages/govuk-frontend/package.json index 9c46950800..744c1db406 100644 --- a/packages/govuk-frontend/package.json +++ b/packages/govuk-frontend/package.json @@ -76,7 +76,7 @@ "govuk-prototype-kit": "^13.16.0", "gulp": "^4.0.2", "gulp-cli": "^2.3.0", - "html-validate": "8.7.4", + "html-validate": "8.9.0", "nunjucks": "^3.2.4", "outdent": "^0.8.0", "postcss": "^8.4.33", diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.yaml b/packages/govuk-frontend/src/govuk/components/character-count/character-count.yaml index 938b49b2fe..b9af4bb2e6 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.yaml +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.yaml @@ -51,6 +51,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: classes type: string required: false diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss b/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss index d64a087530..bc656e69ce 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/_index.scss @@ -4,21 +4,18 @@ @import "../label/index"; @include govuk-exports("govuk/component/checkboxes") { - $govuk-touch-target-size: 44px; + $govuk-touch-target-gutter: 4px; $govuk-checkboxes-size: 40px; + $govuk-touch-target-size: ($govuk-checkboxes-size + $govuk-touch-target-gutter); $govuk-small-checkboxes-size: 24px; $govuk-checkboxes-label-padding-left-right: govuk-spacing(3); + $govuk-checkbox-check-horizontal-position: 10px; .govuk-checkboxes__item { - display: block; + display: flex; + flex-wrap: wrap; position: relative; - - min-height: $govuk-checkboxes-size; - margin-bottom: govuk-spacing(2); - padding-left: $govuk-checkboxes-size; - - clear: left; } .govuk-checkboxes__item:last-child, @@ -27,27 +24,23 @@ } .govuk-checkboxes__input { - $input-offset: ($govuk-touch-target-size - $govuk-checkboxes-size) / 2; - - position: absolute; - - z-index: 1; - top: $input-offset * -1; - left: $input-offset * -1; - width: $govuk-touch-target-size; height: $govuk-touch-target-size; margin: 0; - opacity: 0; - cursor: pointer; } .govuk-checkboxes__label { - display: inline-block; + align-self: center; + + // Ensure that the width of the label is never more than the width of the + // container minus the input width minus the padding on either side of + // the label. This prevents the label from going onto the next line due to + // __item using flex-wrap because we want hints on a separate line. + max-width: calc(100% - (($govuk-checkboxes-label-padding-left-right * 2) + $govuk-touch-target-size)); margin-bottom: 0; - padding: 8px $govuk-checkboxes-label-padding-left-right govuk-spacing(1); + padding: (govuk-spacing(1) + $govuk-border-width-form-element) govuk-spacing(3); cursor: pointer; // remove 300ms pause on mobile touch-action: manipulation; @@ -58,8 +51,8 @@ content: ""; box-sizing: border-box; position: absolute; - top: 0; - left: 0; + top: ($govuk-touch-target-gutter / 2); + left: ($govuk-touch-target-gutter / 2); width: $govuk-checkboxes-size; height: $govuk-checkboxes-size; border: $govuk-border-width-form-element solid currentcolor; @@ -73,29 +66,37 @@ .govuk-checkboxes__label::after { content: ""; box-sizing: border-box; - position: absolute; - top: 11px; - left: 9px; + + // Use "magic numbers" to define shape and position of check mark because + // the complexity of the shape makes it difficult to calculate dynamically. + top: 13px; + left: $govuk-checkbox-check-horizontal-position; width: 23px; height: 12px; - transform: rotate(-45deg); border: solid; border-width: 0 0 5px 5px; // Fix bug in IE11 caused by transform rotate (-45deg). // See: alphagov/govuk_elements/issues/518 border-top-color: transparent; - opacity: 0; - background: transparent; } .govuk-checkboxes__hint { display: block; + width: 100%; + margin-top: govuk-spacing(-1); padding-right: $govuk-checkboxes-label-padding-left-right; - padding-left: $govuk-checkboxes-label-padding-left-right; + padding-left: ($govuk-checkboxes-label-padding-left-right + $govuk-touch-target-size); + } + + // This is to bypass govuk-hint's specificity on hints following labels having + // a margin bottom of 10px (govuk-spacing(2)). Because checkboxes are flexbox, + // the margin doesn't collapse so we have to do this manually. + .govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl) + .govuk-checkboxes__hint { + margin-bottom: 0; } // Focused state @@ -182,14 +183,9 @@ .govuk-checkboxes--small { $input-offset: ($govuk-touch-target-size - $govuk-small-checkboxes-size) / 2; - $label-offset: $govuk-touch-target-size - $input-offset; .govuk-checkboxes__item { - @include govuk-clearfix; - min-height: 0; margin-bottom: 0; - padding-left: $label-offset; - float: left; } // Shift the touch target into the left margin so that the visible edge of @@ -202,30 +198,23 @@ // ▲┆└─ Check box pseudo element, aligned with margin // └─── Touch target (invisible input), shifted into the margin .govuk-checkboxes__input { - left: $input-offset * -1; + margin-left: $input-offset * -1; } - // Adjust the size and position of the label. - // - // Unlike larger checkboxes, we also have to float the label in order to - // 'shrink' it, preventing the hover state from kicking in across the full - // width of the parent element. .govuk-checkboxes__label { - margin-top: -2px; - padding: 13px govuk-spacing(3) 13px 1px; - float: left; - - @include govuk-media-query($from: tablet) { - padding: 11px govuk-spacing(3) 10px 1px; - } + // Create a tiny space between the small checkbox hover state so that it + // doesn't clash with the label + padding-left: 1px; } // [ ] Check box // // Reduce the size of the check box [1], vertically center it within the // touch target [2] + // Left here is 0 because we've shifted the input into the left margin .govuk-checkboxes__label::before { - top: $input-offset - $govuk-border-width-form-element; // 2 + top: $input-offset; // 2 + left: 0; width: $govuk-small-checkboxes-size; // 1 height: $govuk-small-checkboxes-size; // 1 } @@ -234,8 +223,11 @@ // // Reduce the size of the check mark and re-align within the checkbox .govuk-checkboxes__label::after { - top: 15px; - left: 6px; + top: 17px; + + // Horizontal position is just the normal sized left value accounting for + // the new width of the smaller checkbox + left: (16px - $govuk-checkbox-check-horizontal-position); width: 12px; height: 6.5px; border-width: 0 0 3px 3px; @@ -250,16 +242,14 @@ // (If you do use them, they won't look completely broken... but seriously, // don't use them) .govuk-checkboxes__hint { - padding: 0; - clear: both; + padding-left: ($govuk-small-checkboxes-size + $input-offset); } // Align conditional reveals with small checkboxes .govuk-checkboxes__conditional { $margin-left: ($govuk-small-checkboxes-size / 2) - ($conditional-border-width / 2); margin-left: $margin-left; - padding-left: $label-offset - ($margin-left + $conditional-border-width); - clear: both; + padding-left: ($govuk-touch-target-size - $input-offset) - ($margin-left + $conditional-border-width); } // Hover state for small checkboxes. diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.yaml b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.yaml index 96f7dc6bd0..c32b4771cf 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.yaml +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.yaml @@ -27,6 +27,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: idPrefix type: string required: false diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/template.njk b/packages/govuk-frontend/src/govuk/components/checkboxes/template.njk index 49ebb9bd23..2daefaf04e 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/template.njk +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/template.njk @@ -103,7 +103,7 @@
    {% endset -%} -
    +
    {% if params.fieldset %} {% call govukFieldset({ describedBy: describedBy, diff --git a/packages/govuk-frontend/src/govuk/components/date-input/date-input.yaml b/packages/govuk-frontend/src/govuk/components/date-input/date-input.yaml index 8582214727..b1d31f717d 100644 --- a/packages/govuk-frontend/src/govuk/components/date-input/date-input.yaml +++ b/packages/govuk-frontend/src/govuk/components/date-input/date-input.yaml @@ -63,6 +63,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: fieldset type: object required: false diff --git a/packages/govuk-frontend/src/govuk/components/date-input/template.njk b/packages/govuk-frontend/src/govuk/components/date-input/template.njk index bd59435776..42aa0d4c3a 100644 --- a/packages/govuk-frontend/src/govuk/components/date-input/template.njk +++ b/packages/govuk-frontend/src/govuk/components/date-input/template.njk @@ -76,7 +76,7 @@
    {% endset -%} -
    +
    {% if params.fieldset %} {#- We override the fieldset's role to 'group' because otherwise JAWS does not announce the description for a fieldset comprised of text inputs, but diff --git a/packages/govuk-frontend/src/govuk/components/file-upload/file-upload.yaml b/packages/govuk-frontend/src/govuk/components/file-upload/file-upload.yaml index 343c3ef1bb..9bfdb03c8b 100644 --- a/packages/govuk-frontend/src/govuk/components/file-upload/file-upload.yaml +++ b/packages/govuk-frontend/src/govuk/components/file-upload/file-upload.yaml @@ -43,6 +43,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: classes type: string required: false diff --git a/packages/govuk-frontend/src/govuk/components/file-upload/template.njk b/packages/govuk-frontend/src/govuk/components/file-upload/template.njk index e830e816fe..ee5cd01dbf 100644 --- a/packages/govuk-frontend/src/govuk/components/file-upload/template.njk +++ b/packages/govuk-frontend/src/govuk/components/file-upload/template.njk @@ -5,7 +5,7 @@ {#- a record of other elements that we need to associate with the input using aria-describedby – for example hints or error messages -#} {% set describedBy = params.describedBy if params.describedBy else "" %} -
    +
    {{ govukLabel({ html: params.label.html, text: params.label.text, diff --git a/packages/govuk-frontend/src/govuk/components/input/input.yaml b/packages/govuk-frontend/src/govuk/components/input/input.yaml index 9b24a0012f..fae40ceef4 100644 --- a/packages/govuk-frontend/src/govuk/components/input/input.yaml +++ b/packages/govuk-frontend/src/govuk/components/input/input.yaml @@ -93,6 +93,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: classes type: string required: false diff --git a/packages/govuk-frontend/src/govuk/components/input/template.njk b/packages/govuk-frontend/src/govuk/components/input/template.njk index 12c96f7340..9fea708e4c 100644 --- a/packages/govuk-frontend/src/govuk/components/input/template.njk +++ b/packages/govuk-frontend/src/govuk/components/input/template.njk @@ -6,7 +6,7 @@ aria-describedby – for example hints or error messages -#} {% set describedBy = params.describedBy if params.describedBy else "" -%} -
    +
    {{ govukLabel({ html: params.label.html, text: params.label.text, diff --git a/packages/govuk-frontend/src/govuk/components/radios/_index.scss b/packages/govuk-frontend/src/govuk/components/radios/_index.scss index 6b2e612fc3..4e017d4cc8 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/_index.scss +++ b/packages/govuk-frontend/src/govuk/components/radios/_index.scss @@ -4,8 +4,9 @@ @import "../label/index"; @include govuk-exports("govuk/component/radios") { - $govuk-touch-target-size: 44px; + $govuk-touch-target-gutter: 4px; $govuk-radios-size: 40px; + $govuk-touch-target-size: ($govuk-radios-size + $govuk-touch-target-gutter); $govuk-small-radios-size: 24px; $govuk-radios-label-padding-left-right: govuk-spacing(3); // When the default focus width is used on a curved edge it looks visually smaller. @@ -13,15 +14,10 @@ $govuk-radios-focus-width: $govuk-focus-width + 1px; .govuk-radios__item { - display: block; + display: flex; + flex-wrap: wrap; position: relative; - - min-height: $govuk-radios-size; - margin-bottom: govuk-spacing(2); - padding-left: $govuk-radios-size; - - clear: left; } .govuk-radios__item:last-child, @@ -30,27 +26,23 @@ } .govuk-radios__input { - $input-offset: ($govuk-touch-target-size - $govuk-radios-size) / 2; - - position: absolute; - - z-index: 1; - top: $input-offset * -1; - left: $input-offset * -1; - width: $govuk-touch-target-size; height: $govuk-touch-target-size; margin: 0; - opacity: 0; - cursor: pointer; } .govuk-radios__label { - display: inline-block; + align-self: center; + + // Ensure that the width of the label is never more than the width of the + // container minus the input width minus the padding on either side of + // the label. This prevents the label from going onto the next line due to + // __item using flex-wrap because we want hints on a separate line + max-width: calc(100% - ($govuk-radios-label-padding-left-right + $govuk-touch-target-size + govuk-spacing(3))); margin-bottom: 0; - padding: 8px $govuk-radios-label-padding-left-right govuk-spacing(1); + padding: (govuk-spacing(1) + $govuk-border-width-form-element) govuk-spacing(3); cursor: pointer; // remove 300ms pause on mobile touch-action: manipulation; @@ -61,12 +53,10 @@ content: ""; box-sizing: border-box; position: absolute; - top: 0; - left: 0; - + top: ($govuk-touch-target-gutter / 2); + left: ($govuk-touch-target-gutter / 2); width: $govuk-radios-size; height: $govuk-radios-size; - border: $govuk-border-width-form-element solid currentcolor; border-radius: 50%; background: transparent; @@ -77,16 +67,19 @@ // We create the 'button' entirely out of 'border' so that they remain // 'filled' even when colours are overridden in the browser. .govuk-radios__label::after { - content: ""; + $radio-button-size: govuk-spacing(2); + content: ""; position: absolute; - top: govuk-spacing(2); - left: govuk-spacing(2); + // Positioned by getting half the touch target, so we have the centre of the + // input, and then moving back by the button's border width, thus positioning + // the centre of the button in the centre of the input. + top: (($govuk-touch-target-size / 2) - $radio-button-size); + left: (($govuk-touch-target-size / 2) - $radio-button-size); width: 0; height: 0; - - border: govuk-spacing(2) solid currentcolor; + border: $radio-button-size solid currentcolor; border-radius: 50%; opacity: 0; background: currentcolor; @@ -94,8 +87,17 @@ .govuk-radios__hint { display: block; + width: 100%; + margin-top: govuk-spacing(-1); padding-right: $govuk-radios-label-padding-left-right; - padding-left: $govuk-radios-label-padding-left-right; + padding-left: ($govuk-radios-label-padding-left-right + $govuk-touch-target-size); + } + + // This is to bypass govuk-hint's specificity on hints following labels having + // a margin bottom of 10px (govuk-spacing(2)). Because radios are flexbox, + // the margin doesn't collapse so we have to do this manually. + .govuk-label:not(.govuk-label--m):not(.govuk-label--l):not(.govuk-label--xl) + .govuk-radios__hint { + margin-bottom: 0; } // Focused state @@ -140,12 +142,12 @@ .govuk-radios--inline { @include govuk-media-query($from: tablet) { - @include govuk-clearfix; + display: flex; + flex-wrap: wrap; + align-items: flex-start; .govuk-radios__item { margin-right: govuk-spacing(4); - float: left; - clear: none; } } } @@ -198,14 +200,9 @@ .govuk-radios--small { $input-offset: ($govuk-touch-target-size - $govuk-small-radios-size) / 2; - $label-offset: $govuk-touch-target-size - $input-offset; .govuk-radios__item { - @include govuk-clearfix; - min-height: 0; margin-bottom: 0; - padding-left: $label-offset; - float: left; } // Shift the touch target into the left margin so that the visible edge of @@ -218,30 +215,23 @@ // ▲┆└─ Radio pseudo element, aligned with margin // └─── Touch target (invisible input), shifted into the margin .govuk-radios__input { - left: $input-offset * -1; + margin-left: $input-offset * -1; } - // Adjust the size and position of the label. - // - // Unlike larger radios, we also have to float the label in order to - // 'shrink' it, preventing the hover state from kicking in across the full - // width of the parent element. .govuk-radios__label { - margin-top: -2px; - padding: 13px govuk-spacing(3) 13px 1px; - float: left; - - @include govuk-media-query($from: tablet) { - padding: 11px govuk-spacing(3) 10px 1px; - } + // Create a tiny space between the small radio hover state so that it + // doesn't clash with the label + padding-left: 1px; } // ( ) Radio ring // // Reduce the size of the control [1], vertically centering it within the // touch target [2] + // Left here is 0 because we've shifted the input into the left margin .govuk-radios__label::before { - top: $input-offset - $govuk-border-width-form-element; // 2 + top: $input-offset; // 2 + left: 0; width: $govuk-small-radios-size; // 1 height: $govuk-small-radios-size; // 1 } @@ -250,9 +240,12 @@ // // Reduce the size of the 'button' and center it within the ring .govuk-radios__label::after { - top: 15px; - left: 7px; - border-width: 5px; + $radio-button-size: govuk-spacing(1); + + // The same calculation as normal radio buttons but reduce the border width + top: (($govuk-touch-target-size / 2) - $radio-button-size); + left: ((($govuk-touch-target-size / 2) - $radio-button-size) - $input-offset); + border-width: $radio-button-size; } // Fix position of hint with small radios @@ -264,17 +257,14 @@ // (If you do use them, they won't look completely broken... but seriously, // don't use them) .govuk-radios__hint { - padding: 0; - clear: both; - pointer-events: none; + padding-left: ($govuk-small-radios-size + $input-offset); } // Align conditional reveals with small radios .govuk-radios__conditional { $margin-left: ($govuk-small-radios-size / 2) - ($conditional-border-width / 2); margin-left: $margin-left; - padding-left: $label-offset - ($margin-left + $conditional-border-width); - clear: both; + padding-left: ($govuk-touch-target-size - $input-offset) - ($margin-left + $conditional-border-width); } .govuk-radios__divider { diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.yaml b/packages/govuk-frontend/src/govuk/components/radios/radios.yaml index b62359980d..3a21c699a6 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.yaml +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.yaml @@ -23,6 +23,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: idPrefix type: string required: false diff --git a/packages/govuk-frontend/src/govuk/components/radios/template.njk b/packages/govuk-frontend/src/govuk/components/radios/template.njk index 7fe0ab4fd0..57fbc79419 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/template.njk +++ b/packages/govuk-frontend/src/govuk/components/radios/template.njk @@ -93,7 +93,7 @@
    {% endset -%} -
    +
    {% if params.fieldset %} {% call govukFieldset({ describedBy: describedBy, diff --git a/packages/govuk-frontend/src/govuk/components/select/select.yaml b/packages/govuk-frontend/src/govuk/components/select/select.yaml index f0e2424b01..b5ca85f5e9 100644 --- a/packages/govuk-frontend/src/govuk/components/select/select.yaml +++ b/packages/govuk-frontend/src/govuk/components/select/select.yaml @@ -68,6 +68,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: classes type: string required: false diff --git a/packages/govuk-frontend/src/govuk/components/select/template.njk b/packages/govuk-frontend/src/govuk/components/select/template.njk index e3fd5ceca2..9f04c83b73 100644 --- a/packages/govuk-frontend/src/govuk/components/select/template.njk +++ b/packages/govuk-frontend/src/govuk/components/select/template.njk @@ -5,7 +5,7 @@ {#- a record of other elements that we need to associate with the input using aria-describedby – for example hints or error messages -#} {% set describedBy = params.describedBy if params.describedBy else "" %} -
    +
    {{ govukLabel({ html: params.label.html, text: params.label.text, diff --git a/packages/govuk-frontend/src/govuk/components/textarea/template.njk b/packages/govuk-frontend/src/govuk/components/textarea/template.njk index 1042b4ad7c..660e86363f 100644 --- a/packages/govuk-frontend/src/govuk/components/textarea/template.njk +++ b/packages/govuk-frontend/src/govuk/components/textarea/template.njk @@ -5,7 +5,7 @@ {#- a record of other elements that we need to associate with the input using aria-describedby – for example hints or error messages -#} {% set describedBy = params.describedBy if params.describedBy else "" %} -
    +
    {{ govukLabel({ html: params.label.html, text: params.label.text, diff --git a/packages/govuk-frontend/src/govuk/components/textarea/textarea.yaml b/packages/govuk-frontend/src/govuk/components/textarea/textarea.yaml index 6d8c9d5ccc..f7321e4019 100644 --- a/packages/govuk-frontend/src/govuk/components/textarea/textarea.yaml +++ b/packages/govuk-frontend/src/govuk/components/textarea/textarea.yaml @@ -51,6 +51,10 @@ params: type: string required: false description: Classes to add to the form group (for example to show error state for the whole group). + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the form group. - name: classes type: string required: false diff --git a/packages/govuk-frontend/tasks/watch.mjs b/packages/govuk-frontend/tasks/watch.mjs index 2525d22dd3..08f6117a81 100644 --- a/packages/govuk-frontend/tasks/watch.mjs +++ b/packages/govuk-frontend/tasks/watch.mjs @@ -8,13 +8,18 @@ import { assets, fixtures, scripts, styles, templates } from './index.mjs' /** * Watch task + * * During development, this task will: + * * - lint and run `gulp styles` when `.scss` files change * - lint and run `gulp scripts` when `.{cjs,js,mjs}` files change * - lint and run `gulp fixtures` when `.yaml` files change * - lint and run `gulp templates` when `.{md,njk}` files change * - lint and run `gulp assets` when assets change * + * These tasks build output for `gulp watch` in Review app: + * {@link file://./../../govuk-frontend-review/tasks/watch.mjs} + * * @type {import('@govuk-frontend/tasks').TaskFunction} */ export const watch = (options) => @@ -38,7 +43,13 @@ export const watch = (options) => * Stylesheets build watcher */ task.name('compile:scss watch', () => - gulp.watch('**/*.scss', { cwd: options.srcPath }, styles(options)) + gulp.watch( + '**/*.scss', + { cwd: options.srcPath }, + + // Run Sass compile + styles(options) + ) ), /** @@ -67,6 +78,8 @@ export const watch = (options) => gulp.watch( '**/*.{cjs,js,mjs}', { cwd: options.srcPath, ignored: ['**/*.test.*'] }, + + // Run JavaScripts compile scripts(options) ) ), @@ -78,6 +91,8 @@ export const watch = (options) => gulp.watch( 'govuk/components/*/*.yaml', { cwd: options.srcPath }, + + // Run fixtures compile fixtures(options) ) ), @@ -89,12 +104,20 @@ export const watch = (options) => gulp.watch( 'govuk/**/*.{md,njk}', { cwd: options.srcPath }, + + // Run templates copy templates(options) ) ), // Copy GOV.UK Frontend static assets task.name('copy:assets watch', () => - gulp.watch('govuk/assets/**', { cwd: options.srcPath }, assets(options)) + gulp.watch( + 'govuk/assets/**', + { cwd: options.srcPath }, + + // Run assets copy + assets(options) + ) ) )