@hmlr/govuk-react-components-library 1.0.2 → 1.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/.stylelintrc.json +1 -1
- package/README.md +215 -1
- package/assets/uml-diagram.svg +731 -572
- package/bundle-analysis-main.html +2 -2
- package/bundle-analysis-pdfvc.html +1 -1
- package/dist/PDFViewerCanvas.cjs.css +4 -4
- package/dist/PDFViewerCanvas.cjs.js +91 -7
- package/dist/PDFViewerCanvas.cjs.js.map +1 -1
- package/dist/PDFViewerCanvas.esm.css +4 -4
- package/dist/PDFViewerCanvas.esm.js +91 -7
- package/dist/PDFViewerCanvas.esm.js.map +1 -1
- package/dist/index.cjs.css +13 -8
- package/dist/index.cjs.js +356 -60
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +104 -10
- package/dist/index.esm.css +13 -8
- package/dist/index.esm.js +357 -67
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/ExitThisPage/ExitThisPage.config.d.ts +1 -0
- package/dist/types/components/ExitThisPage/ExitThisPage.d.ts +4 -0
- package/dist/types/components/ExitThisPage/ExitThisPage.types.d.ts +17 -0
- package/dist/types/components/ExitThisPage/index.d.ts +2 -0
- package/dist/types/components/PDFViewer/ResolvePDFSource.d.ts +24 -1
- package/dist/types/components/Pagination/Pagination.types.d.ts +23 -13
- package/dist/types/components/Pagination/index.d.ts +2 -0
- package/dist/types/components/PasswordInput/PasswordInput.config.d.ts +1 -0
- package/dist/types/components/PasswordInput/PasswordInput.d.ts +4 -0
- package/dist/types/components/PasswordInput/PasswordInput.types.d.ts +49 -0
- package/dist/types/components/PasswordInput/index.d.ts +2 -0
- package/dist/types/components/index.d.ts +2 -0
- package/dist/types/utils/WithReference.types.d.ts +7 -2
- package/package.json +70 -66
- package/scripts/generateStories.mjs +433 -0
- package/dist/types/JestSetup.d.ts +0 -1
package/dist/index.esm.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@charset "UTF-8";
|
|
2
2
|
/*!
|
|
3
|
-
* Bootstrap v5.3.
|
|
3
|
+
* Bootstrap v5.3.8 (https://getbootstrap.com/)
|
|
4
4
|
* Copyright 2011-2025 The Bootstrap Authors
|
|
5
5
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
6
6
|
*/
|
|
@@ -545,6 +545,10 @@ legend + * {
|
|
|
545
545
|
-webkit-appearance: textfield;
|
|
546
546
|
outline-offset: -2px;
|
|
547
547
|
}
|
|
548
|
+
[type=search]::-webkit-search-cancel-button {
|
|
549
|
+
cursor: pointer;
|
|
550
|
+
filter: grayscale(1);
|
|
551
|
+
}
|
|
548
552
|
|
|
549
553
|
/* rtl:raw:
|
|
550
554
|
[type="tel"],
|
|
@@ -6140,6 +6144,7 @@ textarea.form-control-lg {
|
|
|
6140
6144
|
.spinner-grow,
|
|
6141
6145
|
.spinner-border {
|
|
6142
6146
|
display: inline-block;
|
|
6147
|
+
flex-shrink: 0;
|
|
6143
6148
|
width: var(--bs-spinner-width);
|
|
6144
6149
|
height: var(--bs-spinner-height);
|
|
6145
6150
|
vertical-align: var(--bs-spinner-vertical-align);
|
|
@@ -11859,8 +11864,8 @@ textarea.form-control-lg {
|
|
|
11859
11864
|
}
|
|
11860
11865
|
|
|
11861
11866
|
.card-link :hover {
|
|
11862
|
-
border-color: #fd0;
|
|
11863
11867
|
color: #003078;
|
|
11868
|
+
border-color: #fd0;
|
|
11864
11869
|
}
|
|
11865
11870
|
|
|
11866
11871
|
.card-header {
|
|
@@ -11868,13 +11873,13 @@ textarea.form-control-lg {
|
|
|
11868
11873
|
color: #005ea5;
|
|
11869
11874
|
}
|
|
11870
11875
|
.card-header:hover {
|
|
11871
|
-
background-color: #005ea5;
|
|
11872
11876
|
color: #f8f8f8;
|
|
11877
|
+
background-color: #005ea5;
|
|
11873
11878
|
transition-duration: 0.3s;
|
|
11874
11879
|
}
|
|
11875
11880
|
.card-header:active {
|
|
11876
|
-
border-bottom: #000 groove;
|
|
11877
11881
|
background-color: gold;
|
|
11882
|
+
border-bottom: #000 groove;
|
|
11878
11883
|
transition: ease-in-out;
|
|
11879
11884
|
}
|
|
11880
11885
|
|
|
@@ -11945,24 +11950,24 @@ textarea.form-control-lg {
|
|
|
11945
11950
|
.loading-wheel-2 {
|
|
11946
11951
|
width: 1em;
|
|
11947
11952
|
height: 1em;
|
|
11948
|
-
border-radius: 50%;
|
|
11949
11953
|
margin: 40px;
|
|
11950
|
-
animation: loading-frames 1.1s infinite ease;
|
|
11951
11954
|
font-size: 8px;
|
|
11952
11955
|
text-indent: -9999em;
|
|
11956
|
+
border-radius: 50%;
|
|
11953
11957
|
transform: translateZ(0);
|
|
11958
|
+
animation: loading-frames 1.1s infinite ease;
|
|
11954
11959
|
}
|
|
11955
11960
|
|
|
11956
11961
|
.loading-wheel {
|
|
11957
11962
|
width: 1em;
|
|
11958
11963
|
height: 1em;
|
|
11959
|
-
border-radius: 50%;
|
|
11960
11964
|
margin: 40px;
|
|
11961
11965
|
margin-left: 25px;
|
|
11962
|
-
animation: loading-frames 1.1s infinite ease;
|
|
11963
11966
|
font-size: 8px;
|
|
11964
11967
|
text-indent: -9999em;
|
|
11968
|
+
border-radius: 50%;
|
|
11965
11969
|
transform: translateZ(0);
|
|
11970
|
+
animation: loading-frames 1.1s infinite ease;
|
|
11966
11971
|
}
|
|
11967
11972
|
|
|
11968
11973
|
.head-space {
|
package/dist/index.esm.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { Accordion as Accordion$1, createAll, Button as Button$1, Checkboxes as Checkboxes$1, ErrorSummary as ErrorSummary$1, Header as Header$1, Radios as Radios$1, SkipLink as SkipLink$1, Tabs as Tabs$1 } from 'govuk-frontend';
|
|
2
|
+
import { Accordion as Accordion$1, createAll, Button as Button$1, Checkboxes as Checkboxes$1, ErrorSummary as ErrorSummary$1, ExitThisPage as ExitThisPage$1, Header as Header$1, PasswordInput as PasswordInput$1, Radios as Radios$1, SkipLink as SkipLink$1, Tabs as Tabs$1 } from 'govuk-frontend';
|
|
3
3
|
import { Link, useLocation } from 'react-router-dom';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import React__default, { useContext, useMemo, createElement, Component, useState, useEffect } from 'react';
|
|
5
|
+
import React__default, { useContext, useMemo, createElement, Component, useState, useRef, useEffect } from 'react';
|
|
6
6
|
|
|
7
7
|
function ConfigureOverallAccordion($scope, config) {
|
|
8
8
|
if (JSON.stringify(config) === JSON.stringify({})) {
|
|
@@ -72,10 +72,10 @@ function ExtractAccordionConfigFromAttributes(remainingAttributes) {
|
|
|
72
72
|
return accordionConfig;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const Accordion = ({ headingLevel: HeadingLevel = "h2", items, className, id, ...attributes }) => {
|
|
75
|
+
const Accordion = ({ headingLevel: HeadingLevel = "h2", items = [], className, id, ...attributes }) => {
|
|
76
76
|
const remainingAttributes = { ...attributes };
|
|
77
77
|
ExtractAccordionConfigFromAttributes(remainingAttributes);
|
|
78
|
-
return (jsx("div", { ...remainingAttributes, id: id, className: `govuk-accordion ${className || ""}`, "data-module": "govuk-accordion", children: items.filter(Boolean).map((item, index) => (jsxs("div", { className: `govuk-accordion__section ${item.expanded ? "govuk-accordion__section--expanded" : ""}`, children: [jsxs("div", { className: "govuk-accordion__section-header", children: [jsx(HeadingLevel, { className: "govuk-accordion__section-heading", children: jsx("span", { className: "govuk-accordion__section-button", id: `${id}-heading-${index + 1}`, children: item.heading
|
|
78
|
+
return (jsx("div", { ...remainingAttributes, id: id, className: `govuk-accordion ${className || ""}`, "data-module": "govuk-accordion", children: items.filter(Boolean).map((item, index) => (jsxs("div", { className: `govuk-accordion__section ${item.expanded ? "govuk-accordion__section--expanded" : ""}`, children: [jsxs("div", { className: "govuk-accordion__section-header", children: [jsx(HeadingLevel, { className: "govuk-accordion__section-heading", children: jsx("span", { className: "govuk-accordion__section-button", id: `${id}-heading-${index + 1}`, children: item.heading?.children }) }), item.summary && (jsx("div", { className: "govuk-accordion__section-summary govuk-body", id: `${id}-summary-${index + 1}`, children: item.summary.children }))] }), jsx("div", { id: `${id}-content-${index + 1}`, className: "govuk-accordion__section-content", "aria-labelledby": `${id}-heading-${index + 1}`, children: item.content?.children })] }, item.reactListKey || index))) }));
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
const LinkWithRef = ({ children, to, href, forwardedRef = null, ...attributes }) => {
|
|
@@ -540,7 +540,7 @@ const CardTitle = /*#__PURE__*/React.forwardRef(({
|
|
|
540
540
|
});
|
|
541
541
|
CardTitle.displayName = 'CardTitle';
|
|
542
542
|
|
|
543
|
-
const Card = /*#__PURE__*/React.forwardRef(({
|
|
543
|
+
const Card$1 = /*#__PURE__*/React.forwardRef(({
|
|
544
544
|
bsPrefix,
|
|
545
545
|
className,
|
|
546
546
|
bg,
|
|
@@ -562,8 +562,8 @@ const Card = /*#__PURE__*/React.forwardRef(({
|
|
|
562
562
|
}) : children
|
|
563
563
|
});
|
|
564
564
|
});
|
|
565
|
-
Card.displayName = 'Card';
|
|
566
|
-
var Card
|
|
565
|
+
Card$1.displayName = 'Card';
|
|
566
|
+
var Card = Object.assign(Card$1, {
|
|
567
567
|
Img: CardImg,
|
|
568
568
|
Title: CardTitle,
|
|
569
569
|
Subtitle: CardSubtitle,
|
|
@@ -577,7 +577,7 @@ var Card$1 = Object.assign(Card, {
|
|
|
577
577
|
|
|
578
578
|
const CardColumn = (props) => {
|
|
579
579
|
const { body, header, link } = props;
|
|
580
|
-
return (jsx("div", { className: "col", children: jsxs(Card
|
|
580
|
+
return (jsx("div", { className: "col", children: jsxs(Card, { className: "card-styles", children: [jsx(Link, { to: link, children: jsx(Card.Header, { className: "card-header govuk-!-font-size-27 govuk-!-font-weight-bold", children: header }) }), jsx(Card.Body, { className: "govuk-!-font-size-19", children: body })] }) }));
|
|
581
581
|
};
|
|
582
582
|
|
|
583
583
|
const CardLayout = (props) => {
|
|
@@ -811,6 +811,24 @@ function ConfigureOverallErrorSummary($scope, config) {
|
|
|
811
811
|
createAll(ErrorSummary$1, config, $scope);
|
|
812
812
|
}
|
|
813
813
|
|
|
814
|
+
const ExitThisPage = ({ children = "Exit this page", className, redirectUrl = "https://www.bbc.co.uk/weather", activatedText, timedOutText, pressTwoMoreTimesText, pressOneMoreTimeText, ...attributes }) => {
|
|
815
|
+
const i18nProps = {};
|
|
816
|
+
if (activatedText)
|
|
817
|
+
i18nProps["data-i18n.activated"] = activatedText;
|
|
818
|
+
if (timedOutText)
|
|
819
|
+
i18nProps["data-i18n.timed-out"] = timedOutText;
|
|
820
|
+
if (pressTwoMoreTimesText)
|
|
821
|
+
i18nProps["data-i18n.press-two-more-times"] = pressTwoMoreTimesText;
|
|
822
|
+
if (pressOneMoreTimeText)
|
|
823
|
+
i18nProps["data-i18n.press-one-more-time"] = pressOneMoreTimeText;
|
|
824
|
+
return (jsx("div", { ...attributes, ...i18nProps, className: `govuk-exit-this-page${className ? ` ${className}` : ""}`, "data-module": "govuk-exit-this-page", children: jsxs("a", { href: redirectUrl, role: "button", draggable: false, className: "govuk-button govuk-button--warning govuk-exit-this-page__button govuk-js-exit-this-page-button", "data-module": "govuk-button", rel: "nofollow noreferrer", children: [jsx("span", { className: "govuk-visually-hidden", children: "Emergency" }), " ", children] }) }));
|
|
825
|
+
};
|
|
826
|
+
ExitThisPage.displayName = "ExitThisPage";
|
|
827
|
+
|
|
828
|
+
function ConfigureOverallExitThisPage($scope) {
|
|
829
|
+
createAll(ExitThisPage$1, {}, $scope);
|
|
830
|
+
}
|
|
831
|
+
|
|
814
832
|
const FileUpload = (props) => {
|
|
815
833
|
const { className, errorMessage, formGroup, hint, label, "aria-describedby": describedBy, id, ...attributes } = props;
|
|
816
834
|
let hintComponent;
|
|
@@ -935,6 +953,17 @@ const NotificationBanner = (props) => {
|
|
|
935
953
|
return (jsxs("div", { className: `govuk-notification-banner ${typeClass} ${className || ""}`, role: roleAttribute, "aria-labelledby": titleId, "data-module": "govuk-notification-banner", ...(disableAutoFocus ? { "data-disable-auto-focus": "true" } : {}), ...attributes, children: [jsx("div", { className: "govuk-notification-banner__header", children: jsx(HeadingLevel, { className: "govuk-notification-banner__title", id: titleId, children: title }) }), jsx("div", { className: "govuk-notification-banner__content", children: typeof children === "string" ? (jsx("p", { className: "govuk-notification-banner__heading", children: children })) : (children) })] }));
|
|
936
954
|
};
|
|
937
955
|
|
|
956
|
+
const SvgIcon = ({ type, ...props }) => (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: 15, height: 13, "aria-hidden": "true", ...props, children: jsx("path", { d: type === "next"
|
|
957
|
+
? "M8.107-.008L6.693 1.406 10.986 5.7H-2v2h12.896L6.71 11.676l1.378 1.449 6.744-6.406L8.107-.008z"
|
|
958
|
+
: "M6.594-.008-.133 6.72l6.744 6.406 1.377-1.449-4.185-3.977h12.896v-2H3.715l4.293-4.293L6.594-.008z" }) }));
|
|
959
|
+
|
|
960
|
+
const NavigationButton = ({ href, type, children, labelText, }) => {
|
|
961
|
+
const defaultText = type === "next" ? "Next" : "Previous";
|
|
962
|
+
const typeClass = type === "next" ? "next" : "prev";
|
|
963
|
+
const icon = (jsx(SvgIcon, { type: type, className: `govuk-pagination__icon govuk-pagination__icon--${typeClass}`, focusable: "false", "aria-hidden": "true" }));
|
|
964
|
+
return (jsxs("a", { className: "govuk-link govuk-pagination__link", href: href, rel: type, children: [type === "previous" && icon, jsxs("span", { className: "govuk-pagination__link-title", children: [children ?? defaultText, jsx("span", { className: "govuk-visually-hidden", children: " page" })] }), type === "next" && icon, labelText && (jsxs(Fragment, { children: [jsx("span", { className: "govuk-visually-hidden", children: ":" }), jsx("span", { className: "govuk-pagination__link-label", children: labelText })] }))] }));
|
|
965
|
+
};
|
|
966
|
+
|
|
938
967
|
const DOTS = "...";
|
|
939
968
|
const UsePagination = ({ totalCount, pageSize, siblingCount = 1, currentPage, }) => {
|
|
940
969
|
const range = (start, end) => {
|
|
@@ -970,29 +999,13 @@ const UsePagination = ({ totalCount, pageSize, siblingCount = 1, currentPage, })
|
|
|
970
999
|
return paginationRange;
|
|
971
1000
|
};
|
|
972
1001
|
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
: "M6.594-.008-.133 6.72l6.744 6.406 1.377-1.449-4.185-3.977h12.896v-2H3.715l4.293-4.293L6.594-.008z" }) }));
|
|
976
|
-
|
|
977
|
-
const NavigationButton = ({ isDisabled, onClick, children, name, type, }) => {
|
|
978
|
-
const typeClass = type === "next" ? type : "prev";
|
|
979
|
-
const Icon = (jsx(SvgIcon, { type: type, className: `govuk-pagination__icon govuk-pagination__icon--${typeClass}`, focusable: "false" }));
|
|
980
|
-
return isDisabled ? (jsxs("div", { rel: type, children: [type === "previous" && Icon, jsx("span", { className: "govuk-pagination__link-title", children: name }), type === "next" && Icon, children && (jsxs(Fragment, { children: [type === "previous" && jsx(Fragment, { children: "\u00A0" }), jsx("span", { className: "govuk-visually-hidden", children: ":" }), children, type === "next" && jsx(Fragment, { children: " \u00A0" })] }))] })) : (jsxs("a", { className: "govuk-link govuk-pagination__link", href: "#", rel: type, onClick: onClick, children: [type === "previous" && Icon, jsx("span", { className: "govuk-pagination__link-title", children: name }), type === "next" && Icon, children && (jsxs(Fragment, { children: [type === "previous" && jsx(Fragment, { children: "\u00A0\u00A0" }), jsx("span", { className: "govuk-visually-hidden", children: ":" }), children, type === "next" && jsx(Fragment, { children: "\u00A0\u00A0" })] }))] }));
|
|
981
|
-
};
|
|
982
|
-
|
|
983
|
-
const Pagination = ({ onPageChange, totalCount, siblingCount = 1, currentPage, pageSize, className, previousName = "Previous", nextName = "Next", previousChildren, nextChildren, }) => {
|
|
984
|
-
const paginationRange = UsePagination({
|
|
985
|
-
currentPage,
|
|
986
|
-
totalCount,
|
|
987
|
-
siblingCount,
|
|
988
|
-
pageSize,
|
|
989
|
-
});
|
|
990
|
-
if (!paginationRange || paginationRange.length < 2)
|
|
1002
|
+
const Pagination = ({ previous, next, items, className, }) => {
|
|
1003
|
+
if (!previous && !next && (!items || items.length === 0))
|
|
991
1004
|
return null;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const
|
|
995
|
-
return (jsxs(
|
|
1005
|
+
// Block layout is used when there are no numbered page items — govuk renders
|
|
1006
|
+
// the nav with an extra class and the link title with --decorated modifier.
|
|
1007
|
+
const isBlockLayout = !items || items.length === 0;
|
|
1008
|
+
return (jsxs("nav", { className: `govuk-pagination${isBlockLayout ? " govuk-pagination--block" : ""}${className ? ` ${className}` : ""}`, role: "navigation", "aria-label": "Pagination", children: [previous && (jsx("div", { className: "govuk-pagination__prev", children: jsx(NavigationButton, { type: "previous", ...previous }) })), items && items.length > 0 && (jsx("ul", { className: "govuk-pagination__list", children: items.map((item, index) => item.ellipsis ? (jsx("li", { className: "govuk-pagination__item govuk-pagination__item--ellipses", children: DOTS }, index)) : (jsx("li", { className: `govuk-pagination__item${item.current ? " govuk-pagination__item--current" : ""}`, children: jsx("a", { className: "govuk-link govuk-pagination__link", href: item.href ?? "#", "aria-label": `Page ${item.number}`, ...(item.current ? { "aria-current": "page" } : {}), children: item.number }) }, index))) })), next && (jsx("div", { className: "govuk-pagination__next", children: jsx(NavigationButton, { type: "next", ...next }) }))] }));
|
|
996
1009
|
};
|
|
997
1010
|
|
|
998
1011
|
const Panel = (props) => {
|
|
@@ -1001,6 +1014,49 @@ const Panel = (props) => {
|
|
|
1001
1014
|
return (jsxs("div", { className: `govuk-panel govuk-panel--confirmation ${className || ""}`, ...attributes, children: [jsx(HeadingLevel, { className: "govuk-panel__title", children: titleChildren }), innerHtml] }));
|
|
1002
1015
|
};
|
|
1003
1016
|
|
|
1017
|
+
const PasswordInput = React__default.forwardRef((props, ref) => {
|
|
1018
|
+
const { className, "aria-describedby": describedBy, errorMessage, formGroup, hint, label, name, id, autoComplete = "current-password", showPasswordText = "Show", hidePasswordText = "Hide", showPasswordAriaLabelText = "Show password", hidePasswordAriaLabelText = "Hide password", passwordShownAnnouncementText, passwordHiddenAnnouncementText, ...attributes } = props;
|
|
1019
|
+
let describedByValue = describedBy || "";
|
|
1020
|
+
let hintComponent = null;
|
|
1021
|
+
let errorMessageComponent = null;
|
|
1022
|
+
if (hint) {
|
|
1023
|
+
const hintId = `${id}-hint`;
|
|
1024
|
+
describedByValue += ` ${hintId}`;
|
|
1025
|
+
hintComponent = jsx(Hint, { ...hint, id: hintId });
|
|
1026
|
+
}
|
|
1027
|
+
if (errorMessage) {
|
|
1028
|
+
const errorId = id ? `${id}-error` : "";
|
|
1029
|
+
describedByValue += ` ${errorId}`;
|
|
1030
|
+
errorMessageComponent = jsx(ErrorMessage, { ...errorMessage, id: errorId });
|
|
1031
|
+
}
|
|
1032
|
+
// Build i18n data attributes for the govuk-frontend JS module
|
|
1033
|
+
const i18nProps = {};
|
|
1034
|
+
if (showPasswordText)
|
|
1035
|
+
i18nProps["data-i18n.show-password"] = showPasswordText;
|
|
1036
|
+
if (hidePasswordText)
|
|
1037
|
+
i18nProps["data-i18n.hide-password"] = hidePasswordText;
|
|
1038
|
+
if (showPasswordAriaLabelText)
|
|
1039
|
+
i18nProps["data-i18n.show-password-aria-label"] =
|
|
1040
|
+
showPasswordAriaLabelText;
|
|
1041
|
+
if (hidePasswordAriaLabelText)
|
|
1042
|
+
i18nProps["data-i18n.hide-password-aria-label"] =
|
|
1043
|
+
hidePasswordAriaLabelText;
|
|
1044
|
+
if (passwordShownAnnouncementText)
|
|
1045
|
+
i18nProps["data-i18n.password-shown-announcement"] =
|
|
1046
|
+
passwordShownAnnouncementText;
|
|
1047
|
+
if (passwordHiddenAnnouncementText)
|
|
1048
|
+
i18nProps["data-i18n.password-hidden-announcement"] =
|
|
1049
|
+
passwordHiddenAnnouncementText;
|
|
1050
|
+
// Label handles isPageHeading internally — no extra wrapper needed here
|
|
1051
|
+
const labelEl = jsx(Label, { ...label, htmlFor: id });
|
|
1052
|
+
return (jsxs("div", { className: `govuk-form-group${formGroup?.className ? ` ${formGroup.className}` : ""}${errorMessage ? " govuk-form-group--error" : ""} govuk-password-input`, "data-module": "govuk-password-input", ...i18nProps, children: [labelEl, hintComponent, errorMessageComponent, jsxs("div", { className: "govuk-input__wrapper govuk-password-input__wrapper", children: [jsx("input", { ref: ref, id: id, className: `govuk-input govuk-password-input__input govuk-js-password-input-input${className ? ` ${className}` : ""}${errorMessage ? " govuk-input--error" : ""}`, name: name || id, type: "password", spellCheck: false, autoComplete: autoComplete, autoCapitalize: "none", "aria-describedby": describedByValue.trim() || undefined, ...attributes }), jsx("button", { type: "button", className: "govuk-button govuk-button--secondary govuk-password-input__toggle govuk-js-password-input-toggle", "data-module": "govuk-button", "aria-controls": id, "aria-label": showPasswordAriaLabelText, hidden: true, children: showPasswordText })] })] }));
|
|
1053
|
+
});
|
|
1054
|
+
PasswordInput.displayName = "PasswordInput";
|
|
1055
|
+
|
|
1056
|
+
function ConfigureOverallPasswordInput($scope) {
|
|
1057
|
+
createAll(PasswordInput$1, {}, $scope);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1004
1060
|
function Slugify(str) {
|
|
1005
1061
|
str = str.replace(/^\s+|\s+$/g, ""); // trim leading/trailing white space
|
|
1006
1062
|
str = str.toLowerCase(); // convert string to lowercase
|
|
@@ -1011,28 +1067,281 @@ function Slugify(str) {
|
|
|
1011
1067
|
return str;
|
|
1012
1068
|
}
|
|
1013
1069
|
|
|
1070
|
+
/**
|
|
1071
|
+
* Convert a base64 string to an ArrayBuffer
|
|
1072
|
+
*/
|
|
1073
|
+
const _base64ToArrayBuffer = (base64) => {
|
|
1074
|
+
const binaryString = window.atob(base64);
|
|
1075
|
+
const len = binaryString.length;
|
|
1076
|
+
const bytes = new Uint8Array(len);
|
|
1077
|
+
for (let i = 0; i < len; i++) {
|
|
1078
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
1079
|
+
}
|
|
1080
|
+
return bytes.buffer;
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* Very small heuristic to detect a plain base64 string
|
|
1084
|
+
*/
|
|
1085
|
+
const isBase64 = (value) => /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/.test(value);
|
|
1086
|
+
/**
|
|
1087
|
+
* ResolvePDFSource
|
|
1088
|
+
*
|
|
1089
|
+
* Accepts a source that may be:
|
|
1090
|
+
* - a plain base64 string (PDF binary encoded as base64)
|
|
1091
|
+
* - a data: URL containing base64 (data:application/pdf;base64,...)
|
|
1092
|
+
* - an existing blob: URL (blob:...)
|
|
1093
|
+
* - a Blob/File object
|
|
1094
|
+
* - a regular URL string (http(s) or same-origin path)
|
|
1095
|
+
*
|
|
1096
|
+
* Returns a SourceDeterminer:
|
|
1097
|
+
* - { isBase64Source: true, source: string } when an object URL was created
|
|
1098
|
+
* - { isBase64Source: false, source: string } when source can be used directly
|
|
1099
|
+
*
|
|
1100
|
+
* If an object URL is created, callers should revoke it when no longer needed:
|
|
1101
|
+
* `URL.revokeObjectURL(source)`
|
|
1102
|
+
*/
|
|
1103
|
+
const ResolvePDFSource = (source) => {
|
|
1104
|
+
// Nothing provided
|
|
1105
|
+
if (source === undefined || source === null || source === "") {
|
|
1106
|
+
return { isBase64Source: false, source: "" };
|
|
1107
|
+
}
|
|
1108
|
+
// If source is already a Blob (File), create an object URL
|
|
1109
|
+
if (typeof source.arrayBuffer === "function" ||
|
|
1110
|
+
source instanceof Blob) {
|
|
1111
|
+
const blob = source;
|
|
1112
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1113
|
+
return { isBase64Source: true, source: objectUrl };
|
|
1114
|
+
}
|
|
1115
|
+
// If source is a string:
|
|
1116
|
+
if (typeof source === "string") {
|
|
1117
|
+
// If it's already a blob URL, return as-is
|
|
1118
|
+
if (source.startsWith("blob:")) {
|
|
1119
|
+
return { isBase64Source: false, source };
|
|
1120
|
+
}
|
|
1121
|
+
// If it's a data URL (data:application/pdf;base64,....)
|
|
1122
|
+
if (source.startsWith("data:")) {
|
|
1123
|
+
// data:[<mediatype>][;base64],<data>
|
|
1124
|
+
const commaIndex = source.indexOf(",");
|
|
1125
|
+
if (commaIndex > -1) {
|
|
1126
|
+
const meta = source.substring(0, commaIndex);
|
|
1127
|
+
const dataPart = source.substring(commaIndex + 1);
|
|
1128
|
+
// If it contains base64 marker, decode accordingly
|
|
1129
|
+
if (meta.indexOf(";base64") !== -1) {
|
|
1130
|
+
try {
|
|
1131
|
+
const arrayBuffer = _base64ToArrayBuffer(dataPart);
|
|
1132
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
1133
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1134
|
+
return { isBase64Source: true, source: objectUrl };
|
|
1135
|
+
}
|
|
1136
|
+
catch (err) {
|
|
1137
|
+
console.error("ResolvePDFSource: failed to convert data: URL to blob", err);
|
|
1138
|
+
// fallback to returning original data URL
|
|
1139
|
+
return { isBase64Source: false, source };
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
// Not base64-encoded data URL — return as is
|
|
1144
|
+
return { isBase64Source: false, source };
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
// If it's a plain base64 string (no data: prefix), convert
|
|
1149
|
+
if (isBase64(source)) {
|
|
1150
|
+
try {
|
|
1151
|
+
const arrayBuffer = _base64ToArrayBuffer(source);
|
|
1152
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
1153
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1154
|
+
return { isBase64Source: true, source: objectUrl };
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
console.error("ResolvePDFSource: failed to convert base64 to blob", err);
|
|
1158
|
+
return { isBase64Source: false, source };
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
// Otherwise treat as a normal URL (http(s) or same-origin path)
|
|
1162
|
+
return { isBase64Source: false, source };
|
|
1163
|
+
}
|
|
1164
|
+
// Fallback: convert to string
|
|
1165
|
+
try {
|
|
1166
|
+
const asString = String(source);
|
|
1167
|
+
return { isBase64Source: false, source: asString };
|
|
1168
|
+
}
|
|
1169
|
+
catch (err) {
|
|
1170
|
+
console.error("ResolvePDFSource: failed to convert source to string", err);
|
|
1171
|
+
return { isBase64Source: false, source: "" };
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1014
1175
|
const PDFViewer = (props) => {
|
|
1015
1176
|
const viewerRef = React__default.useRef(null);
|
|
1016
|
-
const { iframeId, src, viewerLocation, documentName, documentNameColour, backend, toolbar = "minimal", additionalBackendAttributes,
|
|
1177
|
+
const { iframeId, src, viewerLocation, documentName, documentNameColour, backend, toolbar = "minimal", additionalBackendAttributes,
|
|
1178
|
+
// optional: lets callers skip client-side fetch for remote resources
|
|
1179
|
+
disableClientFetch = false, ...attributes } = props;
|
|
1017
1180
|
const [loading, setLoading] = useState(false);
|
|
1181
|
+
// ref for object URL (either created by ResolvePDFSource or by us after fetching)
|
|
1182
|
+
const objectUrlRef = useRef(null);
|
|
1183
|
+
// did ResolvePDFSource create the object URL? (helps understanding where to revoke)
|
|
1184
|
+
const createdByResolveRef = useRef(false);
|
|
1185
|
+
// fetch abort controller for cross-origin remote fetches
|
|
1186
|
+
const fetchControllerRef = useRef(null);
|
|
1187
|
+
const isRemoteUrl = (url) => {
|
|
1188
|
+
if (!url)
|
|
1189
|
+
return false;
|
|
1190
|
+
// Check if it's an HTTP(S) URL
|
|
1191
|
+
return /^https?:\/\//i.test(url);
|
|
1192
|
+
};
|
|
1018
1193
|
useEffect(() => {
|
|
1194
|
+
let mounted = true;
|
|
1019
1195
|
setLoading(true);
|
|
1020
1196
|
const element = viewerRef.current;
|
|
1021
|
-
if (!element)
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1197
|
+
if (!element) {
|
|
1198
|
+
setLoading(false);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
// cleanup previous URL/fetch if any
|
|
1202
|
+
if (fetchControllerRef.current) {
|
|
1203
|
+
try {
|
|
1204
|
+
fetchControllerRef.current.abort();
|
|
1205
|
+
}
|
|
1206
|
+
catch (err) {
|
|
1207
|
+
console.error("PDFViewer: fetch abort error:", err);
|
|
1208
|
+
/* ignore */
|
|
1209
|
+
}
|
|
1210
|
+
fetchControllerRef.current = null;
|
|
1211
|
+
}
|
|
1212
|
+
if (objectUrlRef.current) {
|
|
1213
|
+
try {
|
|
1214
|
+
URL.revokeObjectURL(objectUrlRef.current);
|
|
1215
|
+
}
|
|
1216
|
+
catch (err) {
|
|
1217
|
+
console.error("PDFViewer: revokeObjectURL error:", err);
|
|
1218
|
+
}
|
|
1219
|
+
objectUrlRef.current = null;
|
|
1220
|
+
createdByResolveRef.current = false;
|
|
1221
|
+
}
|
|
1222
|
+
const init = async () => {
|
|
1223
|
+
let sourceToUse = src;
|
|
1224
|
+
// 1) Let ResolvePDFSource handle base64/data/blob inputs synchronously.
|
|
1225
|
+
try {
|
|
1226
|
+
const resolved = ResolvePDFSource(src);
|
|
1227
|
+
if (resolved?.source) {
|
|
1228
|
+
sourceToUse = resolved.source;
|
|
1229
|
+
if (resolved.isBase64Source) {
|
|
1230
|
+
// ResolvePDFSource created an object URL for us (data/blob/base64)
|
|
1231
|
+
objectUrlRef.current = resolved.source;
|
|
1232
|
+
createdByResolveRef.current = true;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
catch (err) {
|
|
1237
|
+
// If ResolvePDFSource throws, just ignore and fall back to src
|
|
1238
|
+
console.error("PDFViewer: ResolvePDFSource error:", err);
|
|
1239
|
+
}
|
|
1240
|
+
// 2) If ResolvePDFSource didn't create an object URL and the src is cross-origin HTTP(S),
|
|
1241
|
+
// attempt a client-side fetch to create an object URL (so viewer can load it safely).
|
|
1242
|
+
if (!objectUrlRef.current &&
|
|
1243
|
+
!disableClientFetch &&
|
|
1244
|
+
typeof src === "string" &&
|
|
1245
|
+
/^https?:\/\//i.test(src) &&
|
|
1246
|
+
isRemoteUrl(src)) {
|
|
1247
|
+
const controller = new AbortController();
|
|
1248
|
+
fetchControllerRef.current = controller;
|
|
1249
|
+
try {
|
|
1250
|
+
const resp = await fetch(src, {
|
|
1251
|
+
signal: controller.signal,
|
|
1252
|
+
credentials: "omit",
|
|
1253
|
+
});
|
|
1254
|
+
if (resp.ok) {
|
|
1255
|
+
const blob = await resp.blob();
|
|
1256
|
+
if (blob && blob.size > 0) {
|
|
1257
|
+
const obj = URL.createObjectURL(blob);
|
|
1258
|
+
objectUrlRef.current = obj;
|
|
1259
|
+
createdByResolveRef.current = false; // we created it via fetch
|
|
1260
|
+
sourceToUse = obj;
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
console.warn("PDFViewer: fetched blob is empty - falling back to original src");
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
else {
|
|
1267
|
+
console.warn(`PDFViewer: fetch returned ${resp.status} ${resp.statusText} for ${src}`);
|
|
1268
|
+
// fallback: keep using original src
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
catch (err) {
|
|
1272
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1273
|
+
// aborted, nothing to do
|
|
1274
|
+
if (!mounted)
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
console.error("PDFViewer: error fetching cross-origin PDF:", err);
|
|
1279
|
+
}
|
|
1280
|
+
// fallback to source as-is (original remote URL)
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
// 3) Prepare and call backend
|
|
1284
|
+
if (!mounted)
|
|
1285
|
+
return;
|
|
1286
|
+
const jsProps = {
|
|
1287
|
+
iframeId,
|
|
1288
|
+
source: sourceToUse,
|
|
1289
|
+
viewerLocation,
|
|
1290
|
+
documentName,
|
|
1291
|
+
documentNameColour,
|
|
1292
|
+
element,
|
|
1293
|
+
toolbar,
|
|
1294
|
+
...additionalBackendAttributes,
|
|
1295
|
+
};
|
|
1296
|
+
try {
|
|
1297
|
+
backend(jsProps);
|
|
1298
|
+
}
|
|
1299
|
+
catch (err) {
|
|
1300
|
+
console.error("PDFViewer: backend threw an error:", err);
|
|
1301
|
+
}
|
|
1302
|
+
finally {
|
|
1303
|
+
if (mounted)
|
|
1304
|
+
setLoading(false);
|
|
1305
|
+
}
|
|
1306
|
+
}; // end init
|
|
1307
|
+
init();
|
|
1308
|
+
return () => {
|
|
1309
|
+
mounted = false;
|
|
1310
|
+
// cancel any ongoing fetch
|
|
1311
|
+
if (fetchControllerRef.current) {
|
|
1312
|
+
try {
|
|
1313
|
+
fetchControllerRef.current.abort();
|
|
1314
|
+
}
|
|
1315
|
+
catch (err) {
|
|
1316
|
+
console.error("PDFViewer: fetch abort error during cleanup:", err);
|
|
1317
|
+
}
|
|
1318
|
+
fetchControllerRef.current = null;
|
|
1319
|
+
}
|
|
1320
|
+
// revoke any object URL we created
|
|
1321
|
+
if (objectUrlRef.current) {
|
|
1322
|
+
try {
|
|
1323
|
+
URL.revokeObjectURL(objectUrlRef.current);
|
|
1324
|
+
}
|
|
1325
|
+
catch (err) {
|
|
1326
|
+
console.error("PDFViewer: fetch abort error during cleanup:", err);
|
|
1327
|
+
}
|
|
1328
|
+
objectUrlRef.current = null;
|
|
1329
|
+
createdByResolveRef.current = false;
|
|
1330
|
+
}
|
|
1331
|
+
// Note: PDFViewerBackend also attaches an iframe to the element. If backend does not
|
|
1332
|
+
// remove previous iframes, you might want to clear element.children here.
|
|
1032
1333
|
};
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1334
|
+
// re-run when these inputs change
|
|
1335
|
+
}, [
|
|
1336
|
+
src,
|
|
1337
|
+
iframeId,
|
|
1338
|
+
viewerLocation,
|
|
1339
|
+
documentName,
|
|
1340
|
+
documentNameColour,
|
|
1341
|
+
toolbar,
|
|
1342
|
+
backend,
|
|
1343
|
+
disableClientFetch,
|
|
1344
|
+
]);
|
|
1036
1345
|
return (jsxs(Fragment, { children: [loading && jsx(Loading, { message: "Loading PDF Document" }), jsx("div", { ref: viewerRef, id: `viewer ${documentName ? Slugify(documentName) : ""}`, "data-testid": `viewer ${documentName ? Slugify(documentName) : ""}`, style: { width: "100%", height: "100%" }, ...attributes })] }));
|
|
1037
1346
|
};
|
|
1038
1347
|
|
|
@@ -1046,27 +1355,6 @@ const ObjectToQueryString = (queryParameters) => {
|
|
|
1046
1355
|
: "";
|
|
1047
1356
|
};
|
|
1048
1357
|
|
|
1049
|
-
const _base64ToArrayBuffer = (base64) => {
|
|
1050
|
-
const binaryString = window.atob(base64);
|
|
1051
|
-
const len = binaryString.length;
|
|
1052
|
-
const bytes = new Uint8Array(len);
|
|
1053
|
-
for (let i = 0; i < len; i++) {
|
|
1054
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
1055
|
-
}
|
|
1056
|
-
return bytes.buffer;
|
|
1057
|
-
};
|
|
1058
|
-
const isBase64 = (value) => /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/.test(value);
|
|
1059
|
-
const ResolvePDFSource = (source) => {
|
|
1060
|
-
const isBase64Source = isBase64(source);
|
|
1061
|
-
if (!isBase64Source) {
|
|
1062
|
-
return { isBase64Source, source };
|
|
1063
|
-
}
|
|
1064
|
-
const blob = new Blob([_base64ToArrayBuffer(source)], {
|
|
1065
|
-
type: "application/pdf",
|
|
1066
|
-
});
|
|
1067
|
-
return { isBase64Source, source: URL.createObjectURL(blob) };
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
1358
|
const PDFViewerBackend = ({ iframeId, source, viewerLocation = "/pdfjs-4.4.168-dist/web/viewer.html", documentName, documentNameColour = "black", toolbar = "minimal", element, ...remaining }) => {
|
|
1071
1359
|
const sourceDeterminer = ResolvePDFSource(source);
|
|
1072
1360
|
const iframe = document.createElement("iframe");
|
|
@@ -1083,6 +1371,8 @@ const PDFViewerBackend = ({ iframeId, source, viewerLocation = "/pdfjs-4.4.168-d
|
|
|
1083
1371
|
iframe.src = `${viewerLocation}${queryString}`;
|
|
1084
1372
|
iframe.style.width = "100%";
|
|
1085
1373
|
iframe.style.height = "94%";
|
|
1374
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-downloads");
|
|
1375
|
+
iframe.setAttribute("allow", "cross-origin-isolated");
|
|
1086
1376
|
if (sourceDeterminer.isBase64Source) {
|
|
1087
1377
|
iframe.addEventListener("beforeunload", (event) => {
|
|
1088
1378
|
event.preventDefault();
|
|
@@ -1292,5 +1582,5 @@ const WarningText = ({ children = "Warning message", className, ...attributes })
|
|
|
1292
1582
|
return (jsxs("div", { className: `govuk-warning-text ${className || ""}`, ...attributes, children: [jsx("span", { className: "govuk-warning-text__icon", "aria-hidden": "true", children: "!" }), jsxs("strong", { className: "govuk-warning-text__text", children: [jsx("span", { className: "govuk-visually-hidden", children: "Warning" }), children] })] }));
|
|
1293
1583
|
};
|
|
1294
1584
|
|
|
1295
|
-
export { Accordion, ActionLink, BackLink, Boolean$1 as Boolean, Breadcrumbs, Button, CardColumn, CardLayout, CharacterCount, Checkboxes as Checkbox, ConfigureOverallAccordion, ConfigureOverallButton, ConfigureOverallCheckboxes, ConfigureOverallErrorSummary, ConfigureOverallHeader, ConfigureOverallRadios, ConfigureOverallSkipLink, ConfigureOverallTabs, CookieBanner, DataNavigation, DateInput, Details, DifferenceNavigation, ErrorBoundary, ErrorMessage, ErrorSummary, ExtractAccordionConfigFromAttributes, Fieldset, FileUpload, Footer, Header, Hint, Input, InsetText, Label, Landing, LinkWithRef, Loading, Main, NotFoundPage, NotificationBanner, PDFViewer, PDFViewerBackend, Pagination, Panel, PhaseBanner, ProblemWithService, Radios, Select, ServiceNavigation, SkipLink, SummaryList, Table, Tabs, Tag, TaskList, Textarea, WarningInfo, WarningText };
|
|
1585
|
+
export { Accordion, ActionLink, BackLink, Boolean$1 as Boolean, Breadcrumbs, Button, CardColumn, CardLayout, CharacterCount, Checkboxes as Checkbox, ConfigureOverallAccordion, ConfigureOverallButton, ConfigureOverallCheckboxes, ConfigureOverallErrorSummary, ConfigureOverallExitThisPage, ConfigureOverallHeader, ConfigureOverallPasswordInput, ConfigureOverallRadios, ConfigureOverallSkipLink, ConfigureOverallTabs, CookieBanner, DOTS, DataNavigation, DateInput, Details, DifferenceNavigation, ErrorBoundary, ErrorMessage, ErrorSummary, ExitThisPage, ExtractAccordionConfigFromAttributes, Fieldset, FileUpload, Footer, Header, Hint, Input, InsetText, Label, Landing, LinkWithRef, Loading, Main, NotFoundPage, NotificationBanner, PDFViewer, PDFViewerBackend, Pagination, Panel, PasswordInput, PhaseBanner, ProblemWithService, Radios, Select, ServiceNavigation, SkipLink, SummaryList, Table, Tabs, Tag, TaskList, Textarea, UsePagination, WarningInfo, WarningText };
|
|
1296
1586
|
//# sourceMappingURL=index.esm.js.map
|