-
Notifications
You must be signed in to change notification settings - Fork 0
/
error-boundary.ts
97 lines (93 loc) · 3.06 KB
/
error-boundary.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { Observable, Subject, map } from 'rxjs'
import {
ComponentDescription,
ContextComponent,
FragmentDescription,
SimpleComponent,
} from './component.js'
import { WiringContext } from './wiring-context.js'
import { wire } from './wiring.js'
/**
* Properties passed to an Error View
*/
export interface ErrorViewProps {
/**
* Error that occurred.
*/
error: unknown
}
/**
* Properties supported by the `<ErrorBoundary>` pseudo-component
*/
export interface ErrorBoundaryProps {
/**
* Component to view when an error occurs below this boundary.
*/
errorView: ContextComponent<ErrorViewProps> | SimpleComponent
// errorViewBindMode is not just ChildrenBindMode, because we are using a double layer of fragments 'replace' is unlikely to work well
/**
* Bind mode for error views. Defaults to 'prepend'.
*/
errorViewBindMode?: 'append' | 'prepend'
/**
* Preserve DOM contents of the errored tree.
*
* This is primarily a Debug tool. Components will still be unbound
* as a part of completion so it leaves "dead" components around,
* which are useful for debugging. Your error view may be enough
* to warn users that the surrounding components have died, in which
* case you may be able to make this useful in production builds as
* well (as opposed to setting this in a raw `RuntimeOptions`).
*/
preserveOnComplete?: boolean
}
/**
* Present an error view when errors occur below this in the tree.
*
* @param _props Error Boundary Props
*/
export const ErrorBoundary: ContextComponent<ErrorBoundaryProps> = () => {
throw new Error('ErrorBoundary is a custom-wired component')
}
export function wireErrorBoundary(
description: ComponentDescription,
context: WiringContext,
document = globalThis.document,
): Observable<Element> {
context.isStaticComponent = false
context.isStaticTree = false
const { errorView, errorViewBindMode, preserveOnComplete } =
description.properties as unknown as ErrorBoundaryProps
const errorOccurred = new Subject()
const treeError = errorOccurred.next.bind(errorOccurred)
const errorViewChildren = errorOccurred.pipe(
map((error) => () => {
const childComponent: ComponentDescription = {
type: 'component',
component: errorView,
children: [],
properties: { error },
}
return childComponent
}),
)
const mainComponentFragment: FragmentDescription = {
type: 'fragment',
attributes: {},
children: description.children,
childrenBind: description.childrenBind,
childrenBindMode: description.childrenBindMode,
}
// TODO: Is there a cleaner way than double nested fragments?
const errorViewComponentFragment: FragmentDescription = {
type: 'fragment',
attributes: {},
children: [mainComponentFragment],
childrenBind: errorViewChildren,
childrenBindMode: errorViewBindMode ?? 'prepend',
}
const mainComponent = () => errorViewComponentFragment
const mainContext = { ...context, treeError, preserveOnComplete }
const main = wire(mainComponent, mainContext, undefined, document)
return main
}