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