1
1
/** @import { Effect, TemplateNode, } from '#client' */
2
2
3
- import { BOUNDARY_EFFECT , EFFECT_TRANSPARENT } from '../../constants.js' ;
4
- import { block , branch , destroy_effect , pause_effect } from '../../reactivity/effects.js' ;
3
+ import { BOUNDARY_EFFECT , EFFECT_TRANSPARENT , INERT } from '../../constants.js' ;
4
+ import {
5
+ block ,
6
+ branch ,
7
+ destroy_effect ,
8
+ pause_effect ,
9
+ resume_effect
10
+ } from '../../reactivity/effects.js' ;
5
11
import {
6
12
active_effect ,
7
13
active_reaction ,
@@ -20,8 +26,12 @@ import {
20
26
remove_nodes ,
21
27
set_hydrate_node
22
28
} from '../hydration.js' ;
29
+ import { get_next_sibling } from '../operations.js' ;
23
30
import { queue_micro_task } from '../task.js' ;
24
31
32
+ const SUSPEND_INCREMENT = Symbol ( ) ;
33
+ const SUSPEND_DECREMENT = Symbol ( ) ;
34
+
25
35
/**
26
36
* @param {Effect } boundary
27
37
* @param {() => void } fn
@@ -49,6 +59,7 @@ function with_boundary(boundary, fn) {
49
59
* @param {{
50
60
* onerror?: (error: unknown, reset: () => void) => void,
51
61
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
62
+ * pending?: (anchor: Node) => void
52
63
* }} props
53
64
* @param {((anchor: Node) => void) } boundary_fn
54
65
* @returns {void }
@@ -58,14 +69,95 @@ export function boundary(node, props, boundary_fn) {
58
69
59
70
/** @type {Effect } */
60
71
var boundary_effect ;
72
+ /** @type {Effect | null } */
73
+ var suspended_effect = null ;
74
+ /** @type {DocumentFragment | null } */
75
+ var suspended_fragment = null ;
76
+ var suspend_count = 0 ;
61
77
62
78
block ( ( ) => {
63
79
var boundary = /** @type {Effect } */ ( active_effect ) ;
64
80
var hydrate_open = hydrate_node ;
65
81
var is_creating_fallback = false ;
66
82
67
- // We re-use the effect's fn property to avoid allocation of an additional field
68
- boundary . fn = ( /** @type {unknown }} */ error ) => {
83
+ const render_snippet = ( /** @type { () => void } */ snippet_fn ) => {
84
+ // Render the snippet in a microtask
85
+ queue_micro_task ( ( ) => {
86
+ with_boundary ( boundary , ( ) => {
87
+ is_creating_fallback = true ;
88
+
89
+ try {
90
+ boundary_effect = branch ( ( ) => {
91
+ snippet_fn ( ) ;
92
+ } ) ;
93
+ } catch ( error ) {
94
+ handle_error ( error , boundary , null , boundary . ctx ) ;
95
+ }
96
+
97
+ reset_is_throwing_error ( ) ;
98
+ is_creating_fallback = false ;
99
+ } ) ;
100
+ } ) ;
101
+ } ;
102
+
103
+ // @ts -ignore We re-use the effect's fn property to avoid allocation of an additional field
104
+ boundary . fn = ( /** @type {unknown } */ input ) => {
105
+ let pending = props . pending ;
106
+
107
+ if ( input === SUSPEND_INCREMENT ) {
108
+ if ( ! pending ) {
109
+ return false ;
110
+ }
111
+ suspend_count ++ ;
112
+
113
+ if ( suspended_effect === null ) {
114
+ var effect = boundary_effect ;
115
+ suspended_effect = boundary_effect ;
116
+
117
+ pause_effect ( suspended_effect , ( ) => {
118
+ /** @type {TemplateNode | null } */
119
+ var node = effect . nodes_start ;
120
+ var end = effect . nodes_end ;
121
+ suspended_fragment = document . createDocumentFragment ( ) ;
122
+
123
+ while ( node !== null ) {
124
+ /** @type {TemplateNode | null } */
125
+ var sibling =
126
+ node === end ? null : /** @type {TemplateNode } */ ( get_next_sibling ( node ) ) ;
127
+
128
+ node . remove ( ) ;
129
+ suspended_fragment . append ( node ) ;
130
+ node = sibling ;
131
+ }
132
+ } , false ) ;
133
+
134
+ render_snippet ( ( ) => {
135
+ pending ( anchor ) ;
136
+ } ) ;
137
+ }
138
+ return true ;
139
+ }
140
+
141
+ if ( input === SUSPEND_DECREMENT ) {
142
+ if ( ! pending ) {
143
+ return false ;
144
+ }
145
+ suspend_count -- ;
146
+
147
+ if ( suspend_count === 0 && suspended_effect !== null ) {
148
+ if ( boundary_effect ) {
149
+ destroy_effect ( boundary_effect ) ;
150
+ }
151
+ boundary_effect = suspended_effect ;
152
+ suspended_effect = null ;
153
+ anchor . before ( /** @type {DocumentFragment } */ ( suspended_fragment ) ) ;
154
+ resume_effect ( boundary_effect ) ;
155
+ }
156
+
157
+ return true ;
158
+ }
159
+
160
+ var error = input ;
69
161
var onerror = props . onerror ;
70
162
let failed = props . failed ;
71
163
@@ -96,26 +188,12 @@ export function boundary(node, props, boundary_fn) {
96
188
}
97
189
98
190
if ( failed ) {
99
- // Render the `failed` snippet in a microtask
100
- queue_micro_task ( ( ) => {
101
- with_boundary ( boundary , ( ) => {
102
- is_creating_fallback = true ;
103
-
104
- try {
105
- boundary_effect = branch ( ( ) => {
106
- failed (
107
- anchor ,
108
- ( ) => error ,
109
- ( ) => reset
110
- ) ;
111
- } ) ;
112
- } catch ( error ) {
113
- handle_error ( error , boundary , null , boundary . ctx ) ;
114
- }
115
-
116
- reset_is_throwing_error ( ) ;
117
- is_creating_fallback = false ;
118
- } ) ;
191
+ render_snippet ( ( ) => {
192
+ failed (
193
+ anchor ,
194
+ ( ) => error ,
195
+ ( ) => reset
196
+ ) ;
119
197
} ) ;
120
198
}
121
199
} ;
@@ -132,3 +210,31 @@ export function boundary(node, props, boundary_fn) {
132
210
anchor = hydrate_node ;
133
211
}
134
212
}
213
+
214
+ export function suspend ( ) {
215
+ var current = active_effect ;
216
+
217
+ while ( current !== null ) {
218
+ if ( ( current . f & BOUNDARY_EFFECT ) !== 0 ) {
219
+ // @ts -ignore
220
+ if ( current . fn ( SUSPEND_INCREMENT ) ) {
221
+ return ;
222
+ }
223
+ }
224
+ current = current . parent ;
225
+ }
226
+ }
227
+
228
+ export function unsuspend ( ) {
229
+ var current = active_effect ;
230
+
231
+ while ( current !== null ) {
232
+ if ( ( current . f & BOUNDARY_EFFECT ) !== 0 ) {
233
+ // @ts -ignore
234
+ if ( current . fn ( SUSPEND_DECREMENT ) ) {
235
+ return ;
236
+ }
237
+ }
238
+ current = current . parent ;
239
+ }
240
+ }
0 commit comments