solid-js 2.0.0-beta.6 → 2.0.0-beta.8

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/dist/server.cjs CHANGED
@@ -28,25 +28,82 @@ function runWithObserver(comp, fn) {
28
28
  function getObserver() {
29
29
  return Observer;
30
30
  }
31
- function createSignal(first, second, third) {
31
+ function createDeferredPromise() {
32
+ let settled = false;
33
+ let resolvePromise;
34
+ let rejectPromise;
35
+ const promise = new Promise((resolve, reject) => {
36
+ resolvePromise = resolve;
37
+ rejectPromise = reject;
38
+ });
39
+ return {
40
+ promise,
41
+ resolve(value) {
42
+ if (settled) return;
43
+ settled = true;
44
+ promise.s = 1;
45
+ promise.v = value;
46
+ resolvePromise(value);
47
+ },
48
+ reject(error) {
49
+ if (settled) return;
50
+ settled = true;
51
+ promise.s = 2;
52
+ promise.v = error;
53
+ rejectPromise(error);
54
+ }
55
+ };
56
+ }
57
+ function subscribePendingRetry(error, retry) {
58
+ if (!(error instanceof signals.NotReadyError)) return false;
59
+ error.source?.then(() => retry(), () => retry());
60
+ return true;
61
+ }
62
+ function settleServerAsync(initial, rerun, deferred, onSuccess, onError, isDisposed) {
63
+ let first = true;
64
+ const attempt = () => {
65
+ if (isDisposed()) return;
66
+ let current;
67
+ try {
68
+ current = first ? initial : rerun();
69
+ first = false;
70
+ } catch (error) {
71
+ if (subscribePendingRetry(error, attempt)) return;
72
+ onError(error);
73
+ deferred.reject(error);
74
+ return;
75
+ }
76
+ Promise.resolve(current).then(value => {
77
+ if (isDisposed()) return;
78
+ deferred.resolve(onSuccess(value));
79
+ }, error => {
80
+ if (isDisposed()) return;
81
+ if (subscribePendingRetry(error, attempt)) return;
82
+ onError(error);
83
+ deferred.reject(error);
84
+ });
85
+ };
86
+ attempt();
87
+ }
88
+ function createSignal(first, second) {
32
89
  if (typeof first === "function") {
33
- const opts = third?.deferStream || third?.ssrSource ? {
34
- deferStream: third?.deferStream,
35
- ssrSource: third?.ssrSource
90
+ const opts = second?.deferStream || second?.ssrSource ? {
91
+ deferStream: second?.deferStream,
92
+ ssrSource: second?.ssrSource
36
93
  } : undefined;
37
- const memo = createMemo(prev => first(prev), second, opts);
94
+ const memo = createMemo(prev => first(prev), opts);
38
95
  return [memo, () => undefined];
39
96
  }
40
97
  return [() => first, v => {
41
98
  return first = typeof v === "function" ? v(first) : v;
42
99
  }];
43
100
  }
44
- function createMemo(compute, value, options) {
101
+ function createMemo(compute, options) {
45
102
  const ctx = sharedConfig.context;
46
103
  const owner = signals.createOwner();
47
104
  const comp = {
48
105
  owner,
49
- value: value,
106
+ value: undefined,
50
107
  compute: compute,
51
108
  error: undefined,
52
109
  computed: false,
@@ -57,21 +114,22 @@ function createMemo(compute, value, options) {
57
114
  }));
58
115
  function update() {
59
116
  if (comp.disposed) return;
117
+ const run = () => signals.runWithOwner(owner, () => runWithObserver(comp, () => comp.compute(comp.value)));
60
118
  try {
61
119
  comp.error = undefined;
62
- const result = signals.runWithOwner(owner, () => runWithObserver(comp, () => comp.compute(comp.value)));
120
+ const result = run();
63
121
  comp.computed = true;
64
- processResult(comp, result, owner, ctx, options?.deferStream, options?.ssrSource);
122
+ processResult(comp, result, owner, ctx, options?.deferStream, options?.ssrSource, run);
65
123
  } catch (err) {
66
124
  if (err instanceof signals.NotReadyError) {
67
- err.source?.then(() => update(), () => update());
125
+ subscribePendingRetry(err, update);
68
126
  }
69
127
  comp.error = err;
70
128
  comp.computed = true;
71
129
  }
72
130
  }
73
131
  const ssrSource = options?.ssrSource;
74
- if (ssrSource === "initial" || ssrSource === "client") {
132
+ if (ssrSource === "client") {
75
133
  comp.computed = true;
76
134
  } else if (!options?.lazy) {
77
135
  update();
@@ -146,7 +204,7 @@ function createDeepProxy(target, patches, basePath = []) {
146
204
  };
147
205
  return new Proxy(target, handler);
148
206
  }
149
- function processResult(comp, result, owner, ctx, deferStream, ssrSource) {
207
+ function processResult(comp, result, owner, ctx, deferStream, ssrSource, rerun) {
150
208
  if (comp.disposed) return;
151
209
  const id = owner.id;
152
210
  const noHydrate = signals.getContext(NoHydrateContext, owner);
@@ -160,47 +218,78 @@ function processResult(comp, result, owner, ctx, deferStream, ssrSource) {
160
218
  comp.error = result.v;
161
219
  return;
162
220
  }
163
- result.then(v => {
221
+ const deferred = createDeferredPromise();
222
+ if (ctx?.async && ctx.serialize && id && !noHydrate) ctx.serialize(id, deferred.promise, deferStream);
223
+ settleServerAsync(result, () => rerun ? rerun() : result, deferred, value => {
164
224
  result.s = 1;
165
- result.v = v;
166
- if (comp.disposed) return;
167
- comp.value = v;
225
+ result.v = value;
226
+ comp.value = value;
168
227
  comp.error = undefined;
169
- }, err => {
228
+ return value;
229
+ }, error => {
170
230
  result.s = 2;
171
- result.v = err;
172
- if (comp.disposed) return;
173
- comp.error = err;
174
- });
175
- if (ctx?.async && ctx.serialize && id && !noHydrate) ctx.serialize(id, result, deferStream);
176
- comp.error = new signals.NotReadyError(result);
231
+ result.v = error;
232
+ comp.error = error;
233
+ }, () => comp.disposed);
234
+ comp.error = new signals.NotReadyError(deferred.promise);
177
235
  return;
178
236
  }
179
237
  const iterator = result?.[Symbol.asyncIterator];
180
238
  if (typeof iterator === "function") {
181
- const iter = iterator.call(result);
182
239
  if (ssrSource === "hybrid") {
183
- const promise = iter.next().then(v => {
184
- promise.s = 1;
185
- promise.v = v.value;
186
- if (!v.done) closeAsyncIterator(iter);
187
- if (comp.disposed) return v.value;
188
- comp.value = v.value;
240
+ let currentResult = result;
241
+ let iter;
242
+ const deferred = createDeferredPromise();
243
+ const runFirst = () => {
244
+ const source = currentResult ?? (rerun ? rerun() : result);
245
+ currentResult = undefined;
246
+ const nextIterator = source?.[Symbol.asyncIterator];
247
+ if (typeof nextIterator !== "function") {
248
+ throw new Error("Expected async iterator while retrying server createMemo");
249
+ }
250
+ iter = nextIterator.call(source);
251
+ return iter.next().then(value => {
252
+ if (!value.done) closeAsyncIterator(iter);
253
+ return value.value;
254
+ });
255
+ };
256
+ settleServerAsync(runFirst(), runFirst, deferred, value => {
257
+ comp.value = value;
189
258
  comp.error = undefined;
190
- return v.value;
191
- }, () => {});
192
- if (ctx?.async && ctx.serialize && id && !noHydrate) ctx.serialize(id, promise, deferStream);
193
- comp.error = new signals.NotReadyError(promise);
259
+ return value;
260
+ }, error => {
261
+ comp.error = error;
262
+ }, () => comp.disposed);
263
+ if (ctx?.async && ctx.serialize && id && !noHydrate) ctx.serialize(id, deferred.promise, deferStream);
264
+ comp.error = new signals.NotReadyError(deferred.promise);
194
265
  } else {
195
- const firstNext = iter.next();
196
- const firstReady = firstNext.then(r => {
197
- if (comp.disposed) return;
198
- if (!r.done) {
199
- comp.value = r.value;
266
+ let currentResult = result;
267
+ let iter;
268
+ let firstResult;
269
+ const deferred = createDeferredPromise();
270
+ const runFirst = () => {
271
+ const source = currentResult ?? (rerun ? rerun() : result);
272
+ currentResult = undefined;
273
+ const nextIterator = source?.[Symbol.asyncIterator];
274
+ if (typeof nextIterator !== "function") {
275
+ throw new Error("Expected async iterator while retrying server createMemo");
276
+ }
277
+ iter = nextIterator.call(source);
278
+ return iter.next().then(value => {
279
+ firstResult = value;
280
+ return Promise.resolve();
281
+ });
282
+ };
283
+ settleServerAsync(runFirst(), runFirst, deferred, () => {
284
+ const resolved = firstResult;
285
+ if (resolved && !resolved.done) {
286
+ comp.value = resolved.value;
200
287
  }
201
288
  comp.error = undefined;
202
- return Promise.resolve();
203
- }, () => {});
289
+ return undefined;
290
+ }, error => {
291
+ comp.error = error;
292
+ }, () => comp.disposed);
204
293
  if (ctx?.async && ctx.serialize && id && !noHydrate) {
205
294
  let tappedFirst = true;
206
295
  const tapped = {
@@ -208,7 +297,10 @@ function processResult(comp, result, owner, ctx, deferStream, ssrSource) {
208
297
  next() {
209
298
  if (tappedFirst) {
210
299
  tappedFirst = false;
211
- return firstNext.then(r => r);
300
+ return deferred.promise.then(() => firstResult?.done ? {
301
+ done: true,
302
+ value: undefined
303
+ } : firstResult);
212
304
  }
213
305
  return iter.next().then(r => r);
214
306
  },
@@ -219,7 +311,7 @@ function processResult(comp, result, owner, ctx, deferStream, ssrSource) {
219
311
  };
220
312
  ctx.serialize(id, tapped, deferStream);
221
313
  }
222
- comp.error = new signals.NotReadyError(firstReady);
314
+ comp.error = new signals.NotReadyError(deferred.promise);
223
315
  }
224
316
  return;
225
317
  }
@@ -231,9 +323,9 @@ function closeAsyncIterator(iter, value) {
231
323
  returned.then(undefined, () => {});
232
324
  }
233
325
  }
234
- function serverEffect(compute, effectFn, value, options) {
326
+ function serverEffect(compute, effectFn, options) {
235
327
  const ssrSource = options?.ssrSource;
236
- if (ssrSource === "client" || ssrSource === "initial") {
328
+ if (ssrSource === "client") {
237
329
  signals.createOwner();
238
330
  return;
239
331
  }
@@ -241,7 +333,7 @@ function serverEffect(compute, effectFn, value, options) {
241
333
  const owner = signals.createOwner();
242
334
  const comp = {
243
335
  owner,
244
- value: value,
336
+ value: undefined,
245
337
  compute: compute,
246
338
  error: undefined,
247
339
  computed: true,
@@ -253,19 +345,19 @@ function serverEffect(compute, effectFn, value, options) {
253
345
  }));
254
346
  }
255
347
  try {
256
- const result = signals.runWithOwner(owner, () => runWithObserver(comp, () => compute(value)));
348
+ const result = signals.runWithOwner(owner, () => runWithObserver(comp, () => compute(undefined)));
257
349
  if (ssrSource) {
258
350
  processResult(comp, result, owner, ctx, options?.deferStream, ssrSource);
259
351
  }
260
- effectFn?.(ssrSource ? comp.value ?? result : result, value);
352
+ effectFn?.(ssrSource ? comp.value ?? result : result, undefined);
261
353
  } catch (err) {
262
354
  }
263
355
  }
264
- function createEffect(compute, effect, value, options) {
265
- serverEffect(compute, undefined, value, options);
356
+ function createEffect(compute, effect, options) {
357
+ serverEffect(compute, undefined, options);
266
358
  }
267
- function createRenderEffect(compute, effectFn, value, options) {
268
- serverEffect(compute, effectFn, value, options);
359
+ function createRenderEffect(compute, effectFn, options) {
360
+ serverEffect(compute, effectFn, options);
269
361
  }
270
362
  function createTrackedEffect(compute, options) {
271
363
  const o = signals.getOwner();
@@ -276,8 +368,8 @@ function createReaction(effectFn, options) {
276
368
  tracking();
277
369
  };
278
370
  }
279
- function createOptimistic(first, second, third) {
280
- return createSignal(first, second, third);
371
+ function createOptimistic(first, second) {
372
+ return createSignal(first, second);
281
373
  }
282
374
  function setProperty(state, property, value) {
283
375
  if (state[property] === value) return;
@@ -287,7 +379,7 @@ function setProperty(state, property, value) {
287
379
  }
288
380
  function createStore(first, second) {
289
381
  if (typeof first === "function") {
290
- const store = createProjection(first, second ?? {});
382
+ const store = createProjection(first, second);
291
383
  return [store, fn => fn(store)];
292
384
  }
293
385
  const state = first;
@@ -310,11 +402,11 @@ function createPendingProxy(state, source) {
310
402
  pending = false;
311
403
  }];
312
404
  }
313
- function createProjection(fn, initialValue = {}, options) {
405
+ function createProjection(fn, initialValue, options) {
314
406
  const ctx = sharedConfig.context;
315
407
  const owner = signals.createOwner();
316
408
  const [state] = createStore(initialValue);
317
- if (options?.ssrSource === "initial" || options?.ssrSource === "client") {
409
+ if (options?.ssrSource === "client") {
318
410
  return state;
319
411
  }
320
412
  let disposed = false;
@@ -325,43 +417,69 @@ function createProjection(fn, initialValue = {}, options) {
325
417
  const useProxy = ssrSource !== "hybrid";
326
418
  const patches = [];
327
419
  const draft = useProxy ? createDeepProxy(state, patches) : state;
328
- const result = signals.runWithOwner(owner, () => fn(draft));
420
+ const runProjection = () => signals.runWithOwner(owner, () => fn(draft));
421
+ const result = runProjection();
329
422
  const iteratorFn = result?.[Symbol.asyncIterator];
330
423
  if (typeof iteratorFn === "function") {
331
- const iter = iteratorFn.call(result);
332
424
  if (ssrSource === "hybrid") {
333
- const promise = iter.next().then(r => {
334
- promise.s = 1;
335
- if (!r.done) closeAsyncIterator(iter);
336
- if (disposed) {
337
- promise.v = state;
338
- return state;
425
+ let currentResult = result;
426
+ let iter;
427
+ const deferred = createDeferredPromise();
428
+ const [pending, markReady] = createPendingProxy(state, deferred.promise);
429
+ const runFirst = () => {
430
+ const source = currentResult ?? runProjection();
431
+ currentResult = undefined;
432
+ const nextIterator = source?.[Symbol.asyncIterator];
433
+ if (typeof nextIterator !== "function") {
434
+ throw new Error("Expected async iterator while retrying server createProjection");
339
435
  }
340
- if (r.value !== undefined && r.value !== state) {
341
- Object.assign(state, r.value);
436
+ iter = nextIterator.call(source);
437
+ return iter.next().then(r => {
438
+ if (!r.done) closeAsyncIterator(iter);
439
+ return r.value;
440
+ });
441
+ };
442
+ settleServerAsync(runFirst(), runFirst, deferred, value => {
443
+ if (value !== undefined && value !== state) {
444
+ Object.assign(state, value);
342
445
  }
343
- promise.v = state;
344
446
  markReady();
345
447
  return state;
346
- }, () => {});
347
- if (ctx?.async && !signals.getContext(NoHydrateContext) && owner.id) ctx.serialize(owner.id, promise, options?.deferStream);
348
- const [pending, markReady] = createPendingProxy(state, promise);
448
+ }, error => {
449
+ markReady();
450
+ }, () => disposed);
451
+ if (ctx?.async && !signals.getContext(NoHydrateContext) && owner.id) ctx.serialize(owner.id, deferred.promise, options?.deferStream);
349
452
  return pending;
350
453
  } else {
351
- const firstNext = iter.next();
352
- const firstReady = firstNext.then(r => {
353
- if (disposed) return;
454
+ let currentResult = result;
455
+ let iter;
456
+ let firstResult;
457
+ const deferred = createDeferredPromise();
458
+ const [pending, markReady] = createPendingProxy(state, deferred.promise);
459
+ const runFirst = () => {
460
+ const source = currentResult ?? runProjection();
461
+ currentResult = undefined;
462
+ const nextIterator = source?.[Symbol.asyncIterator];
463
+ if (typeof nextIterator !== "function") {
464
+ throw new Error("Expected async iterator while retrying server createProjection");
465
+ }
466
+ iter = nextIterator.call(source);
467
+ return iter.next().then(value => {
468
+ firstResult = value;
469
+ return Promise.resolve();
470
+ });
471
+ };
472
+ settleServerAsync(runFirst(), runFirst, deferred, () => {
354
473
  patches.length = 0;
355
- if (!r.done) {
356
- if (r.value !== undefined && r.value !== draft) {
357
- Object.assign(state, r.value);
358
- }
474
+ const resolved = firstResult;
475
+ if (resolved && !resolved.done && resolved.value !== undefined && resolved.value !== draft) {
476
+ Object.assign(state, resolved.value);
359
477
  }
360
478
  markReady(JSON.parse(JSON.stringify(state)));
361
- return Promise.resolve();
362
- }, () => {
479
+ return undefined;
480
+ }, error => {
363
481
  markReady();
364
- });
482
+ }, () => disposed);
365
483
  if (ctx?.async && !signals.getContext(NoHydrateContext) && owner.id) {
366
484
  let tappedFirst = true;
367
485
  const tapped = {
@@ -369,8 +487,8 @@ function createProjection(fn, initialValue = {}, options) {
369
487
  next() {
370
488
  if (tappedFirst) {
371
489
  tappedFirst = false;
372
- return firstNext.then(r => {
373
- if (r.done) return {
490
+ return deferred.promise.then(() => {
491
+ if (firstResult?.done) return {
374
492
  done: true,
375
493
  value: undefined
376
494
  };
@@ -408,26 +526,22 @@ function createProjection(fn, initialValue = {}, options) {
408
526
  };
409
527
  ctx.serialize(owner.id, tapped, options?.deferStream);
410
528
  }
411
- const [pending, markReady] = createPendingProxy(state, firstReady);
412
529
  return pending;
413
530
  }
414
531
  }
415
532
  if (result instanceof Promise) {
416
- const promise = result.then(v => {
417
- promise.s = 1;
418
- if (disposed) {
419
- promise.v = state;
420
- return state;
421
- }
422
- if (v !== undefined && v !== state) {
423
- Object.assign(state, v);
533
+ const deferred = createDeferredPromise();
534
+ const [pending, markReady] = createPendingProxy(state, deferred.promise);
535
+ settleServerAsync(result, () => runProjection(), deferred, value => {
536
+ if (value !== undefined && value !== state) {
537
+ Object.assign(state, value);
424
538
  }
425
- promise.v = state;
426
539
  markReady();
427
540
  return state;
428
- }, () => {});
429
- if (ctx?.async && !signals.getContext(NoHydrateContext) && owner.id) ctx.serialize(owner.id, promise, options?.deferStream);
430
- const [pending, markReady] = createPendingProxy(state, promise);
541
+ }, error => {
542
+ markReady();
543
+ }, () => disposed);
544
+ if (ctx?.async && !signals.getContext(NoHydrateContext) && owner.id) ctx.serialize(owner.id, deferred.promise, options?.deferStream);
431
545
  return pending;
432
546
  }
433
547
  if (result !== undefined && result !== state && result !== draft) {
@@ -625,10 +739,10 @@ function useContext(context) {
625
739
  return signals.getContext(context);
626
740
  }
627
741
  function children(fn) {
628
- const c = createMemo(fn, undefined, {
742
+ const c = createMemo(fn, {
629
743
  lazy: true
630
744
  });
631
- const memo = createMemo(() => signals.flatten(c()), undefined, {
745
+ const memo = createMemo(() => signals.flatten(c()), {
632
746
  lazy: true
633
747
  });
634
748
  memo.toArray = () => {
@@ -712,12 +826,6 @@ function ssrHandleError(err) {
712
826
  }
713
827
  throw err;
714
828
  }
715
- class InvalidTopLevelAsyncReadError extends Error {
716
- constructor() {
717
- super("Async values must be read within a tracking scope (JSX, a memo, or an effect's compute function).");
718
- this.name = "InvalidTopLevelAsyncReadError";
719
- }
720
- }
721
829
  function createLoadingBoundary(fn, fallback, options) {
722
830
  const currentCtx = sharedConfig.context;
723
831
  if (!currentCtx) {
@@ -732,6 +840,7 @@ function createLoadingBoundary(fn, fallback, options) {
732
840
  o.id = id + "00";
733
841
  let done;
734
842
  let handledRenderError;
843
+ let retryPromise;
735
844
  let serializeBuffer = [];
736
845
  const bufferedCtx = Object.create(ctx);
737
846
  bufferedCtx.serialize = (id, value, deferStream) => {
@@ -775,21 +884,26 @@ function createLoadingBoundary(fn, fallback, options) {
775
884
  function runDiscovery() {
776
885
  o.dispose(false);
777
886
  serializeBuffer = [];
887
+ retryPromise = undefined;
778
888
  return runLoadingPhase(() => {
779
889
  try {
780
890
  return ctx.resolve(fn());
781
891
  } catch (err) {
782
- if (err instanceof signals.NotReadyError) throw new InvalidTopLevelAsyncReadError();
892
+ if (err instanceof signals.NotReadyError) {
893
+ retryPromise = err.source;
894
+ return undefined;
895
+ }
783
896
  throw err;
784
897
  }
785
898
  });
786
899
  }
787
900
  let ret = runDiscovery();
788
- if (!ret?.p?.length) {
901
+ if (!retryPromise && !ret?.p?.length) {
789
902
  commitBoundaryState();
790
903
  return () => ret;
791
904
  }
792
- const collapseFallback = revealGroup ? revealGroup.register(id) : false;
905
+ const regResult = revealGroup ? revealGroup.register(id) : null;
906
+ const collapseFallback = regResult?.collapseFallback ?? false;
793
907
  if (collapseFallback && !ctx.async) {
794
908
  commitBoundaryState();
795
909
  ctx.serialize(id, "$$f");
@@ -810,6 +924,10 @@ function createLoadingBoundary(fn, fallback, options) {
810
924
  done = ctx.registerFragment(id, regOpts);
811
925
  (async () => {
812
926
  try {
927
+ while (retryPromise) {
928
+ await retryPromise.catch(() => {});
929
+ ret = runDiscovery();
930
+ }
813
931
  commitBoundaryState();
814
932
  while (ret.p.length) {
815
933
  await Promise.all(ret.p).catch(() => {});
@@ -912,15 +1030,16 @@ function Loading(props) {
912
1030
  function Reveal(props) {
913
1031
  const o = signals.createOwner();
914
1032
  const id = o.id;
915
- const together = !!props.together;
916
- const collapsed = !!props.collapsed;
1033
+ const order = props.order ?? "sequential";
1034
+ const collapsed = order === "sequential" && !!props.collapsed;
917
1035
  if (!sharedConfig.context?.async) {
918
1036
  const parent = signals.getOwner();
919
1037
  const parentGroup = parent ? signals.runWithOwner(parent, () => signals.getContext(RevealGroupContext)) : null;
920
1038
  let collapsedByParent = false;
921
1039
  if (parentGroup) {
922
- collapsedByParent = parentGroup.register(id);
923
- if (collapsed || together) console.warn("Nested <Reveal> with collapsed/together won't coordinate correctly with renderToString. Use renderToStream for full support.");
1040
+ const reg = parentGroup.register(id);
1041
+ collapsedByParent = reg.collapseFallback;
1042
+ if (order === "together" || collapsed) console.warn("Nested <Reveal> with collapsed/together won't coordinate correctly with renderToString. Use renderToStream for full support.");
924
1043
  }
925
1044
  let count = 0;
926
1045
  return signals.runWithOwner(o, () => {
@@ -928,8 +1047,11 @@ function Reveal(props) {
928
1047
  id,
929
1048
  register(_key) {
930
1049
  count++;
931
- if (collapsedByParent) return true;
932
- return !together && collapsed && count > 1;
1050
+ const collapseFallback = collapsedByParent || order === "sequential" && collapsed && count > 1;
1051
+ return {
1052
+ collapseFallback,
1053
+ held: false
1054
+ };
933
1055
  },
934
1056
  onResolved() {}
935
1057
  });
@@ -939,58 +1061,129 @@ function Reveal(props) {
939
1061
  const ctx = sharedConfig.context;
940
1062
  const keys = [];
941
1063
  const resolved = new Set();
1064
+ const minimallyResolved = new Set();
942
1065
  const composites = new Map();
1066
+ const activated = new Set();
1067
+ const stash = [];
1068
+ const collapsedLeafKeys = [];
943
1069
  let frontier = 0;
1070
+ let heldByParent = false;
1071
+ let collapsedByParent = false;
1072
+ let selfMinimallyResolved = false;
1073
+ let notifiedParentDone = false;
944
1074
  const parent = signals.getOwner();
945
1075
  const parentGroup = parent ? signals.runWithOwner(parent, () => signals.getContext(RevealGroupContext)) : null;
946
- let collapsedByParent = false;
947
1076
  if (parentGroup) {
948
- collapsedByParent = parentGroup.register(id, {
1077
+ const reg = parentGroup.register(id, {
949
1078
  onActivate: () => {
950
- collapsedByParent = false;
951
- advanceFrontier();
1079
+ if (!heldByParent) return;
1080
+ heldByParent = false;
1081
+ if (collapsedByParent) {
1082
+ collapsedByParent = false;
1083
+ if (collapsedLeafKeys.length) {
1084
+ ctx.revealFallbacks?.([...collapsedLeafKeys]);
1085
+ collapsedLeafKeys.length = 0;
1086
+ }
1087
+ }
1088
+ if (order === "sequential") advanceFrontier();else if (order === "together") checkTogetherRelease();else naturalRelease();
952
1089
  }
953
1090
  });
1091
+ collapsedByParent = reg.collapseFallback;
1092
+ heldByParent = reg.held;
954
1093
  }
955
1094
  function notifyParentIfDone() {
1095
+ if (notifiedParentDone) return;
956
1096
  if (parentGroup && resolved.size === keys.length) {
1097
+ notifiedParentDone = true;
957
1098
  parentGroup.onResolved(id);
958
1099
  }
959
1100
  }
1101
+ function activateComposite(key) {
1102
+ if (activated.has(key)) return;
1103
+ activated.add(key);
1104
+ composites.get(key)();
1105
+ }
1106
+ function updateSelfMinimallyResolved() {
1107
+ if (selfMinimallyResolved) return;
1108
+ if (keys.length === 0) selfMinimallyResolved = true;else if (order === "together") selfMinimallyResolved = minimallyResolved.size === keys.length;else if (order === "sequential") selfMinimallyResolved = minimallyResolved.has(keys[0]);
1109
+ else selfMinimallyResolved = resolved.size > 0;
1110
+ if (selfMinimallyResolved) parentGroup?.onMinimallyResolved?.(id);
1111
+ }
960
1112
  function advanceFrontier() {
1113
+ if (heldByParent) return;
961
1114
  while (frontier < keys.length && resolved.has(keys[frontier])) {
962
- if (!composites.has(keys[frontier])) ctx.revealFragments?.([keys[frontier]]);
1115
+ const k = keys[frontier];
1116
+ if (composites.has(k)) activateComposite(k);else ctx.revealFragments?.([k]);
963
1117
  frontier++;
964
1118
  }
965
1119
  if (frontier < keys.length) {
966
- const activate = composites.get(keys[frontier]);
967
- if (activate) activate();else if (!together && collapsed) ctx.revealFallbacks?.(keys.slice(frontier));
1120
+ const k = keys[frontier];
1121
+ if (composites.has(k)) activateComposite(k);else if (order === "sequential" && collapsed) ctx.revealFallbacks?.([k]);
968
1122
  }
969
1123
  notifyParentIfDone();
970
1124
  }
1125
+ function checkTogetherRelease() {
1126
+ if (order !== "together" || heldByParent) return;
1127
+ if (minimallyResolved.size < keys.length) return;
1128
+ if (stash.length) {
1129
+ ctx.revealFragments?.([...stash]);
1130
+ stash.length = 0;
1131
+ }
1132
+ composites.forEach((_, key) => activateComposite(key));
1133
+ notifyParentIfDone();
1134
+ }
1135
+ function naturalRelease() {
1136
+ if (stash.length) {
1137
+ ctx.revealFragments?.([...stash]);
1138
+ stash.length = 0;
1139
+ }
1140
+ composites.forEach((_, key) => activateComposite(key));
1141
+ notifyParentIfDone();
1142
+ }
971
1143
  return signals.runWithOwner(o, () => {
972
1144
  signals.setContext(RevealGroupContext, {
973
1145
  id,
974
1146
  register(key, options) {
975
1147
  keys.push(key);
976
- if (options?.onActivate) composites.set(key, options.onActivate);
977
- if (collapsedByParent) return true;
978
- return !together && collapsed && keys.length > 1;
1148
+ const isComposite = !!options?.onActivate;
1149
+ if (isComposite) composites.set(key, options.onActivate);
1150
+ const selfCollapse = order === "sequential" && collapsed && keys.length > 1;
1151
+ const collapseFallback = collapsedByParent || selfCollapse;
1152
+ if (collapseFallback && !isComposite) collapsedLeafKeys.push(key);
1153
+ let held = heldByParent;
1154
+ if (!held) {
1155
+ if (order === "together") held = true;else if (order === "sequential" && keys.length > 1) held = true;
1156
+ }
1157
+ return {
1158
+ collapseFallback,
1159
+ held
1160
+ };
979
1161
  },
980
1162
  onResolved(key) {
981
1163
  resolved.add(key);
982
- if (collapsedByParent) {
983
- notifyParentIfDone();
984
- return;
985
- }
986
- if (together) {
987
- if (resolved.size === keys.length) {
988
- ctx.revealFragments?.(id);
989
- notifyParentIfDone();
1164
+ const isLeaf = !composites.has(key);
1165
+ if (isLeaf) {
1166
+ if (order === "together") {
1167
+ stash.push(key);
1168
+ } else if (order === "natural" && heldByParent) {
1169
+ stash.push(key);
1170
+ } else if (order === "natural") {
1171
+ ctx.revealFragments?.([key]);
990
1172
  }
1173
+ markMinimallyResolved(key);
1174
+ if (order === "sequential" && !heldByParent) advanceFrontier();
1175
+ if (order === "natural") updateSelfMinimallyResolved();
991
1176
  } else {
992
- advanceFrontier();
1177
+ if (!heldByParent) {
1178
+ if (order === "sequential") advanceFrontier();else if (order === "natural") activateComposite(key);
1179
+ }
1180
+ if (order === "together") checkTogetherRelease();
1181
+ if (order === "natural") updateSelfMinimallyResolved();
993
1182
  }
1183
+ notifyParentIfDone();
1184
+ },
1185
+ onMinimallyResolved(key) {
1186
+ markMinimallyResolved(key);
994
1187
  }
995
1188
  });
996
1189
  const result = props.children;
@@ -999,6 +1192,12 @@ function Reveal(props) {
999
1192
  }
1000
1193
  return result;
1001
1194
  });
1195
+ function markMinimallyResolved(key) {
1196
+ if (minimallyResolved.has(key)) return;
1197
+ minimallyResolved.add(key);
1198
+ updateSelfMinimallyResolved();
1199
+ if (order === "together") checkTogetherRelease();
1200
+ }
1002
1201
  }
1003
1202
 
1004
1203
  const DEV = undefined;