@pyreon/core 0.24.5 → 0.24.6
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +53 -31
- package/package.json +2 -6
- package/src/compat-marker.ts +0 -79
- package/src/compat-shared.ts +0 -80
- package/src/component.ts +0 -98
- package/src/context.ts +0 -349
- package/src/defer.ts +0 -279
- package/src/dynamic.ts +0 -32
- package/src/env.d.ts +0 -6
- package/src/error-boundary.ts +0 -90
- package/src/for.ts +0 -51
- package/src/h.ts +0 -80
- package/src/index.ts +0 -80
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -747
- package/src/lazy.ts +0 -25
- package/src/lifecycle.ts +0 -152
- package/src/manifest.ts +0 -579
- package/src/map-array.ts +0 -42
- package/src/portal.ts +0 -39
- package/src/props.ts +0 -269
- package/src/ref.ts +0 -32
- package/src/show.ts +0 -121
- package/src/style.ts +0 -102
- package/src/suspense.ts +0 -52
- package/src/telemetry.ts +0 -120
- package/src/tests/compat-marker.test.ts +0 -96
- package/src/tests/compat-shared.test.ts +0 -99
- package/src/tests/component.test.ts +0 -281
- package/src/tests/context.test.ts +0 -629
- package/src/tests/core.test.ts +0 -1290
- package/src/tests/cx.test.ts +0 -70
- package/src/tests/defer.test.ts +0 -359
- package/src/tests/dynamic.test.ts +0 -87
- package/src/tests/error-boundary.test.ts +0 -181
- package/src/tests/extract-props-overloads.types.test.ts +0 -135
- package/src/tests/for.test.ts +0 -117
- package/src/tests/h.test.ts +0 -221
- package/src/tests/jsx-compat.test.tsx +0 -86
- package/src/tests/lazy.test.ts +0 -100
- package/src/tests/lifecycle.test.ts +0 -350
- package/src/tests/manifest-snapshot.test.ts +0 -100
- package/src/tests/map-array.test.ts +0 -313
- package/src/tests/native-marker-error-boundary.test.ts +0 -12
- package/src/tests/portal.test.ts +0 -48
- package/src/tests/props-extended.test.ts +0 -157
- package/src/tests/props.test.ts +0 -250
- package/src/tests/reactive-context.test.ts +0 -69
- package/src/tests/reactive-props.test.ts +0 -157
- package/src/tests/ref.test.ts +0 -70
- package/src/tests/show.test.ts +0 -314
- package/src/tests/style.test.ts +0 -157
- package/src/tests/suspense.test.ts +0 -139
- package/src/tests/telemetry.test.ts +0 -297
- package/src/types.ts +0 -116
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
import { clearReactiveTrace, signal } from '@pyreon/reactivity'
|
|
2
|
-
import type { ErrorContext } from '../telemetry'
|
|
3
|
-
import { registerErrorHandler, reportError } from '../telemetry'
|
|
4
|
-
|
|
5
|
-
describe('registerErrorHandler', () => {
|
|
6
|
-
test('registers handler that receives error context', () => {
|
|
7
|
-
const contexts: ErrorContext[] = []
|
|
8
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
9
|
-
contexts.push(ctx)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const ctx: ErrorContext = {
|
|
13
|
-
component: 'TestComp',
|
|
14
|
-
phase: 'render',
|
|
15
|
-
error: new Error('test'),
|
|
16
|
-
timestamp: 1234567890,
|
|
17
|
-
}
|
|
18
|
-
reportError(ctx)
|
|
19
|
-
expect(contexts).toHaveLength(1)
|
|
20
|
-
expect(contexts[0]).toBe(ctx)
|
|
21
|
-
|
|
22
|
-
unsub()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('returns unregister function', () => {
|
|
26
|
-
let count = 0
|
|
27
|
-
const unsub = registerErrorHandler(() => {
|
|
28
|
-
count++
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
reportError({ component: 'A', phase: 'setup', error: 'e1', timestamp: 0 })
|
|
32
|
-
expect(count).toBe(1)
|
|
33
|
-
|
|
34
|
-
unsub()
|
|
35
|
-
|
|
36
|
-
reportError({ component: 'B', phase: 'render', error: 'e2', timestamp: 0 })
|
|
37
|
-
expect(count).toBe(1) // not called after unregister
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('multiple handlers are all called', () => {
|
|
41
|
-
let count = 0
|
|
42
|
-
const unsub1 = registerErrorHandler(() => count++)
|
|
43
|
-
const unsub2 = registerErrorHandler(() => count++)
|
|
44
|
-
const unsub3 = registerErrorHandler(() => count++)
|
|
45
|
-
|
|
46
|
-
reportError({ component: 'X', phase: 'mount', error: 'err', timestamp: 0 })
|
|
47
|
-
expect(count).toBe(3)
|
|
48
|
-
|
|
49
|
-
unsub1()
|
|
50
|
-
unsub2()
|
|
51
|
-
unsub3()
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('handler errors are swallowed — subsequent handlers still called', () => {
|
|
55
|
-
let secondCalled = false
|
|
56
|
-
let thirdCalled = false
|
|
57
|
-
|
|
58
|
-
const unsub1 = registerErrorHandler(() => {
|
|
59
|
-
throw new Error('handler crash')
|
|
60
|
-
})
|
|
61
|
-
const unsub2 = registerErrorHandler(() => {
|
|
62
|
-
secondCalled = true
|
|
63
|
-
})
|
|
64
|
-
const unsub3 = registerErrorHandler(() => {
|
|
65
|
-
thirdCalled = true
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Should not throw
|
|
69
|
-
expect(() =>
|
|
70
|
-
reportError({ component: 'Y', phase: 'unmount', error: 'err', timestamp: 0 }),
|
|
71
|
-
).not.toThrow()
|
|
72
|
-
expect(secondCalled).toBe(true)
|
|
73
|
-
expect(thirdCalled).toBe(true)
|
|
74
|
-
|
|
75
|
-
unsub1()
|
|
76
|
-
unsub2()
|
|
77
|
-
unsub3()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('unregistering one handler does not affect others', () => {
|
|
81
|
-
const calls: string[] = []
|
|
82
|
-
const unsub1 = registerErrorHandler(() => calls.push('a'))
|
|
83
|
-
const unsub2 = registerErrorHandler(() => calls.push('b'))
|
|
84
|
-
const unsub3 = registerErrorHandler(() => calls.push('c'))
|
|
85
|
-
|
|
86
|
-
unsub2() // remove middle handler
|
|
87
|
-
|
|
88
|
-
reportError({ component: 'Z', phase: 'effect', error: 'e', timestamp: 0 })
|
|
89
|
-
expect(calls).toEqual(['a', 'c'])
|
|
90
|
-
|
|
91
|
-
unsub1()
|
|
92
|
-
unsub3()
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
describe('reportError', () => {
|
|
97
|
-
test('no-op when no handlers registered', () => {
|
|
98
|
-
// Should not throw
|
|
99
|
-
expect(() =>
|
|
100
|
-
reportError({ component: 'None', phase: 'setup', error: 'err', timestamp: 0 }),
|
|
101
|
-
).not.toThrow()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('passes full ErrorContext to handler', () => {
|
|
105
|
-
let received: ErrorContext | null = null
|
|
106
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
107
|
-
received = ctx
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
const ctx: ErrorContext = {
|
|
111
|
-
component: 'MyComp',
|
|
112
|
-
phase: 'render',
|
|
113
|
-
error: new Error('detail'),
|
|
114
|
-
timestamp: 999,
|
|
115
|
-
props: { a: 1, b: 'two' },
|
|
116
|
-
}
|
|
117
|
-
reportError(ctx)
|
|
118
|
-
|
|
119
|
-
expect(received).not.toBeNull()
|
|
120
|
-
expect(received!.component).toBe('MyComp')
|
|
121
|
-
expect(received!.phase).toBe('render')
|
|
122
|
-
expect(received!.error).toBeInstanceOf(Error)
|
|
123
|
-
expect(received!.timestamp).toBe(999)
|
|
124
|
-
expect(received!.props).toEqual({ a: 1, b: 'two' })
|
|
125
|
-
|
|
126
|
-
unsub()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
test('handles all phase types', () => {
|
|
130
|
-
const phases: ErrorContext['phase'][] = ['setup', 'render', 'mount', 'unmount', 'effect']
|
|
131
|
-
const seen: string[] = []
|
|
132
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
133
|
-
seen.push(ctx.phase)
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
for (const phase of phases) {
|
|
137
|
-
reportError({ component: 'X', phase, error: 'e', timestamp: 0 })
|
|
138
|
-
}
|
|
139
|
-
expect(seen).toEqual(phases)
|
|
140
|
-
|
|
141
|
-
unsub()
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
// ─── Regression: reactivity effect errors reach core's registerErrorHandler ──
|
|
146
|
-
//
|
|
147
|
-
// Pre-fix: `@pyreon/reactivity` had its own `setErrorHandler` API that drove
|
|
148
|
-
// effect errors to `console.error`. `@pyreon/core`'s `registerErrorHandler`
|
|
149
|
-
// captured component / mount / render / unmount errors only — effect errors
|
|
150
|
-
// never reached it. Sentry/Datadog wiring missed the entire reactive surface.
|
|
151
|
-
//
|
|
152
|
-
// Post-fix: `registerErrorHandler` installs a `globalThis.__pyreon_report_error__`
|
|
153
|
-
// bridge. Reactivity's effect-error path forwards through that bridge, so
|
|
154
|
-
// effect errors flow into the same `reportError` pipeline as component
|
|
155
|
-
// errors with phase='effect'.
|
|
156
|
-
describe('registerErrorHandler — reactivity bridge (regression)', () => {
|
|
157
|
-
test('effect() errors forward into registered telemetry handler', async () => {
|
|
158
|
-
// Use a clean global state. The bridge install is idempotent within one
|
|
159
|
-
// global; re-importing reactivity here re-uses the same module instance.
|
|
160
|
-
const { effect, signal } = await import('@pyreon/reactivity')
|
|
161
|
-
|
|
162
|
-
const captured: ErrorContext[] = []
|
|
163
|
-
const unsub = registerErrorHandler((ctx) => captured.push(ctx))
|
|
164
|
-
|
|
165
|
-
const trigger = signal(0)
|
|
166
|
-
effect(() => {
|
|
167
|
-
if (trigger() > 0) throw new Error('boom in effect')
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
// Trigger the throw
|
|
171
|
-
trigger.set(1)
|
|
172
|
-
await new Promise<void>((r) => queueMicrotask(() => r()))
|
|
173
|
-
|
|
174
|
-
// Captured exactly once — through the bridge.
|
|
175
|
-
expect(captured).toHaveLength(1)
|
|
176
|
-
expect(captured[0]?.phase).toBe('effect')
|
|
177
|
-
expect(captured[0]?.component).toBe('Effect')
|
|
178
|
-
expect((captured[0]?.error as Error).message).toBe('boom in effect')
|
|
179
|
-
|
|
180
|
-
unsub()
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
test('multiple handlers all receive forwarded effect errors', async () => {
|
|
184
|
-
const { effect, signal } = await import('@pyreon/reactivity')
|
|
185
|
-
|
|
186
|
-
let count1 = 0
|
|
187
|
-
let count2 = 0
|
|
188
|
-
const unsub1 = registerErrorHandler(() => count1++)
|
|
189
|
-
const unsub2 = registerErrorHandler(() => count2++)
|
|
190
|
-
|
|
191
|
-
const trigger = signal(0)
|
|
192
|
-
effect(() => {
|
|
193
|
-
if (trigger() > 0) throw new Error('boom')
|
|
194
|
-
})
|
|
195
|
-
trigger.set(1)
|
|
196
|
-
await new Promise<void>((r) => queueMicrotask(() => r()))
|
|
197
|
-
|
|
198
|
-
expect(count1).toBe(1)
|
|
199
|
-
expect(count2).toBe(1)
|
|
200
|
-
|
|
201
|
-
unsub1()
|
|
202
|
-
unsub2()
|
|
203
|
-
})
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
describe('reportError — reactiveTrace enrichment', () => {
|
|
207
|
-
beforeEach(() => clearReactiveTrace())
|
|
208
|
-
|
|
209
|
-
test('attaches recent signal writes to the error context (dev)', () => {
|
|
210
|
-
const s = signal(0, { name: 'enrichTest' })
|
|
211
|
-
s.set(1)
|
|
212
|
-
s.set(2)
|
|
213
|
-
|
|
214
|
-
let captured: ErrorContext | undefined
|
|
215
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
216
|
-
captured = ctx
|
|
217
|
-
})
|
|
218
|
-
reportError({
|
|
219
|
-
component: 'C',
|
|
220
|
-
phase: 'render',
|
|
221
|
-
error: new Error('boom'),
|
|
222
|
-
timestamp: Date.now(),
|
|
223
|
-
})
|
|
224
|
-
unsub()
|
|
225
|
-
|
|
226
|
-
expect(captured?.reactiveTrace).toBeDefined()
|
|
227
|
-
expect(captured!.reactiveTrace).toHaveLength(2)
|
|
228
|
-
expect(captured!.reactiveTrace![0]).toMatchObject({
|
|
229
|
-
name: 'enrichTest',
|
|
230
|
-
prev: '0',
|
|
231
|
-
next: '1',
|
|
232
|
-
})
|
|
233
|
-
expect(captured!.reactiveTrace![1]).toMatchObject({ prev: '1', next: '2' })
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
test('does not overwrite a caller-supplied reactiveTrace', () => {
|
|
237
|
-
const s = signal(0, { name: 'x' })
|
|
238
|
-
s.set(99)
|
|
239
|
-
|
|
240
|
-
let captured: ErrorContext | undefined
|
|
241
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
242
|
-
captured = ctx
|
|
243
|
-
})
|
|
244
|
-
const supplied = [{ name: 'manual', prev: 'a', next: 'b', timestamp: 1 }]
|
|
245
|
-
reportError({
|
|
246
|
-
component: 'C',
|
|
247
|
-
phase: 'effect',
|
|
248
|
-
error: new Error('boom'),
|
|
249
|
-
timestamp: Date.now(),
|
|
250
|
-
reactiveTrace: supplied,
|
|
251
|
-
})
|
|
252
|
-
unsub()
|
|
253
|
-
|
|
254
|
-
expect(captured!.reactiveTrace).toBe(supplied)
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
test('no trace field when there were no signal writes', () => {
|
|
258
|
-
let captured: ErrorContext | undefined
|
|
259
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
260
|
-
captured = ctx
|
|
261
|
-
})
|
|
262
|
-
reportError({
|
|
263
|
-
component: 'C',
|
|
264
|
-
phase: 'mount',
|
|
265
|
-
error: new Error('boom'),
|
|
266
|
-
timestamp: Date.now(),
|
|
267
|
-
})
|
|
268
|
-
unsub()
|
|
269
|
-
|
|
270
|
-
// Empty buffer → field stays undefined (don't attach a noisy []).
|
|
271
|
-
expect(captured?.reactiveTrace).toBeUndefined()
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
test('the effect-error bridge path is also enriched', () => {
|
|
275
|
-
const s = signal('idle', { name: 'phase' })
|
|
276
|
-
s.set('running')
|
|
277
|
-
|
|
278
|
-
let captured: ErrorContext | undefined
|
|
279
|
-
const unsub = registerErrorHandler((ctx) => {
|
|
280
|
-
captured = ctx
|
|
281
|
-
})
|
|
282
|
-
// Drive the reactivity → core bridge the same way an effect throw does.
|
|
283
|
-
const bridge = (
|
|
284
|
-
globalThis as { __pyreon_report_error__?: (e: unknown, p: 'effect') => void }
|
|
285
|
-
).__pyreon_report_error__
|
|
286
|
-
bridge?.(new Error('effect boom'), 'effect')
|
|
287
|
-
unsub()
|
|
288
|
-
|
|
289
|
-
expect(captured?.component).toBe('Effect')
|
|
290
|
-
expect(captured?.reactiveTrace).toBeDefined()
|
|
291
|
-
expect(captured!.reactiveTrace![0]).toMatchObject({
|
|
292
|
-
name: 'phase',
|
|
293
|
-
prev: '"idle"',
|
|
294
|
-
next: '"running"',
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
})
|
package/src/types.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/// <reference lib="dom" />
|
|
2
|
-
|
|
3
|
-
// ─── VNode ────────────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
// Reactive getter returning a child — wraps dynamic expressions in `() =>`
|
|
6
|
-
export type VNodeChildAtom = VNode | string | number | boolean | null | undefined
|
|
7
|
-
/** Reactive accessor — TS checks this arm FIRST so `{() => cond && <X />}` resolves correctly */
|
|
8
|
-
export type VNodeChildAccessor = () => VNodeChildAtom | VNodeChildAtom[]
|
|
9
|
-
export type VNodeChild = VNodeChildAccessor | VNodeChildAtom | VNodeChildAtom[]
|
|
10
|
-
|
|
11
|
-
export interface VNode {
|
|
12
|
-
/** Tag name, component function, or special symbol (Fragment) */
|
|
13
|
-
type: string | ComponentFn | symbol
|
|
14
|
-
props: Props
|
|
15
|
-
children: VNodeChild[]
|
|
16
|
-
key: string | number | null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ─── Props ────────────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
export type Props = Record<string, unknown>
|
|
22
|
-
|
|
23
|
-
// ─── Component ────────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* A component is a plain function that runs ONCE.
|
|
27
|
-
* It returns any renderable content and may call lifecycle hooks during setup.
|
|
28
|
-
*/
|
|
29
|
-
export type ComponentFn<P extends Props = Props> = (props: P) => VNodeChild
|
|
30
|
-
|
|
31
|
-
// ─── Utility types ───────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Extract the props type from a component function, or pass through if already
|
|
35
|
-
* a props type. **Multi-overload aware** — matches up to 4 call signatures and
|
|
36
|
-
* produces the UNION of their first-argument types. A single-overload function
|
|
37
|
-
* still works (the union of 4 copies of the same props type dedupes back to
|
|
38
|
-
* the single shape).
|
|
39
|
-
*
|
|
40
|
-
* **Why this shape**. `T extends (props: infer P) => any ? P : never` only
|
|
41
|
-
* captures the LAST overload of a multi-overload function — TS's overload-
|
|
42
|
-
* resolution-against-conditional-types semantics. Multi-overload primitives
|
|
43
|
-
* (Iterator, List, Element, etc.) need the union of every overload's props
|
|
44
|
-
* to survive HOC wrapping (`rocketstyle()`, `attrs()`) without silently
|
|
45
|
-
* downgrading the public prop surface to the loosest overload. Mirrors
|
|
46
|
-
* vitus-labs PR #222.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* function Iterator<T extends SimpleValue>(p: { data: T[]; valueName?: string }): VNodeChild
|
|
50
|
-
* function Iterator<T extends ObjectValue>(p: { data: T[]; component: ComponentFn<T> }): VNodeChild
|
|
51
|
-
* type Props = ExtractProps<typeof Iterator>
|
|
52
|
-
* // → { data: SimpleValue[]; valueName?: string }
|
|
53
|
-
* // | { data: ObjectValue[]; component: ComponentFn<ObjectValue> }
|
|
54
|
-
*/
|
|
55
|
-
export type ExtractProps<T> = T extends {
|
|
56
|
-
(props: infer P1, ...args: any): any
|
|
57
|
-
(props: infer P2, ...args: any): any
|
|
58
|
-
(props: infer P3, ...args: any): any
|
|
59
|
-
(props: infer P4, ...args: any): any
|
|
60
|
-
}
|
|
61
|
-
? P1 | P2 | P3 | P4
|
|
62
|
-
: T extends {
|
|
63
|
-
(props: infer P1, ...args: any): any
|
|
64
|
-
(props: infer P2, ...args: any): any
|
|
65
|
-
(props: infer P3, ...args: any): any
|
|
66
|
-
}
|
|
67
|
-
? P1 | P2 | P3
|
|
68
|
-
: T extends {
|
|
69
|
-
(props: infer P1, ...args: any): any
|
|
70
|
-
(props: infer P2, ...args: any): any
|
|
71
|
-
}
|
|
72
|
-
? P1 | P2
|
|
73
|
-
: T extends ComponentFn<infer P>
|
|
74
|
-
? P
|
|
75
|
-
: T
|
|
76
|
-
|
|
77
|
-
/** A higher-order component that wraps a component, optionally transforming its props. */
|
|
78
|
-
export type HigherOrderComponent<HOP extends Props, P extends Props | undefined = undefined> = (
|
|
79
|
-
Component: ComponentFn<HOP>,
|
|
80
|
-
) => ComponentFn<P extends undefined ? HOP : P>
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Internal runtime handle created by the renderer for each mounted component.
|
|
84
|
-
*/
|
|
85
|
-
export interface ComponentInstance {
|
|
86
|
-
vnode: VNode | null
|
|
87
|
-
/** Trigger a re-check / patch cycle (called by the renderer) */
|
|
88
|
-
update(): void
|
|
89
|
-
unmount(): void
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ─── Lifecycle hooks storage (attached per-instance by the renderer) ──────────
|
|
93
|
-
|
|
94
|
-
// Cleanup function optionally returned by onMount hooks
|
|
95
|
-
export type CleanupFn = () => void
|
|
96
|
-
|
|
97
|
-
// ─── NativeItem ───────────────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Result of createTemplate() — a pre-cloned DOM element with its cleanup.
|
|
101
|
-
* Handled directly by mountFor without going through the VNode reconciler,
|
|
102
|
-
* saving 2 allocations per row vs the VNode wrapper path.
|
|
103
|
-
*/
|
|
104
|
-
export interface NativeItem {
|
|
105
|
-
readonly __isNative: true
|
|
106
|
-
el: HTMLElement
|
|
107
|
-
cleanup: (() => void) | null
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface LifecycleHooks {
|
|
111
|
-
mount: (() => CleanupFn | void | undefined)[] | null
|
|
112
|
-
unmount: (() => void)[] | null
|
|
113
|
-
update: (() => void)[] | null
|
|
114
|
-
/** Error handlers — return true to mark the error as handled (stops propagation). */
|
|
115
|
-
error: ((err: unknown) => boolean | undefined)[] | null
|
|
116
|
-
}
|