@genesislcap/foundation-react-utils 15.46.6-alpha-606571609.0 → 15.48.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/README.md +0 -51
- package/dist/custom-elements.json +7 -124
- package/dist/dts/index.d.ts +0 -3
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/index.js +0 -3
- package/dist/foundation-react-utils.api.json +0 -452
- package/dist/foundation-react-utils.d.ts +0 -115
- package/package.json +12 -12
- package/dist/dts/error-boundary/error-boundary-styles.d.ts +0 -6
- package/dist/dts/error-boundary/error-boundary-styles.d.ts.map +0 -1
- package/dist/dts/error-boundary/error-boundary.d.ts +0 -105
- package/dist/dts/error-boundary/error-boundary.d.ts.map +0 -1
- package/dist/dts/error-boundary/index.d.ts +0 -3
- package/dist/dts/error-boundary/index.d.ts.map +0 -1
- package/dist/esm/error-boundary/error-boundary-styles.js +0 -133
- package/dist/esm/error-boundary/error-boundary.js +0 -225
- package/dist/esm/error-boundary/index.js +0 -1
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, FC, ReactNode } from 'react';
|
|
2
|
-
import type { RootOptions } from 'react-dom/client';
|
|
3
|
-
/**
|
|
4
|
-
* Name of the logger the error boundaries log through. They log a caught error, followed by its
|
|
5
|
-
* reference ID, scope, title, tile registration and component stack, only when no `onError`
|
|
6
|
-
* handler is set or when the handler throws. `setupErrorMonitoring` from foundation-utils reports
|
|
7
|
-
* these logs, so apps using it don't need an `onError`.
|
|
8
|
-
* @public
|
|
9
|
-
*/
|
|
10
|
-
export declare const ERROR_BOUNDARY_LOGGER_NAME = "foundation-react-utils:error-boundary";
|
|
11
|
-
/**
|
|
12
|
-
* Where a boundary sits: around the whole app, or around a single tile.
|
|
13
|
-
* @public
|
|
14
|
-
*/
|
|
15
|
-
export type ErrorBoundaryScope = 'application' | 'tile';
|
|
16
|
-
/**
|
|
17
|
-
* What an error boundary passes to `onError` when it catches an error.
|
|
18
|
-
* @public
|
|
19
|
-
*/
|
|
20
|
-
export type ErrorBoundaryReport = {
|
|
21
|
-
error: Error;
|
|
22
|
-
componentStack: string;
|
|
23
|
-
/** Shown in the fallback UI, so support can match a user's screen to a reported event. */
|
|
24
|
-
referenceId: string;
|
|
25
|
-
scope: ErrorBoundaryScope;
|
|
26
|
-
title: string;
|
|
27
|
-
tileRegistration?: string;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Callback that receives every error a boundary catches, for example to send it to an
|
|
31
|
-
* error-monitoring service.
|
|
32
|
-
* @public
|
|
33
|
-
*/
|
|
34
|
-
export type ErrorBoundaryErrorHandler = (report: ErrorBoundaryReport) => void;
|
|
35
|
-
/**
|
|
36
|
-
* Props for {@link AppErrorBoundary}.
|
|
37
|
-
* @public
|
|
38
|
-
*/
|
|
39
|
-
export type AppErrorBoundaryProps = {
|
|
40
|
-
onError?: ErrorBoundaryErrorHandler;
|
|
41
|
-
children: ReactNode;
|
|
42
|
-
};
|
|
43
|
-
/**
|
|
44
|
-
* Props for {@link TileErrorBoundary}.
|
|
45
|
-
* @public
|
|
46
|
-
*/
|
|
47
|
-
export type TileErrorBoundaryProps = {
|
|
48
|
-
/** Human-readable tile name shown in the fallback UI. */
|
|
49
|
-
title: string;
|
|
50
|
-
/** The tile's key in the layout. */
|
|
51
|
-
tileRegistration: string;
|
|
52
|
-
onError?: ErrorBoundaryErrorHandler;
|
|
53
|
-
children: ReactNode;
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* Top-level error boundary. Wrap the whole app in one instance; use {@link TileErrorBoundary}
|
|
57
|
-
* inside it.
|
|
58
|
-
*
|
|
59
|
-
* @remarks
|
|
60
|
-
* Renders a fallback with a reference ID, copyable diagnostics and a Retry button that remounts
|
|
61
|
-
* the children. Each caught error is passed to `onError`. Without one, it's logged through the
|
|
62
|
-
* {@link ERROR_BOUNDARY_LOGGER_NAME} logger instead, which a global log reporter receives. Pair with {@link onCaughtError} on the root so React doesn't log the
|
|
63
|
-
* error a second time.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```tsx
|
|
67
|
-
* createRoot(el, { onCaughtError }).render(
|
|
68
|
-
* <AppErrorBoundary onError={({ error, ...extra }) => Sentry.captureException(error, { extra })}>
|
|
69
|
-
* <App />
|
|
70
|
-
* </AppErrorBoundary>,
|
|
71
|
-
* );
|
|
72
|
-
* ```
|
|
73
|
-
* @public
|
|
74
|
-
*/
|
|
75
|
-
export declare const AppErrorBoundary: FC<AppErrorBoundaryProps>;
|
|
76
|
-
/**
|
|
77
|
-
* Error boundary for a single tile, so a crash replaces only that tile with the fallback UI and
|
|
78
|
-
* the rest of the app keeps running.
|
|
79
|
-
* @public
|
|
80
|
-
*/
|
|
81
|
-
export declare const TileErrorBoundary: FC<TileErrorBoundaryProps>;
|
|
82
|
-
/**
|
|
83
|
-
* Wraps a component in a {@link TileErrorBoundary}.
|
|
84
|
-
*
|
|
85
|
-
* @param WrappedComponent - The tile component.
|
|
86
|
-
* @param title - Human-readable tile name shown in the fallback UI.
|
|
87
|
-
* @param tileRegistration - The tile's key in the layout.
|
|
88
|
-
* @param onError - Optional callback for caught errors.
|
|
89
|
-
* @public
|
|
90
|
-
*/
|
|
91
|
-
export declare const withTileErrorBoundary: <P extends object>(WrappedComponent: ComponentType<P>, title: string, tileRegistration: string, onError?: ErrorBoundaryErrorHandler) => FC<P>;
|
|
92
|
-
/**
|
|
93
|
-
* Root `onCaughtError` for `createRoot`.
|
|
94
|
-
*
|
|
95
|
-
* @remarks
|
|
96
|
-
* By default React 19 logs every error a boundary catches to `console.error`, before the
|
|
97
|
-
* boundary's `componentDidCatch` runs. Console-capturing monitoring tools then report that bare
|
|
98
|
-
* error first, and can drop or duplicate the boundary's own report. {@link AppErrorBoundary} and
|
|
99
|
-
* {@link TileErrorBoundary} report or log errors themselves, so this skips them. Errors caught by
|
|
100
|
-
* any other boundary are still logged to `console.error`.
|
|
101
|
-
*
|
|
102
|
-
* @public
|
|
103
|
-
*/
|
|
104
|
-
export declare const onCaughtError: NonNullable<RootOptions['onCaughtError']>;
|
|
105
|
-
//# sourceMappingURL=error-boundary.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../../src/error-boundary/error-boundary.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAa,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,0CAA0C,CAAC;AAIlF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE9E;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,yBAAyB,CAAC;IACpC,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,yBAAyB,CAAC;IACpC,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAwPF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CAItD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAcxD,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,MAAM,EACpD,kBAAkB,aAAa,CAAC,CAAC,CAAC,EAClC,OAAO,MAAM,EACb,kBAAkB,MAAM,EACxB,UAAU,yBAAyB,KAClC,EAAE,CAAC,CAAC,CAUN,CAAC;AAOF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,WAAW,CAAC,eAAe,CAAC,CAKnE,CAAC"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
export { AppErrorBoundary, ERROR_BOUNDARY_LOGGER_NAME, onCaughtError, TileErrorBoundary, withTileErrorBoundary, } from './error-boundary';
|
|
2
|
-
export type { AppErrorBoundaryProps, ErrorBoundaryErrorHandler, ErrorBoundaryReport, ErrorBoundaryScope, TileErrorBoundaryProps, } from './error-boundary';
|
|
3
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/error-boundary/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,0BAA0B,EAC1B,aAAa,EACb,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fallback UI styles. Colours and fonts read `--eb-*` custom properties, so an app can theme the
|
|
3
|
-
* fallback by setting them on any ancestor.
|
|
4
|
-
*/
|
|
5
|
-
export const errorBoundaryStyles = `
|
|
6
|
-
.error-boundary {
|
|
7
|
-
box-sizing: border-box;
|
|
8
|
-
display: flex;
|
|
9
|
-
flex-direction: column;
|
|
10
|
-
gap: 12px;
|
|
11
|
-
align-items: flex-start;
|
|
12
|
-
width: 100%;
|
|
13
|
-
height: 100%;
|
|
14
|
-
padding: 16px;
|
|
15
|
-
background: var(--eb-bg, #f6f8fb);
|
|
16
|
-
color: var(--eb-text, #18212f);
|
|
17
|
-
font-family: var(--eb-font, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.error-boundary__top,
|
|
21
|
-
.error-boundary__title,
|
|
22
|
-
.error-boundary__subtitle,
|
|
23
|
-
.error-boundary__details,
|
|
24
|
-
.error-boundary__actions,
|
|
25
|
-
.error-boundary__manual-copy-help {
|
|
26
|
-
width: 100%;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.error-boundary__top {
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: space-between;
|
|
33
|
-
gap: 10px;
|
|
34
|
-
flex-wrap: wrap;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.error-boundary__status {
|
|
38
|
-
display: inline-flex;
|
|
39
|
-
align-items: center;
|
|
40
|
-
border: 1px solid var(--eb-error, #c53d3d);
|
|
41
|
-
border-radius: 999px;
|
|
42
|
-
padding: 5px 10px;
|
|
43
|
-
text-transform: uppercase;
|
|
44
|
-
letter-spacing: 0.13em;
|
|
45
|
-
font-size: 11px;
|
|
46
|
-
font-weight: 700;
|
|
47
|
-
color: var(--eb-error, #c53d3d);
|
|
48
|
-
background: var(--eb-error-bg, #fff4f4);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.error-boundary__reference {
|
|
52
|
-
font-family: var(--eb-mono-font, 'Menlo', 'Consolas', 'Liberation Mono', monospace);
|
|
53
|
-
font-size: 11px;
|
|
54
|
-
letter-spacing: 0.08em;
|
|
55
|
-
color: var(--eb-text-muted, #5f6f84);
|
|
56
|
-
text-transform: uppercase;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.error-boundary__title {
|
|
60
|
-
margin: 0;
|
|
61
|
-
font-size: clamp(22px, 3vw, 30px);
|
|
62
|
-
line-height: 1.15;
|
|
63
|
-
font-weight: 700;
|
|
64
|
-
text-wrap: balance;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.error-boundary__subtitle {
|
|
68
|
-
margin: 0;
|
|
69
|
-
max-width: 80ch;
|
|
70
|
-
line-height: 1.45;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.error-boundary__details {
|
|
74
|
-
box-sizing: border-box;
|
|
75
|
-
min-height: 190px;
|
|
76
|
-
max-height: 360px;
|
|
77
|
-
padding: 12px;
|
|
78
|
-
border: 1px solid var(--eb-border, #d3dae6);
|
|
79
|
-
border-radius: 10px;
|
|
80
|
-
background: var(--eb-surface, #ffffff);
|
|
81
|
-
color: inherit;
|
|
82
|
-
font-family: var(--eb-mono-font, 'Menlo', 'Consolas', 'Liberation Mono', monospace);
|
|
83
|
-
font-size: 12px;
|
|
84
|
-
line-height: 1.5;
|
|
85
|
-
resize: vertical;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.error-boundary__actions {
|
|
89
|
-
display: flex;
|
|
90
|
-
gap: 10px;
|
|
91
|
-
flex-wrap: wrap;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.error-boundary__button {
|
|
95
|
-
border: 1px solid var(--eb-accent, #2f6fed);
|
|
96
|
-
border-radius: 999px;
|
|
97
|
-
background: var(--eb-accent, #2f6fed);
|
|
98
|
-
color: var(--eb-accent-foreground, #ffffff);
|
|
99
|
-
cursor: pointer;
|
|
100
|
-
padding: 9px 16px;
|
|
101
|
-
font: inherit;
|
|
102
|
-
font-size: 12px;
|
|
103
|
-
font-weight: 700;
|
|
104
|
-
letter-spacing: 0.04em;
|
|
105
|
-
text-transform: uppercase;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.error-boundary__button:hover {
|
|
109
|
-
background: var(--eb-accent-hover, #255dcc);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.error-boundary__button--secondary {
|
|
113
|
-
border-color: var(--eb-border, #d3dae6);
|
|
114
|
-
background: var(--eb-surface, #ffffff);
|
|
115
|
-
color: inherit;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.error-boundary__button--secondary:hover {
|
|
119
|
-
background: var(--eb-surface-hover, #eef2f8);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.error-boundary__button:focus-visible {
|
|
123
|
-
outline: 2px solid var(--eb-focus, #2f6fed);
|
|
124
|
-
outline-offset: 2px;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.error-boundary__manual-copy-help {
|
|
128
|
-
margin: 2px 0 0;
|
|
129
|
-
font-size: 13px;
|
|
130
|
-
color: var(--eb-text-muted, #5f6f84);
|
|
131
|
-
line-height: 1.4;
|
|
132
|
-
}
|
|
133
|
-
`;
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
var _a;
|
|
2
|
-
import { __awaiter } from "tslib";
|
|
3
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
-
import { createLogger } from '@genesislcap/foundation-logger';
|
|
5
|
-
import { Component, useState } from 'react';
|
|
6
|
-
import { errorBoundaryStyles } from './error-boundary-styles';
|
|
7
|
-
/**
|
|
8
|
-
* Name of the logger the error boundaries log through. They log a caught error, followed by its
|
|
9
|
-
* reference ID, scope, title, tile registration and component stack, only when no `onError`
|
|
10
|
-
* handler is set or when the handler throws. `setupErrorMonitoring` from foundation-utils reports
|
|
11
|
-
* these logs, so apps using it don't need an `onError`.
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
export const ERROR_BOUNDARY_LOGGER_NAME = 'foundation-react-utils:error-boundary';
|
|
15
|
-
const logger = createLogger(ERROR_BOUNDARY_LOGGER_NAME);
|
|
16
|
-
const initialBoundaryState = {
|
|
17
|
-
error: null,
|
|
18
|
-
componentStack: '',
|
|
19
|
-
capturedAt: '',
|
|
20
|
-
referenceId: '',
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Marks instances of these boundaries. A brand rather than `instanceof`, so {@link onCaughtError}
|
|
24
|
-
* still recognises a boundary from another copy of this package, such as one in a federated remote.
|
|
25
|
-
*/
|
|
26
|
-
const BOUNDARY_BRAND = Symbol.for('@genesislcap/foundation-react-utils:error-boundary');
|
|
27
|
-
const REPORTS_GLOBAL = '__GENESIS_ERROR_BOUNDARY_REPORTS__';
|
|
28
|
-
const MAX_REPORTS = 20;
|
|
29
|
-
const COPIED_RESET_MS = 2000;
|
|
30
|
-
const REFERENCE_RANDOM_LENGTH = 6;
|
|
31
|
-
const BASE36 = 36;
|
|
32
|
-
const toError = (value) => {
|
|
33
|
-
if (value instanceof Error) {
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
if (typeof value === 'string') {
|
|
37
|
-
return new Error(value);
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
return new Error(JSON.stringify(value));
|
|
41
|
-
}
|
|
42
|
-
catch (_b) {
|
|
43
|
-
return new Error('Unknown non-serializable error');
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
const createReferenceId = () => {
|
|
47
|
-
const timestamp = new Date().toISOString().split(':').join('-');
|
|
48
|
-
const random = Math.random()
|
|
49
|
-
.toString(BASE36)
|
|
50
|
-
.slice(2, 2 + REFERENCE_RANDOM_LENGTH)
|
|
51
|
-
.toUpperCase();
|
|
52
|
-
return `APP-${timestamp}-${random}`;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Keeps the most recent reports on `window` for dev tooling (the Genesis Create preview reads them).
|
|
56
|
-
*/
|
|
57
|
-
const recordReport = (error, componentStack) => {
|
|
58
|
-
var _b, _c;
|
|
59
|
-
const host = globalThis;
|
|
60
|
-
const reports = ((_b = host[REPORTS_GLOBAL]) !== null && _b !== void 0 ? _b : (host[REPORTS_GLOBAL] = []));
|
|
61
|
-
reports.push({
|
|
62
|
-
message: error.message,
|
|
63
|
-
stack: (_c = error.stack) !== null && _c !== void 0 ? _c : null,
|
|
64
|
-
componentStack: componentStack || null,
|
|
65
|
-
ts: Date.now(),
|
|
66
|
-
});
|
|
67
|
-
if (reports.length > MAX_REPORTS) {
|
|
68
|
-
reports.shift();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const ErrorBoundaryFallback = ({ scope, referenceId, title, subtitle, details, onRetry, }) => {
|
|
72
|
-
const [copied, setCopied] = useState(false);
|
|
73
|
-
const [copyHelpVisible, setCopyHelpVisible] = useState(false);
|
|
74
|
-
const handleCopy = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
-
try {
|
|
76
|
-
yield navigator.clipboard.writeText(details);
|
|
77
|
-
setCopied(true);
|
|
78
|
-
setCopyHelpVisible(false);
|
|
79
|
-
window.setTimeout(() => setCopied(false), COPIED_RESET_MS);
|
|
80
|
-
}
|
|
81
|
-
catch (_b) {
|
|
82
|
-
setCopyHelpVisible(true);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
return (_jsxs("section", { className: "error-boundary", role: "alert", "data-test-id": "error-boundary", children: [_jsx("style", { children: errorBoundaryStyles }), _jsxs("div", { className: "error-boundary__top", children: [_jsx("span", { className: "error-boundary__status", children: scope === 'application' ? 'Application incident' : 'Tile incident' }), _jsxs("span", { className: "error-boundary__reference", "data-test-id": "error-boundary-reference", children: ["Ref: ", referenceId] })] }), _jsx("h2", { className: "error-boundary__title", children: title }), _jsx("p", { className: "error-boundary__subtitle", children: subtitle }), _jsx("textarea", { className: "error-boundary__details", readOnly: true, value: details, "aria-label": "Error diagnostics details", "data-test-id": "error-boundary-details" }), _jsxs("div", { className: "error-boundary__actions", children: [_jsx("button", { type: "button", className: "error-boundary__button", onClick: onRetry, "data-test-id": "error-boundary-retry", children: "Retry" }), _jsx("button", { type: "button", className: "error-boundary__button error-boundary__button--secondary", onClick: handleCopy, "data-test-id": "error-boundary-copy", children: copied ? 'Copied' : 'Copy diagnostics' })] }), copyHelpVisible ? (_jsx("p", { className: "error-boundary__manual-copy-help", role: "status", children: "Clipboard access is blocked in this browser context. Select the diagnostics text and copy it manually." })) : null] }));
|
|
86
|
-
};
|
|
87
|
-
class BaseErrorBoundary extends Component {
|
|
88
|
-
constructor() {
|
|
89
|
-
super(...arguments);
|
|
90
|
-
this[_a] = true;
|
|
91
|
-
this.state = initialBoundaryState;
|
|
92
|
-
this.handleRetry = () => {
|
|
93
|
-
this.setState(initialBoundaryState);
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
static getDerivedStateFromError(error) {
|
|
97
|
-
return {
|
|
98
|
-
error: toError(error),
|
|
99
|
-
capturedAt: new Date().toISOString(),
|
|
100
|
-
referenceId: createReferenceId(),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
componentDidCatch(error, errorInfo) {
|
|
104
|
-
var _b;
|
|
105
|
-
const { scope, title, tileRegistration, onError } = this.props;
|
|
106
|
-
const normalized = toError(error);
|
|
107
|
-
const componentStack = (_b = errorInfo.componentStack) !== null && _b !== void 0 ? _b : '';
|
|
108
|
-
const { referenceId } = this.state;
|
|
109
|
-
this.setState({ componentStack });
|
|
110
|
-
recordReport(normalized, componentStack);
|
|
111
|
-
// The error comes first so a global log reporter can report it as an exception, with the
|
|
112
|
-
// context object attached (see `setupErrorMonitoring` in foundation-utils).
|
|
113
|
-
const logCaughtError = () => logger.error(`[${scope}] error boundary captured an error`, normalized, {
|
|
114
|
-
referenceId,
|
|
115
|
-
scope,
|
|
116
|
-
title,
|
|
117
|
-
tileRegistration,
|
|
118
|
-
componentStack,
|
|
119
|
-
});
|
|
120
|
-
// With a handler set, the handler reports the error, so logging it as well would send it to
|
|
121
|
-
// any global log reporter a second time.
|
|
122
|
-
if (!onError) {
|
|
123
|
-
logCaughtError();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
onError({ error: normalized, componentStack, referenceId, scope, title, tileRegistration });
|
|
128
|
-
}
|
|
129
|
-
catch (handlerError) {
|
|
130
|
-
// A failing reporter must not take down the fallback UI, or lose the original error.
|
|
131
|
-
logCaughtError();
|
|
132
|
-
logger.error(`[${scope}] error boundary onError handler threw`, handlerError);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
render() {
|
|
136
|
-
var _b;
|
|
137
|
-
const { error, referenceId, capturedAt, componentStack } = this.state;
|
|
138
|
-
if (!error) {
|
|
139
|
-
return this.props.children;
|
|
140
|
-
}
|
|
141
|
-
const { scope, title, tileRegistration } = this.props;
|
|
142
|
-
const diagnostics = [
|
|
143
|
-
`Reference: ${referenceId}`,
|
|
144
|
-
`Scope: ${scope}`,
|
|
145
|
-
`Title: ${title}`,
|
|
146
|
-
`Tile registration: ${tileRegistration !== null && tileRegistration !== void 0 ? tileRegistration : 'N/A'}`,
|
|
147
|
-
`Captured at: ${capturedAt}`,
|
|
148
|
-
`URL: ${window.location.href}`,
|
|
149
|
-
`User agent: ${window.navigator.userAgent}`,
|
|
150
|
-
`Error name: ${error.name}`,
|
|
151
|
-
`Error message: ${error.message}`,
|
|
152
|
-
'Error stack:',
|
|
153
|
-
(_b = error.stack) !== null && _b !== void 0 ? _b : 'No stack available',
|
|
154
|
-
'Component stack:',
|
|
155
|
-
componentStack || 'No component stack available',
|
|
156
|
-
].join('\n');
|
|
157
|
-
const isApp = scope === 'application';
|
|
158
|
-
return (_jsx(ErrorBoundaryFallback, { scope: scope, referenceId: referenceId, title: isApp ? 'Something went wrong' : `Something went wrong in "${title}"`, subtitle: isApp
|
|
159
|
-
? 'The app hit an unexpected error. You can retry or copy the diagnostics.'
|
|
160
|
-
: 'This tile crashed, but the rest of the application keeps running. Retry or copy the diagnostics.', details: diagnostics, onRetry: this.handleRetry }));
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
_a = BOUNDARY_BRAND;
|
|
164
|
-
/**
|
|
165
|
-
* Top-level error boundary. Wrap the whole app in one instance; use {@link TileErrorBoundary}
|
|
166
|
-
* inside it.
|
|
167
|
-
*
|
|
168
|
-
* @remarks
|
|
169
|
-
* Renders a fallback with a reference ID, copyable diagnostics and a Retry button that remounts
|
|
170
|
-
* the children. Each caught error is passed to `onError`. Without one, it's logged through the
|
|
171
|
-
* {@link ERROR_BOUNDARY_LOGGER_NAME} logger instead, which a global log reporter receives. Pair with {@link onCaughtError} on the root so React doesn't log the
|
|
172
|
-
* error a second time.
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* ```tsx
|
|
176
|
-
* createRoot(el, { onCaughtError }).render(
|
|
177
|
-
* <AppErrorBoundary onError={({ error, ...extra }) => Sentry.captureException(error, { extra })}>
|
|
178
|
-
* <App />
|
|
179
|
-
* </AppErrorBoundary>,
|
|
180
|
-
* );
|
|
181
|
-
* ```
|
|
182
|
-
* @public
|
|
183
|
-
*/
|
|
184
|
-
export const AppErrorBoundary = ({ onError, children }) => (_jsx(BaseErrorBoundary, { scope: "application", title: "Application", onError: onError, children: children }));
|
|
185
|
-
/**
|
|
186
|
-
* Error boundary for a single tile, so a crash replaces only that tile with the fallback UI and
|
|
187
|
-
* the rest of the app keeps running.
|
|
188
|
-
* @public
|
|
189
|
-
*/
|
|
190
|
-
export const TileErrorBoundary = ({ title, tileRegistration, onError, children, }) => (_jsx(BaseErrorBoundary, { scope: "tile", title: title, tileRegistration: tileRegistration, onError: onError, children: children }));
|
|
191
|
-
/**
|
|
192
|
-
* Wraps a component in a {@link TileErrorBoundary}.
|
|
193
|
-
*
|
|
194
|
-
* @param WrappedComponent - The tile component.
|
|
195
|
-
* @param title - Human-readable tile name shown in the fallback UI.
|
|
196
|
-
* @param tileRegistration - The tile's key in the layout.
|
|
197
|
-
* @param onError - Optional callback for caught errors.
|
|
198
|
-
* @public
|
|
199
|
-
*/
|
|
200
|
-
export const withTileErrorBoundary = (WrappedComponent, title, tileRegistration, onError) => {
|
|
201
|
-
const WithTileErrorBoundary = (props) => (_jsx(TileErrorBoundary, { title: title, tileRegistration: tileRegistration, onError: onError, children: _jsx(WrappedComponent, Object.assign({}, props)) }));
|
|
202
|
-
WithTileErrorBoundary.displayName = `WithTileErrorBoundary(${title}:${tileRegistration}:${WrappedComponent.displayName || WrappedComponent.name || 'TileComponent'})`;
|
|
203
|
-
return WithTileErrorBoundary;
|
|
204
|
-
};
|
|
205
|
-
const isFoundationBoundary = (boundary) => typeof boundary === 'object' &&
|
|
206
|
-
boundary !== null &&
|
|
207
|
-
boundary[BOUNDARY_BRAND] === true;
|
|
208
|
-
/**
|
|
209
|
-
* Root `onCaughtError` for `createRoot`.
|
|
210
|
-
*
|
|
211
|
-
* @remarks
|
|
212
|
-
* By default React 19 logs every error a boundary catches to `console.error`, before the
|
|
213
|
-
* boundary's `componentDidCatch` runs. Console-capturing monitoring tools then report that bare
|
|
214
|
-
* error first, and can drop or duplicate the boundary's own report. {@link AppErrorBoundary} and
|
|
215
|
-
* {@link TileErrorBoundary} report or log errors themselves, so this skips them. Errors caught by
|
|
216
|
-
* any other boundary are still logged to `console.error`.
|
|
217
|
-
*
|
|
218
|
-
* @public
|
|
219
|
-
*/
|
|
220
|
-
export const onCaughtError = (error, errorInfo) => {
|
|
221
|
-
if (isFoundationBoundary(errorInfo.errorBoundary)) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
console.error(error);
|
|
225
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { AppErrorBoundary, ERROR_BOUNDARY_LOGGER_NAME, onCaughtError, TileErrorBoundary, withTileErrorBoundary, } from './error-boundary';
|