@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.cjs.js
CHANGED
|
@@ -92,10 +92,10 @@ function ExtractAccordionConfigFromAttributes(remainingAttributes) {
|
|
|
92
92
|
return accordionConfig;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
const Accordion = ({ headingLevel: HeadingLevel = "h2", items, className, id, ...attributes }) => {
|
|
95
|
+
const Accordion = ({ headingLevel: HeadingLevel = "h2", items = [], className, id, ...attributes }) => {
|
|
96
96
|
const remainingAttributes = { ...attributes };
|
|
97
97
|
ExtractAccordionConfigFromAttributes(remainingAttributes);
|
|
98
|
-
return (jsxRuntime.jsx("div", { ...remainingAttributes, id: id, className: `govuk-accordion ${className || ""}`, "data-module": "govuk-accordion", children: items.filter(Boolean).map((item, index) => (jsxRuntime.jsxs("div", { className: `govuk-accordion__section ${item.expanded ? "govuk-accordion__section--expanded" : ""}`, children: [jsxRuntime.jsxs("div", { className: "govuk-accordion__section-header", children: [jsxRuntime.jsx(HeadingLevel, { className: "govuk-accordion__section-heading", children: jsxRuntime.jsx("span", { className: "govuk-accordion__section-button", id: `${id}-heading-${index + 1}`, children: item.heading
|
|
98
|
+
return (jsxRuntime.jsx("div", { ...remainingAttributes, id: id, className: `govuk-accordion ${className || ""}`, "data-module": "govuk-accordion", children: items.filter(Boolean).map((item, index) => (jsxRuntime.jsxs("div", { className: `govuk-accordion__section ${item.expanded ? "govuk-accordion__section--expanded" : ""}`, children: [jsxRuntime.jsxs("div", { className: "govuk-accordion__section-header", children: [jsxRuntime.jsx(HeadingLevel, { className: "govuk-accordion__section-heading", children: jsxRuntime.jsx("span", { className: "govuk-accordion__section-button", id: `${id}-heading-${index + 1}`, children: item.heading?.children }) }), item.summary && (jsxRuntime.jsx("div", { className: "govuk-accordion__section-summary govuk-body", id: `${id}-summary-${index + 1}`, children: item.summary.children }))] }), jsxRuntime.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))) }));
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
const LinkWithRef = ({ children, to, href, forwardedRef = null, ...attributes }) => {
|
|
@@ -831,6 +831,24 @@ function ConfigureOverallErrorSummary($scope, config) {
|
|
|
831
831
|
govukFrontend.createAll(govukFrontend.ErrorSummary, config, $scope);
|
|
832
832
|
}
|
|
833
833
|
|
|
834
|
+
const ExitThisPage = ({ children = "Exit this page", className, redirectUrl = "https://www.bbc.co.uk/weather", activatedText, timedOutText, pressTwoMoreTimesText, pressOneMoreTimeText, ...attributes }) => {
|
|
835
|
+
const i18nProps = {};
|
|
836
|
+
if (activatedText)
|
|
837
|
+
i18nProps["data-i18n.activated"] = activatedText;
|
|
838
|
+
if (timedOutText)
|
|
839
|
+
i18nProps["data-i18n.timed-out"] = timedOutText;
|
|
840
|
+
if (pressTwoMoreTimesText)
|
|
841
|
+
i18nProps["data-i18n.press-two-more-times"] = pressTwoMoreTimesText;
|
|
842
|
+
if (pressOneMoreTimeText)
|
|
843
|
+
i18nProps["data-i18n.press-one-more-time"] = pressOneMoreTimeText;
|
|
844
|
+
return (jsxRuntime.jsx("div", { ...attributes, ...i18nProps, className: `govuk-exit-this-page${className ? ` ${className}` : ""}`, "data-module": "govuk-exit-this-page", children: jsxRuntime.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: [jsxRuntime.jsx("span", { className: "govuk-visually-hidden", children: "Emergency" }), " ", children] }) }));
|
|
845
|
+
};
|
|
846
|
+
ExitThisPage.displayName = "ExitThisPage";
|
|
847
|
+
|
|
848
|
+
function ConfigureOverallExitThisPage($scope) {
|
|
849
|
+
govukFrontend.createAll(govukFrontend.ExitThisPage, {}, $scope);
|
|
850
|
+
}
|
|
851
|
+
|
|
834
852
|
const FileUpload = (props) => {
|
|
835
853
|
const { className, errorMessage, formGroup, hint, label, "aria-describedby": describedBy, id, ...attributes } = props;
|
|
836
854
|
let hintComponent;
|
|
@@ -955,6 +973,17 @@ const NotificationBanner = (props) => {
|
|
|
955
973
|
return (jsxRuntime.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: [jsxRuntime.jsx("div", { className: "govuk-notification-banner__header", children: jsxRuntime.jsx(HeadingLevel, { className: "govuk-notification-banner__title", id: titleId, children: title }) }), jsxRuntime.jsx("div", { className: "govuk-notification-banner__content", children: typeof children === "string" ? (jsxRuntime.jsx("p", { className: "govuk-notification-banner__heading", children: children })) : (children) })] }));
|
|
956
974
|
};
|
|
957
975
|
|
|
976
|
+
const SvgIcon = ({ type, ...props }) => (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: 15, height: 13, "aria-hidden": "true", ...props, children: jsxRuntime.jsx("path", { d: type === "next"
|
|
977
|
+
? "M8.107-.008L6.693 1.406 10.986 5.7H-2v2h12.896L6.71 11.676l1.378 1.449 6.744-6.406L8.107-.008z"
|
|
978
|
+
: "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" }) }));
|
|
979
|
+
|
|
980
|
+
const NavigationButton = ({ href, type, children, labelText, }) => {
|
|
981
|
+
const defaultText = type === "next" ? "Next" : "Previous";
|
|
982
|
+
const typeClass = type === "next" ? "next" : "prev";
|
|
983
|
+
const icon = (jsxRuntime.jsx(SvgIcon, { type: type, className: `govuk-pagination__icon govuk-pagination__icon--${typeClass}`, focusable: "false", "aria-hidden": "true" }));
|
|
984
|
+
return (jsxRuntime.jsxs("a", { className: "govuk-link govuk-pagination__link", href: href, rel: type, children: [type === "previous" && icon, jsxRuntime.jsxs("span", { className: "govuk-pagination__link-title", children: [children ?? defaultText, jsxRuntime.jsx("span", { className: "govuk-visually-hidden", children: " page" })] }), type === "next" && icon, labelText && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "govuk-visually-hidden", children: ":" }), jsxRuntime.jsx("span", { className: "govuk-pagination__link-label", children: labelText })] }))] }));
|
|
985
|
+
};
|
|
986
|
+
|
|
958
987
|
const DOTS = "...";
|
|
959
988
|
const UsePagination = ({ totalCount, pageSize, siblingCount = 1, currentPage, }) => {
|
|
960
989
|
const range = (start, end) => {
|
|
@@ -990,29 +1019,13 @@ const UsePagination = ({ totalCount, pageSize, siblingCount = 1, currentPage, })
|
|
|
990
1019
|
return paginationRange;
|
|
991
1020
|
};
|
|
992
1021
|
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
: "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" }) }));
|
|
996
|
-
|
|
997
|
-
const NavigationButton = ({ isDisabled, onClick, children, name, type, }) => {
|
|
998
|
-
const typeClass = type === "next" ? type : "prev";
|
|
999
|
-
const Icon = (jsxRuntime.jsx(SvgIcon, { type: type, className: `govuk-pagination__icon govuk-pagination__icon--${typeClass}`, focusable: "false" }));
|
|
1000
|
-
return isDisabled ? (jsxRuntime.jsxs("div", { rel: type, children: [type === "previous" && Icon, jsxRuntime.jsx("span", { className: "govuk-pagination__link-title", children: name }), type === "next" && Icon, children && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [type === "previous" && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "\u00A0" }), jsxRuntime.jsx("span", { className: "govuk-visually-hidden", children: ":" }), children, type === "next" && jsxRuntime.jsx(jsxRuntime.Fragment, { children: " \u00A0" })] }))] })) : (jsxRuntime.jsxs("a", { className: "govuk-link govuk-pagination__link", href: "#", rel: type, onClick: onClick, children: [type === "previous" && Icon, jsxRuntime.jsx("span", { className: "govuk-pagination__link-title", children: name }), type === "next" && Icon, children && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [type === "previous" && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "\u00A0\u00A0" }), jsxRuntime.jsx("span", { className: "govuk-visually-hidden", children: ":" }), children, type === "next" && jsxRuntime.jsx(jsxRuntime.Fragment, { children: "\u00A0\u00A0" })] }))] }));
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
const Pagination = ({ onPageChange, totalCount, siblingCount = 1, currentPage, pageSize, className, previousName = "Previous", nextName = "Next", previousChildren, nextChildren, }) => {
|
|
1004
|
-
const paginationRange = UsePagination({
|
|
1005
|
-
currentPage,
|
|
1006
|
-
totalCount,
|
|
1007
|
-
siblingCount,
|
|
1008
|
-
pageSize,
|
|
1009
|
-
});
|
|
1010
|
-
if (!paginationRange || paginationRange.length < 2)
|
|
1022
|
+
const Pagination = ({ previous, next, items, className, }) => {
|
|
1023
|
+
if (!previous && !next && (!items || items.length === 0))
|
|
1011
1024
|
return null;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
return (jsxRuntime.jsxs(
|
|
1025
|
+
// Block layout is used when there are no numbered page items — govuk renders
|
|
1026
|
+
// the nav with an extra class and the link title with --decorated modifier.
|
|
1027
|
+
const isBlockLayout = !items || items.length === 0;
|
|
1028
|
+
return (jsxRuntime.jsxs("nav", { className: `govuk-pagination${isBlockLayout ? " govuk-pagination--block" : ""}${className ? ` ${className}` : ""}`, role: "navigation", "aria-label": "Pagination", children: [previous && (jsxRuntime.jsx("div", { className: "govuk-pagination__prev", children: jsxRuntime.jsx(NavigationButton, { type: "previous", ...previous }) })), items && items.length > 0 && (jsxRuntime.jsx("ul", { className: "govuk-pagination__list", children: items.map((item, index) => item.ellipsis ? (jsxRuntime.jsx("li", { className: "govuk-pagination__item govuk-pagination__item--ellipses", children: DOTS }, index)) : (jsxRuntime.jsx("li", { className: `govuk-pagination__item${item.current ? " govuk-pagination__item--current" : ""}`, children: jsxRuntime.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 && (jsxRuntime.jsx("div", { className: "govuk-pagination__next", children: jsxRuntime.jsx(NavigationButton, { type: "next", ...next }) }))] }));
|
|
1016
1029
|
};
|
|
1017
1030
|
|
|
1018
1031
|
const Panel = (props) => {
|
|
@@ -1021,6 +1034,49 @@ const Panel = (props) => {
|
|
|
1021
1034
|
return (jsxRuntime.jsxs("div", { className: `govuk-panel govuk-panel--confirmation ${className || ""}`, ...attributes, children: [jsxRuntime.jsx(HeadingLevel, { className: "govuk-panel__title", children: titleChildren }), innerHtml] }));
|
|
1022
1035
|
};
|
|
1023
1036
|
|
|
1037
|
+
const PasswordInput = React.forwardRef((props, ref) => {
|
|
1038
|
+
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;
|
|
1039
|
+
let describedByValue = describedBy || "";
|
|
1040
|
+
let hintComponent = null;
|
|
1041
|
+
let errorMessageComponent = null;
|
|
1042
|
+
if (hint) {
|
|
1043
|
+
const hintId = `${id}-hint`;
|
|
1044
|
+
describedByValue += ` ${hintId}`;
|
|
1045
|
+
hintComponent = jsxRuntime.jsx(Hint, { ...hint, id: hintId });
|
|
1046
|
+
}
|
|
1047
|
+
if (errorMessage) {
|
|
1048
|
+
const errorId = id ? `${id}-error` : "";
|
|
1049
|
+
describedByValue += ` ${errorId}`;
|
|
1050
|
+
errorMessageComponent = jsxRuntime.jsx(ErrorMessage, { ...errorMessage, id: errorId });
|
|
1051
|
+
}
|
|
1052
|
+
// Build i18n data attributes for the govuk-frontend JS module
|
|
1053
|
+
const i18nProps = {};
|
|
1054
|
+
if (showPasswordText)
|
|
1055
|
+
i18nProps["data-i18n.show-password"] = showPasswordText;
|
|
1056
|
+
if (hidePasswordText)
|
|
1057
|
+
i18nProps["data-i18n.hide-password"] = hidePasswordText;
|
|
1058
|
+
if (showPasswordAriaLabelText)
|
|
1059
|
+
i18nProps["data-i18n.show-password-aria-label"] =
|
|
1060
|
+
showPasswordAriaLabelText;
|
|
1061
|
+
if (hidePasswordAriaLabelText)
|
|
1062
|
+
i18nProps["data-i18n.hide-password-aria-label"] =
|
|
1063
|
+
hidePasswordAriaLabelText;
|
|
1064
|
+
if (passwordShownAnnouncementText)
|
|
1065
|
+
i18nProps["data-i18n.password-shown-announcement"] =
|
|
1066
|
+
passwordShownAnnouncementText;
|
|
1067
|
+
if (passwordHiddenAnnouncementText)
|
|
1068
|
+
i18nProps["data-i18n.password-hidden-announcement"] =
|
|
1069
|
+
passwordHiddenAnnouncementText;
|
|
1070
|
+
// Label handles isPageHeading internally — no extra wrapper needed here
|
|
1071
|
+
const labelEl = jsxRuntime.jsx(Label, { ...label, htmlFor: id });
|
|
1072
|
+
return (jsxRuntime.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, jsxRuntime.jsxs("div", { className: "govuk-input__wrapper govuk-password-input__wrapper", children: [jsxRuntime.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 }), jsxRuntime.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 })] })] }));
|
|
1073
|
+
});
|
|
1074
|
+
PasswordInput.displayName = "PasswordInput";
|
|
1075
|
+
|
|
1076
|
+
function ConfigureOverallPasswordInput($scope) {
|
|
1077
|
+
govukFrontend.createAll(govukFrontend.PasswordInput, {}, $scope);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1024
1080
|
function Slugify(str) {
|
|
1025
1081
|
str = str.replace(/^\s+|\s+$/g, ""); // trim leading/trailing white space
|
|
1026
1082
|
str = str.toLowerCase(); // convert string to lowercase
|
|
@@ -1031,28 +1087,281 @@ function Slugify(str) {
|
|
|
1031
1087
|
return str;
|
|
1032
1088
|
}
|
|
1033
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* Convert a base64 string to an ArrayBuffer
|
|
1092
|
+
*/
|
|
1093
|
+
const _base64ToArrayBuffer = (base64) => {
|
|
1094
|
+
const binaryString = window.atob(base64);
|
|
1095
|
+
const len = binaryString.length;
|
|
1096
|
+
const bytes = new Uint8Array(len);
|
|
1097
|
+
for (let i = 0; i < len; i++) {
|
|
1098
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
1099
|
+
}
|
|
1100
|
+
return bytes.buffer;
|
|
1101
|
+
};
|
|
1102
|
+
/**
|
|
1103
|
+
* Very small heuristic to detect a plain base64 string
|
|
1104
|
+
*/
|
|
1105
|
+
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);
|
|
1106
|
+
/**
|
|
1107
|
+
* ResolvePDFSource
|
|
1108
|
+
*
|
|
1109
|
+
* Accepts a source that may be:
|
|
1110
|
+
* - a plain base64 string (PDF binary encoded as base64)
|
|
1111
|
+
* - a data: URL containing base64 (data:application/pdf;base64,...)
|
|
1112
|
+
* - an existing blob: URL (blob:...)
|
|
1113
|
+
* - a Blob/File object
|
|
1114
|
+
* - a regular URL string (http(s) or same-origin path)
|
|
1115
|
+
*
|
|
1116
|
+
* Returns a SourceDeterminer:
|
|
1117
|
+
* - { isBase64Source: true, source: string } when an object URL was created
|
|
1118
|
+
* - { isBase64Source: false, source: string } when source can be used directly
|
|
1119
|
+
*
|
|
1120
|
+
* If an object URL is created, callers should revoke it when no longer needed:
|
|
1121
|
+
* `URL.revokeObjectURL(source)`
|
|
1122
|
+
*/
|
|
1123
|
+
const ResolvePDFSource = (source) => {
|
|
1124
|
+
// Nothing provided
|
|
1125
|
+
if (source === undefined || source === null || source === "") {
|
|
1126
|
+
return { isBase64Source: false, source: "" };
|
|
1127
|
+
}
|
|
1128
|
+
// If source is already a Blob (File), create an object URL
|
|
1129
|
+
if (typeof source.arrayBuffer === "function" ||
|
|
1130
|
+
source instanceof Blob) {
|
|
1131
|
+
const blob = source;
|
|
1132
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1133
|
+
return { isBase64Source: true, source: objectUrl };
|
|
1134
|
+
}
|
|
1135
|
+
// If source is a string:
|
|
1136
|
+
if (typeof source === "string") {
|
|
1137
|
+
// If it's already a blob URL, return as-is
|
|
1138
|
+
if (source.startsWith("blob:")) {
|
|
1139
|
+
return { isBase64Source: false, source };
|
|
1140
|
+
}
|
|
1141
|
+
// If it's a data URL (data:application/pdf;base64,....)
|
|
1142
|
+
if (source.startsWith("data:")) {
|
|
1143
|
+
// data:[<mediatype>][;base64],<data>
|
|
1144
|
+
const commaIndex = source.indexOf(",");
|
|
1145
|
+
if (commaIndex > -1) {
|
|
1146
|
+
const meta = source.substring(0, commaIndex);
|
|
1147
|
+
const dataPart = source.substring(commaIndex + 1);
|
|
1148
|
+
// If it contains base64 marker, decode accordingly
|
|
1149
|
+
if (meta.indexOf(";base64") !== -1) {
|
|
1150
|
+
try {
|
|
1151
|
+
const arrayBuffer = _base64ToArrayBuffer(dataPart);
|
|
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 data: URL to blob", err);
|
|
1158
|
+
// fallback to returning original data URL
|
|
1159
|
+
return { isBase64Source: false, source };
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
// Not base64-encoded data URL — return as is
|
|
1164
|
+
return { isBase64Source: false, source };
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
// If it's a plain base64 string (no data: prefix), convert
|
|
1169
|
+
if (isBase64(source)) {
|
|
1170
|
+
try {
|
|
1171
|
+
const arrayBuffer = _base64ToArrayBuffer(source);
|
|
1172
|
+
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
|
|
1173
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
1174
|
+
return { isBase64Source: true, source: objectUrl };
|
|
1175
|
+
}
|
|
1176
|
+
catch (err) {
|
|
1177
|
+
console.error("ResolvePDFSource: failed to convert base64 to blob", err);
|
|
1178
|
+
return { isBase64Source: false, source };
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
// Otherwise treat as a normal URL (http(s) or same-origin path)
|
|
1182
|
+
return { isBase64Source: false, source };
|
|
1183
|
+
}
|
|
1184
|
+
// Fallback: convert to string
|
|
1185
|
+
try {
|
|
1186
|
+
const asString = String(source);
|
|
1187
|
+
return { isBase64Source: false, source: asString };
|
|
1188
|
+
}
|
|
1189
|
+
catch (err) {
|
|
1190
|
+
console.error("ResolvePDFSource: failed to convert source to string", err);
|
|
1191
|
+
return { isBase64Source: false, source: "" };
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1034
1195
|
const PDFViewer = (props) => {
|
|
1035
1196
|
const viewerRef = React.useRef(null);
|
|
1036
|
-
const { iframeId, src, viewerLocation, documentName, documentNameColour, backend, toolbar = "minimal", additionalBackendAttributes,
|
|
1197
|
+
const { iframeId, src, viewerLocation, documentName, documentNameColour, backend, toolbar = "minimal", additionalBackendAttributes,
|
|
1198
|
+
// optional: lets callers skip client-side fetch for remote resources
|
|
1199
|
+
disableClientFetch = false, ...attributes } = props;
|
|
1037
1200
|
const [loading, setLoading] = React.useState(false);
|
|
1201
|
+
// ref for object URL (either created by ResolvePDFSource or by us after fetching)
|
|
1202
|
+
const objectUrlRef = React.useRef(null);
|
|
1203
|
+
// did ResolvePDFSource create the object URL? (helps understanding where to revoke)
|
|
1204
|
+
const createdByResolveRef = React.useRef(false);
|
|
1205
|
+
// fetch abort controller for cross-origin remote fetches
|
|
1206
|
+
const fetchControllerRef = React.useRef(null);
|
|
1207
|
+
const isRemoteUrl = (url) => {
|
|
1208
|
+
if (!url)
|
|
1209
|
+
return false;
|
|
1210
|
+
// Check if it's an HTTP(S) URL
|
|
1211
|
+
return /^https?:\/\//i.test(url);
|
|
1212
|
+
};
|
|
1038
1213
|
React.useEffect(() => {
|
|
1214
|
+
let mounted = true;
|
|
1039
1215
|
setLoading(true);
|
|
1040
1216
|
const element = viewerRef.current;
|
|
1041
|
-
if (!element)
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1217
|
+
if (!element) {
|
|
1218
|
+
setLoading(false);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
// cleanup previous URL/fetch if any
|
|
1222
|
+
if (fetchControllerRef.current) {
|
|
1223
|
+
try {
|
|
1224
|
+
fetchControllerRef.current.abort();
|
|
1225
|
+
}
|
|
1226
|
+
catch (err) {
|
|
1227
|
+
console.error("PDFViewer: fetch abort error:", err);
|
|
1228
|
+
/* ignore */
|
|
1229
|
+
}
|
|
1230
|
+
fetchControllerRef.current = null;
|
|
1231
|
+
}
|
|
1232
|
+
if (objectUrlRef.current) {
|
|
1233
|
+
try {
|
|
1234
|
+
URL.revokeObjectURL(objectUrlRef.current);
|
|
1235
|
+
}
|
|
1236
|
+
catch (err) {
|
|
1237
|
+
console.error("PDFViewer: revokeObjectURL error:", err);
|
|
1238
|
+
}
|
|
1239
|
+
objectUrlRef.current = null;
|
|
1240
|
+
createdByResolveRef.current = false;
|
|
1241
|
+
}
|
|
1242
|
+
const init = async () => {
|
|
1243
|
+
let sourceToUse = src;
|
|
1244
|
+
// 1) Let ResolvePDFSource handle base64/data/blob inputs synchronously.
|
|
1245
|
+
try {
|
|
1246
|
+
const resolved = ResolvePDFSource(src);
|
|
1247
|
+
if (resolved?.source) {
|
|
1248
|
+
sourceToUse = resolved.source;
|
|
1249
|
+
if (resolved.isBase64Source) {
|
|
1250
|
+
// ResolvePDFSource created an object URL for us (data/blob/base64)
|
|
1251
|
+
objectUrlRef.current = resolved.source;
|
|
1252
|
+
createdByResolveRef.current = true;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
catch (err) {
|
|
1257
|
+
// If ResolvePDFSource throws, just ignore and fall back to src
|
|
1258
|
+
console.error("PDFViewer: ResolvePDFSource error:", err);
|
|
1259
|
+
}
|
|
1260
|
+
// 2) If ResolvePDFSource didn't create an object URL and the src is cross-origin HTTP(S),
|
|
1261
|
+
// attempt a client-side fetch to create an object URL (so viewer can load it safely).
|
|
1262
|
+
if (!objectUrlRef.current &&
|
|
1263
|
+
!disableClientFetch &&
|
|
1264
|
+
typeof src === "string" &&
|
|
1265
|
+
/^https?:\/\//i.test(src) &&
|
|
1266
|
+
isRemoteUrl(src)) {
|
|
1267
|
+
const controller = new AbortController();
|
|
1268
|
+
fetchControllerRef.current = controller;
|
|
1269
|
+
try {
|
|
1270
|
+
const resp = await fetch(src, {
|
|
1271
|
+
signal: controller.signal,
|
|
1272
|
+
credentials: "omit",
|
|
1273
|
+
});
|
|
1274
|
+
if (resp.ok) {
|
|
1275
|
+
const blob = await resp.blob();
|
|
1276
|
+
if (blob && blob.size > 0) {
|
|
1277
|
+
const obj = URL.createObjectURL(blob);
|
|
1278
|
+
objectUrlRef.current = obj;
|
|
1279
|
+
createdByResolveRef.current = false; // we created it via fetch
|
|
1280
|
+
sourceToUse = obj;
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
console.warn("PDFViewer: fetched blob is empty - falling back to original src");
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
console.warn(`PDFViewer: fetch returned ${resp.status} ${resp.statusText} for ${src}`);
|
|
1288
|
+
// fallback: keep using original src
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
catch (err) {
|
|
1292
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1293
|
+
// aborted, nothing to do
|
|
1294
|
+
if (!mounted)
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
console.error("PDFViewer: error fetching cross-origin PDF:", err);
|
|
1299
|
+
}
|
|
1300
|
+
// fallback to source as-is (original remote URL)
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
// 3) Prepare and call backend
|
|
1304
|
+
if (!mounted)
|
|
1305
|
+
return;
|
|
1306
|
+
const jsProps = {
|
|
1307
|
+
iframeId,
|
|
1308
|
+
source: sourceToUse,
|
|
1309
|
+
viewerLocation,
|
|
1310
|
+
documentName,
|
|
1311
|
+
documentNameColour,
|
|
1312
|
+
element,
|
|
1313
|
+
toolbar,
|
|
1314
|
+
...additionalBackendAttributes,
|
|
1315
|
+
};
|
|
1316
|
+
try {
|
|
1317
|
+
backend(jsProps);
|
|
1318
|
+
}
|
|
1319
|
+
catch (err) {
|
|
1320
|
+
console.error("PDFViewer: backend threw an error:", err);
|
|
1321
|
+
}
|
|
1322
|
+
finally {
|
|
1323
|
+
if (mounted)
|
|
1324
|
+
setLoading(false);
|
|
1325
|
+
}
|
|
1326
|
+
}; // end init
|
|
1327
|
+
init();
|
|
1328
|
+
return () => {
|
|
1329
|
+
mounted = false;
|
|
1330
|
+
// cancel any ongoing fetch
|
|
1331
|
+
if (fetchControllerRef.current) {
|
|
1332
|
+
try {
|
|
1333
|
+
fetchControllerRef.current.abort();
|
|
1334
|
+
}
|
|
1335
|
+
catch (err) {
|
|
1336
|
+
console.error("PDFViewer: fetch abort error during cleanup:", err);
|
|
1337
|
+
}
|
|
1338
|
+
fetchControllerRef.current = null;
|
|
1339
|
+
}
|
|
1340
|
+
// revoke any object URL we created
|
|
1341
|
+
if (objectUrlRef.current) {
|
|
1342
|
+
try {
|
|
1343
|
+
URL.revokeObjectURL(objectUrlRef.current);
|
|
1344
|
+
}
|
|
1345
|
+
catch (err) {
|
|
1346
|
+
console.error("PDFViewer: fetch abort error during cleanup:", err);
|
|
1347
|
+
}
|
|
1348
|
+
objectUrlRef.current = null;
|
|
1349
|
+
createdByResolveRef.current = false;
|
|
1350
|
+
}
|
|
1351
|
+
// Note: PDFViewerBackend also attaches an iframe to the element. If backend does not
|
|
1352
|
+
// remove previous iframes, you might want to clear element.children here.
|
|
1052
1353
|
};
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1354
|
+
// re-run when these inputs change
|
|
1355
|
+
}, [
|
|
1356
|
+
src,
|
|
1357
|
+
iframeId,
|
|
1358
|
+
viewerLocation,
|
|
1359
|
+
documentName,
|
|
1360
|
+
documentNameColour,
|
|
1361
|
+
toolbar,
|
|
1362
|
+
backend,
|
|
1363
|
+
disableClientFetch,
|
|
1364
|
+
]);
|
|
1056
1365
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [loading && jsxRuntime.jsx(Loading, { message: "Loading PDF Document" }), jsxRuntime.jsx("div", { ref: viewerRef, id: `viewer ${documentName ? Slugify(documentName) : ""}`, "data-testid": `viewer ${documentName ? Slugify(documentName) : ""}`, style: { width: "100%", height: "100%" }, ...attributes })] }));
|
|
1057
1366
|
};
|
|
1058
1367
|
|
|
@@ -1066,27 +1375,6 @@ const ObjectToQueryString = (queryParameters) => {
|
|
|
1066
1375
|
: "";
|
|
1067
1376
|
};
|
|
1068
1377
|
|
|
1069
|
-
const _base64ToArrayBuffer = (base64) => {
|
|
1070
|
-
const binaryString = window.atob(base64);
|
|
1071
|
-
const len = binaryString.length;
|
|
1072
|
-
const bytes = new Uint8Array(len);
|
|
1073
|
-
for (let i = 0; i < len; i++) {
|
|
1074
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
1075
|
-
}
|
|
1076
|
-
return bytes.buffer;
|
|
1077
|
-
};
|
|
1078
|
-
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);
|
|
1079
|
-
const ResolvePDFSource = (source) => {
|
|
1080
|
-
const isBase64Source = isBase64(source);
|
|
1081
|
-
if (!isBase64Source) {
|
|
1082
|
-
return { isBase64Source, source };
|
|
1083
|
-
}
|
|
1084
|
-
const blob = new Blob([_base64ToArrayBuffer(source)], {
|
|
1085
|
-
type: "application/pdf",
|
|
1086
|
-
});
|
|
1087
|
-
return { isBase64Source, source: URL.createObjectURL(blob) };
|
|
1088
|
-
};
|
|
1089
|
-
|
|
1090
1378
|
const PDFViewerBackend = ({ iframeId, source, viewerLocation = "/pdfjs-4.4.168-dist/web/viewer.html", documentName, documentNameColour = "black", toolbar = "minimal", element, ...remaining }) => {
|
|
1091
1379
|
const sourceDeterminer = ResolvePDFSource(source);
|
|
1092
1380
|
const iframe = document.createElement("iframe");
|
|
@@ -1103,6 +1391,8 @@ const PDFViewerBackend = ({ iframeId, source, viewerLocation = "/pdfjs-4.4.168-d
|
|
|
1103
1391
|
iframe.src = `${viewerLocation}${queryString}`;
|
|
1104
1392
|
iframe.style.width = "100%";
|
|
1105
1393
|
iframe.style.height = "94%";
|
|
1394
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-downloads");
|
|
1395
|
+
iframe.setAttribute("allow", "cross-origin-isolated");
|
|
1106
1396
|
if (sourceDeterminer.isBase64Source) {
|
|
1107
1397
|
iframe.addEventListener("beforeunload", (event) => {
|
|
1108
1398
|
event.preventDefault();
|
|
@@ -1326,11 +1616,14 @@ exports.ConfigureOverallAccordion = ConfigureOverallAccordion;
|
|
|
1326
1616
|
exports.ConfigureOverallButton = ConfigureOverallButton;
|
|
1327
1617
|
exports.ConfigureOverallCheckboxes = ConfigureOverallCheckboxes;
|
|
1328
1618
|
exports.ConfigureOverallErrorSummary = ConfigureOverallErrorSummary;
|
|
1619
|
+
exports.ConfigureOverallExitThisPage = ConfigureOverallExitThisPage;
|
|
1329
1620
|
exports.ConfigureOverallHeader = ConfigureOverallHeader;
|
|
1621
|
+
exports.ConfigureOverallPasswordInput = ConfigureOverallPasswordInput;
|
|
1330
1622
|
exports.ConfigureOverallRadios = ConfigureOverallRadios;
|
|
1331
1623
|
exports.ConfigureOverallSkipLink = ConfigureOverallSkipLink;
|
|
1332
1624
|
exports.ConfigureOverallTabs = ConfigureOverallTabs;
|
|
1333
1625
|
exports.CookieBanner = CookieBanner;
|
|
1626
|
+
exports.DOTS = DOTS;
|
|
1334
1627
|
exports.DataNavigation = DataNavigation;
|
|
1335
1628
|
exports.DateInput = DateInput;
|
|
1336
1629
|
exports.Details = Details;
|
|
@@ -1338,6 +1631,7 @@ exports.DifferenceNavigation = DifferenceNavigation;
|
|
|
1338
1631
|
exports.ErrorBoundary = ErrorBoundary;
|
|
1339
1632
|
exports.ErrorMessage = ErrorMessage;
|
|
1340
1633
|
exports.ErrorSummary = ErrorSummary;
|
|
1634
|
+
exports.ExitThisPage = ExitThisPage;
|
|
1341
1635
|
exports.ExtractAccordionConfigFromAttributes = ExtractAccordionConfigFromAttributes;
|
|
1342
1636
|
exports.Fieldset = Fieldset;
|
|
1343
1637
|
exports.FileUpload = FileUpload;
|
|
@@ -1357,6 +1651,7 @@ exports.PDFViewer = PDFViewer;
|
|
|
1357
1651
|
exports.PDFViewerBackend = PDFViewerBackend;
|
|
1358
1652
|
exports.Pagination = Pagination;
|
|
1359
1653
|
exports.Panel = Panel;
|
|
1654
|
+
exports.PasswordInput = PasswordInput;
|
|
1360
1655
|
exports.PhaseBanner = PhaseBanner;
|
|
1361
1656
|
exports.ProblemWithService = ProblemWithService;
|
|
1362
1657
|
exports.Radios = Radios;
|
|
@@ -1369,6 +1664,7 @@ exports.Tabs = Tabs;
|
|
|
1369
1664
|
exports.Tag = Tag;
|
|
1370
1665
|
exports.TaskList = TaskList;
|
|
1371
1666
|
exports.Textarea = Textarea;
|
|
1667
|
+
exports.UsePagination = UsePagination;
|
|
1372
1668
|
exports.WarningInfo = WarningInfo;
|
|
1373
1669
|
exports.WarningText = WarningText;
|
|
1374
1670
|
//# sourceMappingURL=index.cjs.js.map
|