@govuk-one-login/frontend-ui 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/all.css CHANGED
@@ -1 +1 @@
1
- @media(max-width: 640px){.govuk-header__navigation-item{border-left:none !important}.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px !important}.govuk-header__navigation-item{padding-top:8px !important}}.frontendUi_header_signOut-item{padding:5px 0px 5px 30px;border-left:1px solid #b1b4b6;margin-left:auto}.frontendUi_header_signOut-item--rebrand{border-left:none;padding:5px 0px 5px 0px;margin-left:auto;font-weight:700 !important}.frontendUi_header__signOut{display:flex;flex-wrap:wrap}.frontendUi-header__content{margin-left:auto}.govuk-header__navigation--signOut{padding:15px 0 15px !important}.govuk-template--rebranded .govuk-header__navigation{padding:15px 0 15px !important}@media(max-width: 640px){.govuk-header__navigation--signOut{padding:18px 0 8px !important}.govuk-template--rebranded .govuk-header__navigation{padding:18px 0 8px !important}}.govuk-template--rebranded .govuk-header__navigation-item a{font-weight:700 !important}.govuk-template--rebranded .govuk-header__navigation-item{padding-top:5px !important}@media(min-width: 20em)and (max-width: 48.0525em){.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px}}@media(max-width: 323px){.govuk-header__logo{padding-right:5px}.frontendUi_header_signOut-item{padding-left:0px}}@media(max-width: 261px){.frontendUi-header__content{margin-left:unset}.govuk-template--rebranded .govuk-header__navigation{padding:0px 0 8px !important}.govuk-header__logotype{max-width:100%;max-height:auto}.govuk-template--rebranded .govuk-header__logo{padding-top:5% !important;padding-bottom:5% !important}}.govuk-tag{font-size:16px;font-weight:bold;line-height:1;display:inline-block;padding-top:5px;padding-right:8px;padding-bottom:4px;padding-left:8px;outline:2px solid rgba(0,0,0,0);outline-offset:-2px;color:#fff !important;background-color:#1d70b8 !important;letter-spacing:1px !important;text-decoration:none !important;text-transform:uppercase !important}@media(max-width: 256px){.govuk-phase-banner__content{display:block}}.language-select{margin:15px 0 15px 0}.language-select__list{margin-top:1em;text-align:right}.language-select__list-item{display:inline-block}.language-select__list-item:first-child::after{content:"";display:inline-block;position:relative;top:.1875em;height:1em;border-right:.09375em solid #000}.language-select__list-item a,.language-select__list-item [aria-current]{padding:.3125em}@media screen and (max-width: 641px){.language-select__list{float:none;text-align:left;padding-bottom:10px;border-bottom:1px solid #b1b4b6}}.spinner{width:80px;height:80px;border-radius:50%;border-width:12px;border-style:solid;border-color:#dee0e2;border-top-color:#005ea5;margin-bottom:15px}@media(forced-colors: active){.spinner{forced-color-adjust:none;border-top-color:rgba(0,0,0,0) !important}}@media not (prefers-reduced-motion){.spinner{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}}@media(prefers-reduced-motion){.spinner{transform:rotate(0.125turn)}}.spinner__finished{border-color:#005ea5;-webkit-animation:none;animation:none}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.centre{margin-left:auto;margin-right:auto;text-align:center;display:block}@media(max-width: 256px){.govuk-footer__crown{max-width:100%;max-height:auto}}.govuk-footer{padding-top:25px;padding-bottom:15px;border-top:1px solid #b1b4b6;color:#0b0c0c;background:#f3f2f1}.govuk-template--rebranded .govuk-footer{border-top:10px solid #1d70b8;background:#f4f8fb}.govuk-footer__copyright-logo::before{background:#6e777a}.govuk-template--rebranded .govuk-footer__copyright-logo::before{background:currentcolor}.govuk-button--progress{position:relative}.govuk-button--progress-loading{background-color:#505a5f !important;color:#fff !important;pointer-events:none !important;padding-left:40px !important}@keyframes rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.govuk-button--progress-loading::before{content:"";border:3px solid hsla(0,0%,100%,.35);border-top:3px solid #fff;border-radius:50%;width:20px;height:20px;animation:rotate 1s infinite linear;position:absolute;top:12% !important;left:8px}.govuk-progress-button--disabled{opacity:1 !important}@media(prefers-reduced-motion: reduce){.govuk-button--progress-loading{padding-left:10px !important}.govuk-button--progress-loading::before{display:none}}
1
+ @media(max-width: 640px){.govuk-header__navigation-item{border-left:none !important}.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px !important}.govuk-header__navigation-item{padding-top:8px !important}}.frontendUi_header_signOut-item{padding:5px 0px 5px 30px;border-left:1px solid #b1b4b6;margin-left:auto}.frontendUi_header_signOut-item--rebrand{border-left:none;padding:5px 0px 5px 0px;margin-left:auto;font-weight:700 !important}.frontendUi_header__signOut{display:flex;flex-wrap:wrap}.frontendUi-header__content{margin-left:auto}.govuk-header__navigation--signOut{padding:15px 0 15px !important}.govuk-template--rebranded .govuk-header__navigation{padding:15px 0 15px !important}@media(max-width: 640px){.govuk-header__navigation--signOut{padding:18px 0 8px !important}.govuk-template--rebranded .govuk-header__navigation{padding:18px 0 8px !important}}.govuk-template--rebranded .govuk-header__navigation-item a{font-weight:700 !important}.govuk-template--rebranded .govuk-header__navigation-item{padding-top:5px !important}@media(min-width: 20em)and (max-width: 48.0525em){.govuk-template--rebranded .govuk-header__navigation-list{padding-bottom:0px}}@media(max-width: 323px){.govuk-header__logo{padding-right:5px}.frontendUi_header_signOut-item{padding-left:0px}}@media(max-width: 261px){.frontendUi-header__content{margin-left:unset}.govuk-template--rebranded .govuk-header__navigation{padding:0px 0 8px !important}.govuk-header__logotype{max-width:100%;max-height:auto}.govuk-template--rebranded .govuk-header__logo{padding-top:5% !important;padding-bottom:5% !important}}.govuk-tag{font-size:16px;font-weight:bold;line-height:1;display:inline-block;padding-top:5px;padding-right:8px;padding-bottom:4px;padding-left:8px;outline:2px solid rgba(0,0,0,0);outline-offset:-2px;color:#fff !important;background-color:#1d70b8 !important;letter-spacing:1px !important;text-decoration:none !important;text-transform:uppercase !important}@media(max-width: 256px){.govuk-phase-banner__content{display:block}}.language-select{margin:15px 0 15px 0}.language-select__list{margin-top:1em;text-align:right}.language-select__list-item{display:inline-block}.language-select__list-item:first-child::after{content:"";display:inline-block;position:relative;top:.1875em;height:1em;border-right:.09375em solid #000}.language-select__list-item a,.language-select__list-item [aria-current]{padding:.3125em}@media screen and (max-width: 641px){.language-select__list{float:none;text-align:left;padding-bottom:10px;border-bottom:1px solid #b1b4b6}}.spinner{width:80px;height:80px;border-radius:50%;border-width:12px;border-style:solid;border-color:#dee0e2;border-top-color:#005ea5;margin-bottom:15px}@media(forced-colors: active){.spinner{forced-color-adjust:none;border-top-color:rgba(0,0,0,0) !important}}@media not (prefers-reduced-motion){.spinner{-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}}@media(prefers-reduced-motion){.spinner{transform:rotate(0.125turn)}}.spinner__finished{border-color:#005ea5;-webkit-animation:none;animation:none}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.centre{margin-left:auto;margin-right:auto;text-align:center;display:block}@media(max-width: 256px){.govuk-footer__crown{max-width:100%;max-height:auto}}.govuk-footer{padding-top:25px;padding-bottom:15px;border-top:1px solid #b1b4b6;color:#0b0c0c;background:#f3f2f1}.govuk-template--rebranded .govuk-footer{border-top:10px solid #1d70b8;background:#f4f8fb}.govuk-footer__copyright-logo::before{background:#6e777a}.govuk-template--rebranded .govuk-footer__copyright-logo::before{background:currentcolor}.govuk-button--progress{position:relative}.govuk-button--progress-loading{background-color:#505a5f !important;color:#fff !important;pointer-events:none !important;padding-left:40px !important}@keyframes rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.govuk-button--progress-loading::before{content:"";border:3px solid hsla(0,0%,100%,.35);border-top:3px solid #fff;border-radius:50%;width:20px;height:20px;animation:rotate 1s infinite linear;position:absolute;top:12% !important;left:8px}.govuk-progress-button--disabled{opacity:1 !important}@media(prefers-reduced-motion: reduce){.govuk-button--progress-loading{padding-left:10px !important}.govuk-button--progress-loading::before{display:none}}.step-card{border:1px solid #b1b4b6;gap:0;box-sizing:border-box;display:flex;flex-direction:column;align-items:flex-start;text-align:left;background:#fff;color:#0b0c0c}@media(max-width: 768px){.step-card{padding-left:.75rem;padding-right:.75rem;margin-left:.75rem;margin-right:.75rem}}@media(min-width: 769px){.step-card{padding-left:1.5rem;padding-right:1.5rem}}.step-item-container{display:flex;align-items:flex-start;border-bottom:1px solid #b1b4b6}.step-item-image{flex-shrink:0;margin-right:16px;display:flex;align-items:center;background-color:#e8f1f8;justify-content:space-around;width:70px;height:70px}.step-item-content{display:flex;align-items:flex-start;width:100%}.step-title-container{display:grid;grid-template-columns:max-content 1fr;column-gap:.25em;align-items:baseline}.step-number{grid-column:1;grid-row:1;margin-bottom:0}.step-title{grid-column:2;grid-row:1;margin-bottom:6px}.step-description{grid-column:2;grid-row:2;margin-top:0;margin-bottom:0}.step-description.govuk-list--bullet{grid-column:1/-1;padding-left:0;margin-left:1.2em;margin-top:0px}.step-card .govuk-list li:last-child .step-item-container{border-bottom:none;padding-bottom:0}@media(max-width: 300px){.step-item-container{flex-direction:column}.step-item-image{margin-right:0;margin-bottom:12px}.step-title-container{display:flex;flex-wrap:wrap;align-items:baseline}.step-description{width:100%}}.govuk-list{margin-bottom:0 !important;width:100%}
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var path = require('node:path');
4
3
  var node_fs = require('node:fs');
4
+ var path = require('node:path');
5
+ var pino = require('pino');
6
+ var _ = require('lodash');
5
7
 
6
8
  var cookieBanner$1 = {
7
9
  body1: "Rydym yn defnyddio rhai cwcis hanfodol i wneud i'r gwasanaeth hwn weithio.",
@@ -80,6 +82,9 @@ var progressButton$1 = {
80
82
  longWaitingText: "Parhau i aros",
81
83
  noJavascriptMessage: "Gall gymryd hyd at 10 eiliad i barhau i'r dudalen nesaf. Ar ôl i chi barhau, peidiwch ag ail-lwytho na chau'r dudalen hon."
82
84
  };
85
+ var stepCard$1 = {
86
+ header: "Cwblhewch y camau canlynol"
87
+ };
83
88
  var translationCy = {
84
89
  cookieBanner: cookieBanner$1,
85
90
  footer: footer$1,
@@ -87,7 +92,8 @@ var translationCy = {
87
92
  languageSelect: languageSelect$1,
88
93
  phaseBanner: phaseBanner$1,
89
94
  skipLink: skipLink$1,
90
- progressButton: progressButton$1
95
+ progressButton: progressButton$1,
96
+ stepCard: stepCard$1
91
97
  };
92
98
 
93
99
  var cookieBanner = {
@@ -167,6 +173,9 @@ var progressButton = {
167
173
  longWaitingText: "Keep waiting",
168
174
  noJavascriptMessage: "It can take up to 10 seconds to continue to the next page. After you continue, do not reload or close this page."
169
175
  };
176
+ var stepCard = {
177
+ header: "Complete the following steps"
178
+ };
170
179
  var translationEn = {
171
180
  cookieBanner: cookieBanner,
172
181
  footer: footer,
@@ -174,12 +183,145 @@ var translationEn = {
174
183
  languageSelect: languageSelect,
175
184
  phaseBanner: phaseBanner,
176
185
  skipLink: skipLink,
177
- progressButton: progressButton
186
+ progressButton: progressButton,
187
+ stepCard: stepCard
188
+ };
189
+
190
+ let logger;
191
+ const setCustomLogger = (customLogger) => {
192
+ logger = customLogger;
193
+ };
194
+ const getLogger = () => {
195
+ var _a, _b;
196
+ if (!logger) {
197
+ logger = pino.pino({
198
+ name: "@govuk-one-login/frontend-passthrough-headers",
199
+ level: (_b = (_a = process.env.LOG_LEVEL) !== null && _a !== void 0 ? _a : process.env.LOGS_LEVEL) !== null && _b !== void 0 ? _b : "warn",
200
+ });
201
+ return logger;
202
+ }
203
+ return logger;
178
204
  };
179
205
 
206
+ const getGTM = (req, res, next) => {
207
+ res.locals.ga4ContainerId = req.app.get("APP.GTM.GA4_CONTAINER_ID");
208
+ res.locals.analyticsCookieDomain = req.app.get("APP.GTM.ANALYTICS_COOKIE_DOMAIN");
209
+ res.locals.ga4Enabled = req.app.get("APP.GTM.GA4_ENABLED");
210
+ res.locals.analyticsDataSensitive = req.app.get("APP.GTM.ANALYTICS_DATA_SENSITIVE");
211
+ res.locals.ga4PageViewEnabled = req.app.get("APP.GTM.GA4_PAGE_VIEW_ENABLED");
212
+ res.locals.ga4FormResponseEnabled = req.app.get("APP.GTM.GA4_FORM_RESPONSE_ENABLED");
213
+ res.locals.ga4FormErrorEnabled = req.app.get("APP.GTM.GA4_FORM_ERROR_ENABLED");
214
+ res.locals.ga4FormChangeEnabled = req.app.get("APP.GTM.GA4_FORM_CHANGE_ENABLED");
215
+ res.locals.ga4NavigationEnabled = req.app.get("APP.GTM.GA4_NAVIGATION_ENABLED");
216
+ res.locals.ga4SelectContentEnabled = req.app.get("APP.GTM.GA4_SELECT_CONTENT_ENABLED");
217
+ next();
218
+ };
219
+ const getAssetPath = (req, res, next) => {
220
+ res.locals.assetPath = req.app.get("APP.ASSET_PATH");
221
+ next();
222
+ };
223
+ const getLanguageToggle = (req, res, next, customLogger) => {
224
+ if (customLogger) {
225
+ setCustomLogger(customLogger);
226
+ }
227
+ const logger = getLogger();
228
+ const toggleValue = req.app.get("APP.LANGUAGE_TOGGLE_ENABLED");
229
+ res.locals.showLanguageToggle = toggleValue && toggleValue === "1";
230
+ res.locals.htmlLang = req.i18n.language;
231
+ try {
232
+ res.locals.currentUrl = new URL(req.protocol + "://" + req.get("host") + req.originalUrl);
233
+ }
234
+ catch (e) {
235
+ if (e instanceof Error) {
236
+ logger.warn("Error constructing url for language toggle", e.message);
237
+ }
238
+ }
239
+ next();
240
+ };
241
+
242
+ var locals = /*#__PURE__*/Object.freeze({
243
+ __proto__: null,
244
+ getAssetPath: getAssetPath,
245
+ getGTM: getGTM,
246
+ getLanguageToggle: getLanguageToggle
247
+ });
248
+
249
+ const setGTM = ({ app, ga4ContainerId, analyticsCookieDomain, ga4Enabled, ga4PageViewEnabled, ga4FormResponseEnabled, ga4FormErrorEnabled, ga4FormChangeEnabled, ga4NavigationEnabled, ga4SelectContentEnabled, analyticsDataSensitive, }) => {
250
+ app.set("APP.GTM.GA4_CONTAINER_ID", ga4ContainerId);
251
+ app.set("APP.GTM.ANALYTICS_COOKIE_DOMAIN", analyticsCookieDomain);
252
+ app.set("APP.GTM.GA4_ENABLED", ga4Enabled);
253
+ app.set("APP.GTM.GA4_PAGE_VIEW_ENABLED", ga4PageViewEnabled);
254
+ app.set("APP.GTM.GA4_FORM_RESPONSE_ENABLED", ga4FormResponseEnabled);
255
+ app.set("APP.GTM.GA4_FORM_ERROR_ENABLED", ga4FormErrorEnabled);
256
+ app.set("APP.GTM.GA4_FORM_CHANGE_ENABLED", ga4FormChangeEnabled);
257
+ app.set("APP.GTM.GA4_NAVIGATION_ENABLED", ga4NavigationEnabled);
258
+ app.set("APP.GTM.GA4_SELECT_CONTENT_ENABLED", ga4SelectContentEnabled);
259
+ app.set("APP.GTM.ANALYTICS_DATA_SENSITIVE", analyticsDataSensitive !== null && analyticsDataSensitive !== void 0 ? analyticsDataSensitive : true);
260
+ };
261
+ const setLanguageToggle = ({ app, showLanguageToggle, }) => {
262
+ app.set("APP.LANGUAGE_TOGGLE_ENABLED", showLanguageToggle);
263
+ };
264
+
265
+ var settings = /*#__PURE__*/Object.freeze({
266
+ __proto__: null,
267
+ setGTM: setGTM,
268
+ setLanguageToggle: setLanguageToggle
269
+ });
270
+
271
+ const baseHelmetConfig = {
272
+ contentSecurityPolicy: {
273
+ directives: {
274
+ defaultSrc: ["'self'"],
275
+ styleSrc: ["'self'"],
276
+ scriptSrc: [
277
+ "'self'",
278
+ (_req, res) => `'nonce-${res.locals.cspNonce}'`,
279
+ "'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='",
280
+ "https://www.googletagmanager.com",
281
+ "https://www.google-analytics.com",
282
+ "https://ssl.google-analytics.com",
283
+ ],
284
+ imgSrc: [
285
+ "'self'",
286
+ "data:",
287
+ "https://www.googletagmanager.com",
288
+ "*.google-analytics.com",
289
+ "*.analytics.google.com",
290
+ ],
291
+ fontSrc: ["'self'"],
292
+ formAction: ["*"],
293
+ objectSrc: ["'none'"],
294
+ connectSrc: [
295
+ "'self'",
296
+ "*.google-analytics.com",
297
+ "*.analytics.google.com",
298
+ ],
299
+ },
300
+ },
301
+ dnsPrefetchControl: {
302
+ allow: false,
303
+ },
304
+ frameguard: {
305
+ action: "deny",
306
+ },
307
+ hsts: {
308
+ maxAge: 31536000, // 1 Year
309
+ preload: true,
310
+ includeSubDomains: true,
311
+ },
312
+ referrerPolicy: false,
313
+ permittedCrossDomainPolicies: false,
314
+ expectCt: false,
315
+ crossOriginEmbedderPolicy: false,
316
+ };
317
+ function getHelmetConfig(additions = {}) {
318
+ return _.merge(baseHelmetConfig, additions);
319
+ }
320
+
180
321
  // Implementation
181
322
  function frontendUiMiddleware(req, res, next) {
182
323
  res.locals.translations = req.i18n.store.data[req.i18n.language];
324
+ res.locals.allTranslations = req.i18n.store.data;
183
325
  res.locals.basePath = process.cwd();
184
326
  next();
185
327
  }
@@ -197,6 +339,29 @@ const frontendUiMiddlewareIdentityBypass = (req, res, next) => {
197
339
  res.locals.basePath = process.cwd();
198
340
  next();
199
341
  };
342
+ function resolvePath(obj, path) {
343
+ return path.split(".").reduce((acc, key) => (acc && typeof acc === "object" ? acc[key] : undefined), obj);
344
+ }
345
+ function buildSteps(allTranslations, currentTranslations, steps) {
346
+ if (!allTranslations || !steps || !currentTranslations)
347
+ return [];
348
+ return steps
349
+ .slice(0, 4)
350
+ .map(({ key, image }) => {
351
+ const allLanguageData = Object.keys(allTranslations).map((lng) => resolvePath(allTranslations[lng], key));
352
+ return {
353
+ data: resolvePath(currentTranslations, key),
354
+ allLanguageData,
355
+ image: image !== null && image !== void 0 ? image : null,
356
+ };
357
+ })
358
+ .filter(({ allLanguageData }) => allLanguageData.every((step) => { var _a, _b; return (step === null || step === void 0 ? void 0 : step.title) && ((step === null || step === void 0 ? void 0 : step.description) || ((_b = (_a = step === null || step === void 0 ? void 0 : step.bulletList) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0); }));
359
+ }
360
+ function addFrontendUiGlobals(nunjucksEnv) {
361
+ nunjucksEnv.addGlobal("addLanguageParam", addLanguageParam);
362
+ nunjucksEnv.addGlobal("contactUsUrl", contactUsUrl);
363
+ nunjucksEnv.addGlobal("buildSteps", buildSteps);
364
+ }
200
365
  function addLanguageParam(language, url) {
201
366
  if (!url) {
202
367
  console.warn("URL is undefined. The parameter cannot be added, and the toggle will not work.");
@@ -211,7 +376,7 @@ function contactUsUrl(baseUrl, urlToAppend) {
211
376
  }
212
377
  try {
213
378
  const newUrl = new URL(urlToAppend); // Create a new URL object to modify
214
- newUrl.search = ''; // Remove the query parameters
379
+ newUrl.search = ""; // Remove the query parameters
215
380
  const encodedUrl = encodeURIComponent(newUrl.toString()); // Encode the entire URL
216
381
  const searchParams = new URLSearchParams({ fromURL: encodedUrl });
217
382
  return `${baseUrl}?${searchParams.toString()}`;
@@ -248,12 +413,18 @@ const getTranslationObject = (locale, filepath) => {
248
413
  return {}; // Return an empty object as a fallback
249
414
  };
250
415
 
416
+ exports.addFrontendUiGlobals = addFrontendUiGlobals;
251
417
  exports.addLanguageParam = addLanguageParam;
418
+ exports.buildSteps = buildSteps;
252
419
  exports.contactUsUrl = contactUsUrl;
253
420
  exports.frontendUiMiddleware = frontendUiMiddleware;
254
421
  exports.frontendUiMiddlewareIdentityBypass = frontendUiMiddlewareIdentityBypass;
255
422
  exports.frontendUiTranslationCy = translationCy;
256
423
  exports.frontendUiTranslationEn = translationEn;
424
+ exports.getHelmetConfig = getHelmetConfig;
257
425
  exports.getTranslationObject = getTranslationObject;
426
+ exports.locals = locals;
427
+ exports.resolvePath = resolvePath;
258
428
  exports.setBaseTranslations = setBaseTranslations;
259
429
  exports.setFrontendUiTranslations = setFrontendUiTranslations;
430
+ exports.settings = settings;
@@ -1,5 +1,6 @@
1
- import i18next from "i18next";
2
1
  import { NextFunction, Request, Response } from "express";
2
+ import i18next from "i18next";
3
+ export * from "./lib";
3
4
  interface I18nData {
4
5
  language: string;
5
6
  store: {
@@ -14,6 +15,9 @@ interface ExpressRequest extends Request {
14
15
  interface ExpressResponse extends Response {
15
16
  locals: {
16
17
  translations: unknown;
18
+ allTranslations: {
19
+ [lng: string]: unknown;
20
+ };
17
21
  basePath?: string;
18
22
  };
19
23
  }
@@ -23,6 +27,9 @@ interface PlainRequest {
23
27
  interface PlainResponse {
24
28
  locals: {
25
29
  translations: unknown;
30
+ allTranslations: {
31
+ [lng: string]: unknown;
32
+ };
26
33
  basePath?: string;
27
34
  };
28
35
  }
@@ -30,6 +37,24 @@ export declare function frontendUiMiddleware(req: ExpressRequest, res: ExpressRe
30
37
  export declare function frontendUiMiddleware(req: PlainRequest, res: PlainResponse, next: NextFunction): void;
31
38
  export declare const setFrontendUiTranslations: (instanceI18n: typeof i18next) => void;
32
39
  export declare const frontendUiMiddlewareIdentityBypass: (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
40
+ export declare function resolvePath(obj: unknown, path: string): unknown;
41
+ type StepData = {
42
+ title?: unknown;
43
+ description?: unknown;
44
+ bulletList?: unknown[];
45
+ };
46
+ type StepInput = {
47
+ key: string;
48
+ image?: string | null;
49
+ };
50
+ export declare function buildSteps(allTranslations: Record<string, unknown>, currentTranslations: unknown, steps: StepInput[]): {
51
+ data: StepData;
52
+ allLanguageData: StepData[];
53
+ image: string | null;
54
+ }[];
55
+ export declare function addFrontendUiGlobals(nunjucksEnv: {
56
+ addGlobal: (name: string, value: unknown) => void;
57
+ }): void;
33
58
  export declare function addLanguageParam(language: string, url?: URL): string;
34
59
  export declare function contactUsUrl(baseUrl: string, urlToAppend: string): string | null;
35
60
  export declare const setBaseTranslations: (instanceI18n: typeof i18next, filePath?: string) => void;
@@ -1,5 +1,6 @@
1
- import i18next from "i18next";
2
1
  import { NextFunction, Request, Response } from "express";
2
+ import i18next from "i18next";
3
+ export * from "./lib";
3
4
  interface I18nData {
4
5
  language: string;
5
6
  store: {
@@ -14,6 +15,9 @@ interface ExpressRequest extends Request {
14
15
  interface ExpressResponse extends Response {
15
16
  locals: {
16
17
  translations: unknown;
18
+ allTranslations: {
19
+ [lng: string]: unknown;
20
+ };
17
21
  basePath?: string;
18
22
  };
19
23
  }
@@ -23,6 +27,9 @@ interface PlainRequest {
23
27
  interface PlainResponse {
24
28
  locals: {
25
29
  translations: unknown;
30
+ allTranslations: {
31
+ [lng: string]: unknown;
32
+ };
26
33
  basePath?: string;
27
34
  };
28
35
  }
@@ -30,6 +37,24 @@ export declare function frontendUiMiddleware(req: ExpressRequest, res: ExpressRe
30
37
  export declare function frontendUiMiddleware(req: PlainRequest, res: PlainResponse, next: NextFunction): void;
31
38
  export declare const setFrontendUiTranslations: (instanceI18n: typeof i18next) => void;
32
39
  export declare const frontendUiMiddlewareIdentityBypass: (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
40
+ export declare function resolvePath(obj: unknown, path: string): unknown;
41
+ type StepData = {
42
+ title?: unknown;
43
+ description?: unknown;
44
+ bulletList?: unknown[];
45
+ };
46
+ type StepInput = {
47
+ key: string;
48
+ image?: string | null;
49
+ };
50
+ export declare function buildSteps(allTranslations: Record<string, unknown>, currentTranslations: unknown, steps: StepInput[]): {
51
+ data: StepData;
52
+ allLanguageData: StepData[];
53
+ image: string | null;
54
+ }[];
55
+ export declare function addFrontendUiGlobals(nunjucksEnv: {
56
+ addGlobal: (name: string, value: unknown) => void;
57
+ }): void;
33
58
  export declare function addLanguageParam(language: string, url?: URL): string;
34
59
  export declare function contactUsUrl(baseUrl: string, urlToAppend: string): string | null;
35
60
  export declare const setBaseTranslations: (instanceI18n: typeof i18next, filePath?: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQ1D,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAaR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAerE,CAAC;AAGF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC;AAEF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,31 +1,3 @@
1
- import { Request, Response } from "express";
2
- declare const _default: {
3
- contentSecurityPolicy: {
4
- directives: {
5
- defaultSrc: string[];
6
- styleSrc: string[];
7
- scriptSrc: (string | ((_req: Request, res: Response) => string))[];
8
- imgSrc: string[];
9
- formAction: string[];
10
- objectSrc: string[];
11
- connectSrc: string[];
12
- };
13
- };
14
- dnsPrefetchControl: {
15
- allow: boolean;
16
- };
17
- frameguard: {
18
- action: string;
19
- };
20
- hsts: {
21
- maxAge: number;
22
- preload: boolean;
23
- includeSubDomains: boolean;
24
- };
25
- referrerPolicy: boolean;
26
- permittedCrossDomainPolicies: boolean;
27
- expectCt: boolean;
28
- crossOriginEmbedderPolicy: boolean;
29
- };
30
- export default _default;
1
+ import { type HelmetOptions } from "helmet";
2
+ export declare function getHelmetConfig(additions?: HelmetOptions): HelmetOptions;
31
3
  //# sourceMappingURL=helmet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helmet.d.ts","sourceRoot":"","sources":["../../../../src/lib/helmet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;;;;;;yCAS7B,OAAO,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;;AAPrC,wBA4CE"}
1
+ {"version":3,"file":"helmet.d.ts","sourceRoot":"","sources":["../../../../src/lib/helmet.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAiD5C,wBAAgB,eAAe,CAAC,SAAS,GAAE,aAAkB,GAAG,aAAa,CAE5E"}
@@ -1,4 +1,4 @@
1
1
  export * as locals from "./locals";
2
2
  export * as settings from "./settings";
3
- export * as helmet from "./helmet";
3
+ export * from "./helmet";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,cAAc,UAAU,CAAC"}
@@ -3,4 +3,5 @@
3
3
  @use "./language-select";
4
4
  @use "./spinner";
5
5
  @use "./footer";
6
- @use "./progress-button";
6
+ @use "./progress-button";
7
+ @use "./step-card";
@@ -46,7 +46,7 @@
46
46
  {%- if pageErrorState %}
47
47
  {{ 'general.govuk.errorTitlePrefix' | translate }}
48
48
  {% endif %}
49
- {%- if pageTitleKey %}{{ pageTitleKey | translateWithContextOrFallback(context) }} – GOV.UK One Login{% endif %}
49
+ {%- if pageTitleKey %}{{ pageTitleKey | translate(context) }} – GOV.UK One Login{% endif %}
50
50
  {%- endblock %}
51
51
 
52
52
  {% block bodyStart %}
@@ -158,7 +158,7 @@
158
158
  </script>
159
159
  {{ ga4OnPageLoad({
160
160
  nonce: cspNonce,
161
- statusCode: statusCode,
161
+ statusCode: statusCode,
162
162
  dynamic: isPageDynamic,
163
163
  englishPageTitle: pageTitleKey | translateToEnglish,
164
164
  taxonomyLevel1: taxLevel1,
@@ -1,4 +1,4 @@
1
- @import "../../../../node_modules/govuk-frontend/govuk/helpers/_colour.scss";
1
+ @import "../../../../node_modules/govuk-frontend/dist/govuk/helpers/_colour.scss";
2
2
  // Temporarily overwrite the tag component styling to match pre-v5 appearance.
3
3
  // This ensures a cohesive phase banner across One Login applications during the transition.
4
4
  .govuk-tag {
@@ -22,7 +22,7 @@
22
22
  }
23
23
 
24
24
  @media (max-width: 256px) {
25
- .govuk-phase-banner__content{
26
- display: block;
27
- }
25
+ .govuk-phase-banner__content {
26
+ display: block;
27
+ }
28
28
  }
@@ -0,0 +1,112 @@
1
+ # Step Card
2
+
3
+ This component displays a numbered list of steps, each with an image, title, and description or bullet list. It is intended to guide users through a process in a clear, structured format.
4
+
5
+ - Renders up to 4 steps
6
+ - Each step's content comes from your translation files, referenced by a dot-notation key
7
+ - A step is only rendered if it is valid (has a `title` and either a `description` or `bulletList`) in **all** configured languages
8
+ - Steps that are invalid in any language are silently omitted
9
+
10
+ ## How it works
11
+
12
+ Step content lives entirely in your translation files. The component uses `buildSteps` — a Nunjucks global provided by `@govuk-one-login/frontend-ui` — to resolve each step's content across all languages and filter out any steps that are incomplete in any language before passing them to the macro.
13
+
14
+ `buildSteps` is called in the outer template scope (where Nunjucks globals are available), inline within the macro call. The macro itself just renders the pre-resolved steps.
15
+
16
+ `allTranslations` and `translations` are set automatically on `res.locals` by `frontendUiMiddleware` and are available in all templates without any extra setup.
17
+
18
+ ## Setup
19
+
20
+ Register `buildSteps` as a Nunjucks global in your app's Nunjucks configuration:
21
+
22
+ ```js
23
+ const frontendUi = require("@govuk-one-login/frontend-ui");
24
+
25
+ nunjucksEnv.addGlobal("buildSteps", frontendUi.buildSteps);
26
+ ```
27
+
28
+ Or if you are already calling `addFrontendUiGlobals`, this is included automatically:
29
+
30
+ ```js
31
+ frontendUi.addFrontendUiGlobals(nunjucksEnv);
32
+ ```
33
+
34
+ Ensure `frontendUiMiddleware` is registered in your Express app:
35
+
36
+ ```js
37
+ const { frontendUiMiddleware } = require("@govuk-one-login/frontend-ui");
38
+ app.use(frontendUiMiddleware);
39
+ ```
40
+
41
+ ## Translation file structure
42
+
43
+ Each step must have a `title` and either a `description` or a `bulletList`. The `key` you pass is the full dot-notation path from the root of the language object in your translation data.
44
+
45
+ ```json
46
+ {
47
+ "pages": {
48
+ "myPage": {
49
+ "steps": [
50
+ {
51
+ "title": "Create an account",
52
+ "description": "Sign up using your email address."
53
+ },
54
+ {
55
+ "title": "Verify your identity",
56
+ "bulletList": ["Provide your passport", "Answer security questions"]
57
+ }
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ This structure must exist in **every** language's translation file. Any step missing `title` and `description`/`bulletList` in any language will be omitted from all languages.
65
+
66
+ ## Usage
67
+
68
+ ```njk
69
+ {% from "frontend-ui/build/components/step-card/macro.njk" import frontendUiStepCard %}
70
+
71
+ {{ frontendUiStepCard({
72
+ translations: translations.translation.stepCard,
73
+ steps: buildSteps(allTranslations, translations, [
74
+ { key: "translation.pages.myPage.steps.0", image: "/assets/images/step1.svg" },
75
+ { key: "translation.pages.myPage.steps.1", image: "/assets/images/step2.svg" },
76
+ { key: "translation.pages.myPage.steps.2", image: null }
77
+ ])
78
+ }) }}
79
+ ```
80
+
81
+ ## Parameters
82
+
83
+ ### `frontendUiStepCard`
84
+
85
+ | Name | Type | Required | Description |
86
+ |------|------|----------|-------------|
87
+ | `translations` | object | Yes | Translations object for the component (e.g. `translations.translation.stepCard`). The template uses `translations.header` for the section heading. |
88
+ | `steps` | array | Yes | Array of resolved step objects returned by `buildSteps`. |
89
+
90
+ ### `buildSteps(allTranslations, translations, steps)`
91
+
92
+ | Argument | Type | Description |
93
+ |----------|------|-------------|
94
+ | `allTranslations` | object | All language translation data, available in templates as `allTranslations` via `frontendUiMiddleware`. |
95
+ | `translations` | object | Current language translation data, available in templates as `translations` via `frontendUiMiddleware`. |
96
+ | `steps` | array | Array of step definitions, each with a `key` and optional `image`. |
97
+
98
+ ### Step definition
99
+
100
+ | Name | Type | Required | Description |
101
+ |------|------|----------|-------------|
102
+ | `key` | string | Yes | Full dot-notation path to the step from the root of the language object (e.g. `translation.pages.myPage.steps.0`). |
103
+ | `image` | string | No | Path to the step image. Falls back to an inline SVG placeholder if `null` or omitted. |
104
+
105
+ ## Styles
106
+
107
+ Styles for this component are defined in `_index.scss`. The component uses GOV.UK Frontend base styles and responsive breakpoints.
108
+
109
+ ## Accessibility
110
+
111
+ - Step images are marked with `aria-hidden="true"` as they are decorative
112
+ - Steps are rendered as an ordered list (`<ol>`) to convey sequence to assistive technologies
@@ -0,0 +1,112 @@
1
+ @import "../../../../node_modules/govuk-frontend/dist/govuk/base.scss";
2
+
3
+ .step-card {
4
+ border: 1px solid $govuk-border-colour;
5
+ gap: 0;
6
+ box-sizing: border-box;
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: flex-start;
10
+ text-align: left;
11
+ background: #fff;
12
+ color: #0b0c0c;
13
+
14
+ @media (max-width: 768px) {
15
+ padding-left: 0.75rem;
16
+ padding-right: 0.75rem;
17
+ margin-left: 0.75rem;
18
+ margin-right: 0.75rem;
19
+ }
20
+
21
+ @media (min-width: 769px) {
22
+ padding-left: 1.5rem;
23
+ padding-right: 1.5rem;
24
+ }
25
+ }
26
+
27
+ .step-item-container {
28
+ display: flex;
29
+ align-items: flex-start;
30
+ border-bottom: 1px solid $govuk-border-colour;
31
+ }
32
+
33
+ .step-item-image {
34
+ flex-shrink: 0;
35
+ margin-right: 16px;
36
+ display: flex;
37
+ align-items: center;
38
+ background-color: #e8f1f8;
39
+ justify-content: space-around;
40
+ width: 70px;
41
+ height: 70px;
42
+ }
43
+
44
+ .step-item-content {
45
+ display: flex;
46
+ align-items: flex-start;
47
+ width: 100%;
48
+ }
49
+
50
+ .step-title-container {
51
+ display: grid;
52
+ grid-template-columns: max-content 1fr;
53
+ column-gap: 0.25em;
54
+ align-items: baseline;
55
+ }
56
+
57
+ .step-number {
58
+ grid-column: 1;
59
+ grid-row: 1;
60
+ margin-bottom: 0;
61
+ }
62
+
63
+ .step-title {
64
+ grid-column: 2;
65
+ grid-row: 1;
66
+ margin-bottom: 6px;
67
+ }
68
+
69
+ .step-description {
70
+ grid-column: 2;
71
+ grid-row: 2;
72
+ margin-top: 0;
73
+ margin-bottom: 0;
74
+ }
75
+
76
+ .step-description.govuk-list--bullet {
77
+ grid-column: 1 / -1;
78
+ padding-left: 0;
79
+ margin-left: 1.2em;
80
+ margin-top: 0px;
81
+ }
82
+
83
+ .step-card .govuk-list li:last-child .step-item-container {
84
+ border-bottom: none;
85
+ padding-bottom: 0;
86
+ }
87
+
88
+ @media (max-width: 300px) {
89
+ .step-item-container {
90
+ flex-direction: column;
91
+ }
92
+
93
+ .step-item-image {
94
+ margin-right: 0;
95
+ margin-bottom: 12px;
96
+ }
97
+
98
+ .step-title-container {
99
+ display: flex;
100
+ flex-wrap: wrap;
101
+ align-items: baseline;
102
+ }
103
+
104
+ .step-description {
105
+ width: 100%;
106
+ }
107
+ }
108
+
109
+ .govuk-list {
110
+ margin-bottom: 0 !important;
111
+ width: 100%;
112
+ }
@@ -0,0 +1,2 @@
1
+ {% macro frontendUiStepCard(params) %} {%- include "./template.njk" -%} {% endmacro
2
+ %}
@@ -0,0 +1,22 @@
1
+ params:
2
+ - name: translations
3
+ type: object
4
+ required: true
5
+ description: translations object from middleware (translations.translation.stepCard)
6
+ - name: steps
7
+ type: array
8
+ required: true
9
+ description: array of step objects combining translation data and image. Maximum of 4 steps will be rendered.
10
+ params:
11
+ - name: data
12
+ type: object
13
+ required: true
14
+ description: the step translation object containing title, description or bulletList
15
+ - name: image
16
+ type: string
17
+ required: false
18
+ description: path to the image for this step, or null for the default placeholder
19
+ - name: stepsPath
20
+ type: string
21
+ required: false
22
+ description: dot-notation path to the steps array within each language's translation data (e.g. "translation.pages.stepCard1.steps"). Requires configureFrontendUiNunjucks to be called in your nunjucks setup. When provided, a step is only rendered if it is valid in all languages.
@@ -0,0 +1,33 @@
1
+ {% set stepCard = params.translations %}
2
+ {% set steps = params.steps if params.steps else [] %}
3
+
4
+ <div class="step-card govuk-!-padding-top-3 govuk-!-padding-bottom-6 govuk-!-margin-bottom-6">
5
+ <h2 class="govuk-heading-m">{{ stepCard.header }}</h2>
6
+ <ol class="govuk-list" role="list">
7
+ {% for step in steps %}
8
+ <li class="step-item" role="listitem">
9
+ <div class="step-item-container govuk-!-padding-top-6{% if not loop.last %} govuk-!-padding-bottom-6{% endif %}">
10
+ <div class="step-item-image" aria-hidden="true">
11
+ <img src="{{ step.image if step.image else 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2270%22 height=%2270%22 viewBox=%220 0 70 70%22%3E%3Crect width=%2270%22 height=%2270%22 fill=%22%23e8f1f8%22/%3E%3C/svg%3E' }}" alt="">
12
+ </div>
13
+ <div class="step-item-content">
14
+ <div class="step-title-container">
15
+ <span class="step-number govuk-body govuk-!-font-weight-bold">{{loop.index}}.</span>
16
+ <p class="govuk-body govuk-!-font-weight-bold step-title">{{step.data.title}}</p>
17
+ {% if step.data.description and not step.data.bulletList %}
18
+ <p class="govuk-body step-description">{{ step.data.description }}</p>
19
+ {% endif %}
20
+ {% if step.data.bulletList and step.data.bulletList.length %}
21
+ <ul class="govuk-list govuk-list--bullet step-description">
22
+ {% for item in step.data.bulletList %}
23
+ <li>{{ item }}</li>
24
+ {% endfor %}
25
+ </ul>
26
+ {% endif %}
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </li>
31
+ {% endfor %}
32
+ </ol>
33
+ </div>
@@ -1,5 +1,6 @@
1
- import i18next from "i18next";
2
1
  import { NextFunction, Request, Response } from "express";
2
+ import i18next from "i18next";
3
+ export * from "./lib";
3
4
  interface I18nData {
4
5
  language: string;
5
6
  store: {
@@ -14,6 +15,9 @@ interface ExpressRequest extends Request {
14
15
  interface ExpressResponse extends Response {
15
16
  locals: {
16
17
  translations: unknown;
18
+ allTranslations: {
19
+ [lng: string]: unknown;
20
+ };
17
21
  basePath?: string;
18
22
  };
19
23
  }
@@ -23,6 +27,9 @@ interface PlainRequest {
23
27
  interface PlainResponse {
24
28
  locals: {
25
29
  translations: unknown;
30
+ allTranslations: {
31
+ [lng: string]: unknown;
32
+ };
26
33
  basePath?: string;
27
34
  };
28
35
  }
@@ -30,6 +37,24 @@ export declare function frontendUiMiddleware(req: ExpressRequest, res: ExpressRe
30
37
  export declare function frontendUiMiddleware(req: PlainRequest, res: PlainResponse, next: NextFunction): void;
31
38
  export declare const setFrontendUiTranslations: (instanceI18n: typeof i18next) => void;
32
39
  export declare const frontendUiMiddlewareIdentityBypass: (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
40
+ export declare function resolvePath(obj: unknown, path: string): unknown;
41
+ type StepData = {
42
+ title?: unknown;
43
+ description?: unknown;
44
+ bulletList?: unknown[];
45
+ };
46
+ type StepInput = {
47
+ key: string;
48
+ image?: string | null;
49
+ };
50
+ export declare function buildSteps(allTranslations: Record<string, unknown>, currentTranslations: unknown, steps: StepInput[]): {
51
+ data: StepData;
52
+ allLanguageData: StepData[];
53
+ image: string | null;
54
+ }[];
55
+ export declare function addFrontendUiGlobals(nunjucksEnv: {
56
+ addGlobal: (name: string, value: unknown) => void;
57
+ }): void;
33
58
  export declare function addLanguageParam(language: string, url?: URL): string;
34
59
  export declare function contactUsUrl(baseUrl: string, urlToAppend: string): string | null;
35
60
  export declare const setBaseTranslations: (instanceI18n: typeof i18next, filePath?: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQ1D,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAaR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAerE,CAAC;AAGF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC;AAEF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,OAAO,MAAM,SAAS,CAAC;AAI9B,cAAc,OAAO,CAAC;AAGtB,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,IAAI,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;KAClC,CAAC;CACH;AAED,UAAU,cAAe,SAAQ,OAAO;IACtC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,eAAgB,SAAQ,QAAQ;IACxC,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;QAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE;QACN,YAAY,EAAE,OAAO,CAAC;QACtB,eAAe,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC;QAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAGD,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAER,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,GAAG,EAAE,aAAa,EAClB,IAAI,EAAE,YAAY,GACjB,IAAI,CAAC;AAcR,eAAO,MAAM,yBAAyB,GAAI,cAAc,OAAO,OAAO,SAerE,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAC7C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,YAAY,SAUnB,CAAC;AAEF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAI/D;AAED,KAAK,QAAQ,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AACnF,KAAK,SAAS,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAExD,wBAAgB,UAAU,CACxB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,mBAAmB,EAAE,OAAO,EAC5B,KAAK,EAAE,SAAS,EAAE,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,eAAe,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,CAmBzE;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE;IAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,QAItG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,UAU3D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,iBAkBhE;AAED,eAAO,MAAM,mBAAmB,GAC9B,cAAc,OAAO,OAAO,EAC5B,WAAW,MAAM,SASlB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,WAAW,MAAM,KAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAuBxB,CAAC;AAEF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACpF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -1,5 +1,7 @@
1
- import path from 'node:path';
2
1
  import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pino } from 'pino';
4
+ import _ from 'lodash';
3
5
 
4
6
  var cookieBanner$1 = {
5
7
  body1: "Rydym yn defnyddio rhai cwcis hanfodol i wneud i'r gwasanaeth hwn weithio.",
@@ -78,6 +80,9 @@ var progressButton$1 = {
78
80
  longWaitingText: "Parhau i aros",
79
81
  noJavascriptMessage: "Gall gymryd hyd at 10 eiliad i barhau i'r dudalen nesaf. Ar ôl i chi barhau, peidiwch ag ail-lwytho na chau'r dudalen hon."
80
82
  };
83
+ var stepCard$1 = {
84
+ header: "Cwblhewch y camau canlynol"
85
+ };
81
86
  var translationCy = {
82
87
  cookieBanner: cookieBanner$1,
83
88
  footer: footer$1,
@@ -85,7 +90,8 @@ var translationCy = {
85
90
  languageSelect: languageSelect$1,
86
91
  phaseBanner: phaseBanner$1,
87
92
  skipLink: skipLink$1,
88
- progressButton: progressButton$1
93
+ progressButton: progressButton$1,
94
+ stepCard: stepCard$1
89
95
  };
90
96
 
91
97
  var cookieBanner = {
@@ -165,6 +171,9 @@ var progressButton = {
165
171
  longWaitingText: "Keep waiting",
166
172
  noJavascriptMessage: "It can take up to 10 seconds to continue to the next page. After you continue, do not reload or close this page."
167
173
  };
174
+ var stepCard = {
175
+ header: "Complete the following steps"
176
+ };
168
177
  var translationEn = {
169
178
  cookieBanner: cookieBanner,
170
179
  footer: footer,
@@ -172,12 +181,145 @@ var translationEn = {
172
181
  languageSelect: languageSelect,
173
182
  phaseBanner: phaseBanner,
174
183
  skipLink: skipLink,
175
- progressButton: progressButton
184
+ progressButton: progressButton,
185
+ stepCard: stepCard
186
+ };
187
+
188
+ let logger;
189
+ const setCustomLogger = (customLogger) => {
190
+ logger = customLogger;
191
+ };
192
+ const getLogger = () => {
193
+ var _a, _b;
194
+ if (!logger) {
195
+ logger = pino({
196
+ name: "@govuk-one-login/frontend-passthrough-headers",
197
+ level: (_b = (_a = process.env.LOG_LEVEL) !== null && _a !== void 0 ? _a : process.env.LOGS_LEVEL) !== null && _b !== void 0 ? _b : "warn",
198
+ });
199
+ return logger;
200
+ }
201
+ return logger;
176
202
  };
177
203
 
204
+ const getGTM = (req, res, next) => {
205
+ res.locals.ga4ContainerId = req.app.get("APP.GTM.GA4_CONTAINER_ID");
206
+ res.locals.analyticsCookieDomain = req.app.get("APP.GTM.ANALYTICS_COOKIE_DOMAIN");
207
+ res.locals.ga4Enabled = req.app.get("APP.GTM.GA4_ENABLED");
208
+ res.locals.analyticsDataSensitive = req.app.get("APP.GTM.ANALYTICS_DATA_SENSITIVE");
209
+ res.locals.ga4PageViewEnabled = req.app.get("APP.GTM.GA4_PAGE_VIEW_ENABLED");
210
+ res.locals.ga4FormResponseEnabled = req.app.get("APP.GTM.GA4_FORM_RESPONSE_ENABLED");
211
+ res.locals.ga4FormErrorEnabled = req.app.get("APP.GTM.GA4_FORM_ERROR_ENABLED");
212
+ res.locals.ga4FormChangeEnabled = req.app.get("APP.GTM.GA4_FORM_CHANGE_ENABLED");
213
+ res.locals.ga4NavigationEnabled = req.app.get("APP.GTM.GA4_NAVIGATION_ENABLED");
214
+ res.locals.ga4SelectContentEnabled = req.app.get("APP.GTM.GA4_SELECT_CONTENT_ENABLED");
215
+ next();
216
+ };
217
+ const getAssetPath = (req, res, next) => {
218
+ res.locals.assetPath = req.app.get("APP.ASSET_PATH");
219
+ next();
220
+ };
221
+ const getLanguageToggle = (req, res, next, customLogger) => {
222
+ if (customLogger) {
223
+ setCustomLogger(customLogger);
224
+ }
225
+ const logger = getLogger();
226
+ const toggleValue = req.app.get("APP.LANGUAGE_TOGGLE_ENABLED");
227
+ res.locals.showLanguageToggle = toggleValue && toggleValue === "1";
228
+ res.locals.htmlLang = req.i18n.language;
229
+ try {
230
+ res.locals.currentUrl = new URL(req.protocol + "://" + req.get("host") + req.originalUrl);
231
+ }
232
+ catch (e) {
233
+ if (e instanceof Error) {
234
+ logger.warn("Error constructing url for language toggle", e.message);
235
+ }
236
+ }
237
+ next();
238
+ };
239
+
240
+ var locals = /*#__PURE__*/Object.freeze({
241
+ __proto__: null,
242
+ getAssetPath: getAssetPath,
243
+ getGTM: getGTM,
244
+ getLanguageToggle: getLanguageToggle
245
+ });
246
+
247
+ const setGTM = ({ app, ga4ContainerId, analyticsCookieDomain, ga4Enabled, ga4PageViewEnabled, ga4FormResponseEnabled, ga4FormErrorEnabled, ga4FormChangeEnabled, ga4NavigationEnabled, ga4SelectContentEnabled, analyticsDataSensitive, }) => {
248
+ app.set("APP.GTM.GA4_CONTAINER_ID", ga4ContainerId);
249
+ app.set("APP.GTM.ANALYTICS_COOKIE_DOMAIN", analyticsCookieDomain);
250
+ app.set("APP.GTM.GA4_ENABLED", ga4Enabled);
251
+ app.set("APP.GTM.GA4_PAGE_VIEW_ENABLED", ga4PageViewEnabled);
252
+ app.set("APP.GTM.GA4_FORM_RESPONSE_ENABLED", ga4FormResponseEnabled);
253
+ app.set("APP.GTM.GA4_FORM_ERROR_ENABLED", ga4FormErrorEnabled);
254
+ app.set("APP.GTM.GA4_FORM_CHANGE_ENABLED", ga4FormChangeEnabled);
255
+ app.set("APP.GTM.GA4_NAVIGATION_ENABLED", ga4NavigationEnabled);
256
+ app.set("APP.GTM.GA4_SELECT_CONTENT_ENABLED", ga4SelectContentEnabled);
257
+ app.set("APP.GTM.ANALYTICS_DATA_SENSITIVE", analyticsDataSensitive !== null && analyticsDataSensitive !== void 0 ? analyticsDataSensitive : true);
258
+ };
259
+ const setLanguageToggle = ({ app, showLanguageToggle, }) => {
260
+ app.set("APP.LANGUAGE_TOGGLE_ENABLED", showLanguageToggle);
261
+ };
262
+
263
+ var settings = /*#__PURE__*/Object.freeze({
264
+ __proto__: null,
265
+ setGTM: setGTM,
266
+ setLanguageToggle: setLanguageToggle
267
+ });
268
+
269
+ const baseHelmetConfig = {
270
+ contentSecurityPolicy: {
271
+ directives: {
272
+ defaultSrc: ["'self'"],
273
+ styleSrc: ["'self'"],
274
+ scriptSrc: [
275
+ "'self'",
276
+ (_req, res) => `'nonce-${res.locals.cspNonce}'`,
277
+ "'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='",
278
+ "https://www.googletagmanager.com",
279
+ "https://www.google-analytics.com",
280
+ "https://ssl.google-analytics.com",
281
+ ],
282
+ imgSrc: [
283
+ "'self'",
284
+ "data:",
285
+ "https://www.googletagmanager.com",
286
+ "*.google-analytics.com",
287
+ "*.analytics.google.com",
288
+ ],
289
+ fontSrc: ["'self'"],
290
+ formAction: ["*"],
291
+ objectSrc: ["'none'"],
292
+ connectSrc: [
293
+ "'self'",
294
+ "*.google-analytics.com",
295
+ "*.analytics.google.com",
296
+ ],
297
+ },
298
+ },
299
+ dnsPrefetchControl: {
300
+ allow: false,
301
+ },
302
+ frameguard: {
303
+ action: "deny",
304
+ },
305
+ hsts: {
306
+ maxAge: 31536000, // 1 Year
307
+ preload: true,
308
+ includeSubDomains: true,
309
+ },
310
+ referrerPolicy: false,
311
+ permittedCrossDomainPolicies: false,
312
+ expectCt: false,
313
+ crossOriginEmbedderPolicy: false,
314
+ };
315
+ function getHelmetConfig(additions = {}) {
316
+ return _.merge(baseHelmetConfig, additions);
317
+ }
318
+
178
319
  // Implementation
179
320
  function frontendUiMiddleware(req, res, next) {
180
321
  res.locals.translations = req.i18n.store.data[req.i18n.language];
322
+ res.locals.allTranslations = req.i18n.store.data;
181
323
  res.locals.basePath = process.cwd();
182
324
  next();
183
325
  }
@@ -195,6 +337,29 @@ const frontendUiMiddlewareIdentityBypass = (req, res, next) => {
195
337
  res.locals.basePath = process.cwd();
196
338
  next();
197
339
  };
340
+ function resolvePath(obj, path) {
341
+ return path.split(".").reduce((acc, key) => (acc && typeof acc === "object" ? acc[key] : undefined), obj);
342
+ }
343
+ function buildSteps(allTranslations, currentTranslations, steps) {
344
+ if (!allTranslations || !steps || !currentTranslations)
345
+ return [];
346
+ return steps
347
+ .slice(0, 4)
348
+ .map(({ key, image }) => {
349
+ const allLanguageData = Object.keys(allTranslations).map((lng) => resolvePath(allTranslations[lng], key));
350
+ return {
351
+ data: resolvePath(currentTranslations, key),
352
+ allLanguageData,
353
+ image: image !== null && image !== void 0 ? image : null,
354
+ };
355
+ })
356
+ .filter(({ allLanguageData }) => allLanguageData.every((step) => { var _a, _b; return (step === null || step === void 0 ? void 0 : step.title) && ((step === null || step === void 0 ? void 0 : step.description) || ((_b = (_a = step === null || step === void 0 ? void 0 : step.bulletList) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0); }));
357
+ }
358
+ function addFrontendUiGlobals(nunjucksEnv) {
359
+ nunjucksEnv.addGlobal("addLanguageParam", addLanguageParam);
360
+ nunjucksEnv.addGlobal("contactUsUrl", contactUsUrl);
361
+ nunjucksEnv.addGlobal("buildSteps", buildSteps);
362
+ }
198
363
  function addLanguageParam(language, url) {
199
364
  if (!url) {
200
365
  console.warn("URL is undefined. The parameter cannot be added, and the toggle will not work.");
@@ -209,7 +374,7 @@ function contactUsUrl(baseUrl, urlToAppend) {
209
374
  }
210
375
  try {
211
376
  const newUrl = new URL(urlToAppend); // Create a new URL object to modify
212
- newUrl.search = ''; // Remove the query parameters
377
+ newUrl.search = ""; // Remove the query parameters
213
378
  const encodedUrl = encodeURIComponent(newUrl.toString()); // Encode the entire URL
214
379
  const searchParams = new URLSearchParams({ fromURL: encodedUrl });
215
380
  return `${baseUrl}?${searchParams.toString()}`;
@@ -246,4 +411,4 @@ const getTranslationObject = (locale, filepath) => {
246
411
  return {}; // Return an empty object as a fallback
247
412
  };
248
413
 
249
- export { addLanguageParam, contactUsUrl, frontendUiMiddleware, frontendUiMiddlewareIdentityBypass, translationCy as frontendUiTranslationCy, translationEn as frontendUiTranslationEn, getTranslationObject, setBaseTranslations, setFrontendUiTranslations };
414
+ export { addFrontendUiGlobals, addLanguageParam, buildSteps, contactUsUrl, frontendUiMiddleware, frontendUiMiddlewareIdentityBypass, translationCy as frontendUiTranslationCy, translationEn as frontendUiTranslationEn, getHelmetConfig, getTranslationObject, locals, resolvePath, setBaseTranslations, setFrontendUiTranslations, settings };
@@ -1,31 +1,3 @@
1
- import { Request, Response } from "express";
2
- declare const _default: {
3
- contentSecurityPolicy: {
4
- directives: {
5
- defaultSrc: string[];
6
- styleSrc: string[];
7
- scriptSrc: (string | ((_req: Request, res: Response) => string))[];
8
- imgSrc: string[];
9
- formAction: string[];
10
- objectSrc: string[];
11
- connectSrc: string[];
12
- };
13
- };
14
- dnsPrefetchControl: {
15
- allow: boolean;
16
- };
17
- frameguard: {
18
- action: string;
19
- };
20
- hsts: {
21
- maxAge: number;
22
- preload: boolean;
23
- includeSubDomains: boolean;
24
- };
25
- referrerPolicy: boolean;
26
- permittedCrossDomainPolicies: boolean;
27
- expectCt: boolean;
28
- crossOriginEmbedderPolicy: boolean;
29
- };
30
- export default _default;
1
+ import { type HelmetOptions } from "helmet";
2
+ export declare function getHelmetConfig(additions?: HelmetOptions): HelmetOptions;
31
3
  //# sourceMappingURL=helmet.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helmet.d.ts","sourceRoot":"","sources":["../../../../src/lib/helmet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;;;;;;yCAS7B,OAAO,OAAO,QAAQ;;;;;;;;;;;;;;;;;;;;;;;AAPrC,wBA4CE"}
1
+ {"version":3,"file":"helmet.d.ts","sourceRoot":"","sources":["../../../../src/lib/helmet.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAiD5C,wBAAgB,eAAe,CAAC,SAAS,GAAE,aAAkB,GAAG,aAAa,CAE5E"}
@@ -1,4 +1,4 @@
1
1
  export * as locals from "./locals";
2
2
  export * as settings from "./settings";
3
- export * as helmet from "./helmet";
3
+ export * from "./helmet";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC;AACvC,cAAc,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@govuk-one-login/frontend-ui",
3
- "version": "5.0.0",
3
+ "version": "5.1.0",
4
4
  "description": "",
5
5
  "main": "build/cjs/backend/index.cjs",
6
6
  "module": "build/esm/backend/index.js",
@@ -12,8 +12,8 @@
12
12
  "build": "rollup -c",
13
13
  "dev": "rollup -c --watch",
14
14
  "test": "jest --coverage src",
15
- "test:visual": "npx playwright install --with-deps && playwright test browser-tests/functional-tests",
16
- "test:visual:update": "npx playwright install --with-deps && playwright test browser-tests/functional-tests --update-snapshots"
15
+ "test:visual": "playwright test browser-tests/functional-tests",
16
+ "test:visual:update": "playwright test browser-tests/functional-tests --update-snapshots"
17
17
  },
18
18
  "files": [
19
19
  "build/",
@@ -43,7 +43,13 @@
43
43
  "peerDependencies": {
44
44
  "@govuk-one-login/frontend-analytics": " ^3.0.1 || ^4.0.3",
45
45
  "express": "^5.1.0 || >= 4.21.2",
46
- "govuk-frontend": "^4.10.1 || ^5.0.0"
46
+ "govuk-frontend": "^4.10.1 || ^5.0.0",
47
+ "hmpo-components": "^7.1.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "hmpo-components": {
51
+ "optional": true
52
+ }
47
53
  },
48
54
  "exports": {
49
55
  ".": {
@@ -57,7 +63,6 @@
57
63
  },
58
64
  "types": "./build/esm/backend/index.d.ts",
59
65
  "optionalDependencies": {
60
- "@govuk-one-login/frontend-analytics": "^4.0.7",
61
- "hmpo-components": "^7.1.0"
66
+ "@govuk-one-login/frontend-analytics": "^4.0.7"
62
67
  }
63
68
  }