@c9up/aurora 0.1.15 → 0.1.16

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/hydrate.js CHANGED
@@ -358,29 +358,11 @@ function hydrateSlot(slot, node, value, cleanups, mountHooks, markerCursor) {
358
358
  }
359
359
  }
360
360
  /**
361
- * Hydrate a text slot. SSR inlined the value as a text node (or skipped
362
- * it for null/false/undefined). We locate the **first text node sibling
363
- * preceding the path's terminal index** that's where SSR wrote the
364
- * value and wire an effect that overwrites its `data` on changes.
365
- *
366
- * For reactive values (signals/functions), the effect updates the
367
- * existing text node in place. For nested TemplateResults, we
368
- * recursively hydrate against the captured sibling range.
369
- */
370
- /**
371
- * First text node inside a marker pair's range, or a fresh empty one inserted
372
- * before the end marker (when the SSR value was empty → no text node yet).
361
+ * Hydrate a text slot against its SSR boundary-marker pair. A reactive slot
362
+ * (signal/function) always goes through the swap-capable structured path so a
363
+ * value that changes type (scalar template array) re-renders correctly; a
364
+ * direct template/array adopts its SSR range once; a static scalar is left as-is.
373
365
  */
374
- function reactiveTextNode(pair) {
375
- for (let n = pair.start.nextSibling; n !== null && n !== pair.end; n = n.nextSibling) {
376
- if (n.nodeType === 3 /* TEXT */)
377
- return n;
378
- }
379
- const doc = pair.end.ownerDocument ?? document;
380
- const fresh = doc.createTextNode("");
381
- pair.end.parentNode?.insertBefore(fresh, pair.end);
382
- return fresh;
383
- }
384
366
  function hydrateTextSlot(commentMarker, value, cleanups, mountHooks, markerCursor) {
385
367
  // Every text slot is SSR-wrapped in a <!--$-->…<!--/$--> pair, and its path
386
368
  // resolves (via collapseMarkerRanges) to the start marker. Consume the
@@ -395,39 +377,32 @@ function hydrateTextSlot(commentMarker, value, cleanups, mountHooks, markerCurso
395
377
  const reactiveFn = isSignal(value) || typeof value === "function"
396
378
  ? value
397
379
  : null;
398
- const current = reactiveFn ? reactiveFn() : value;
399
- // Structured value (nested template / array): reactive swap on change;
400
- // direct → adopt the SSR range once (inner bindings wired against it).
401
- if (isTemplateResult(current) || Array.isArray(current)) {
402
- if (reactiveFn) {
403
- hydrateReactiveStructured(reactiveFn, pair, cleanups, mountHooks, markerCursor);
404
- return;
405
- }
380
+ // Reactive slot its value TYPE can change across renders (scalar template
381
+ // array), e.g. `${() => collapsed() ? '' : html`<span>…</span>`}`. Always
382
+ // use the swap-capable structured path: its effect re-renders the value
383
+ // (whatever type) into the marker range via renderValueToNodes. Locking a
384
+ // reactive slot to a scalar text-node effect (based on its FIRST value) would
385
+ // String() a later template/array into "[object Object]".
386
+ if (reactiveFn) {
387
+ hydrateReactiveStructured(reactiveFn, pair, cleanups, mountHooks, markerCursor);
388
+ return;
389
+ }
390
+ // Non-reactive (direct) value — adopt the SSR range once.
391
+ if (isTemplateResult(value) || Array.isArray(value)) {
406
392
  const range = [];
407
393
  for (let n = pair.start.nextSibling; n !== null && n !== pair.end; n = n.nextSibling) {
408
394
  range.push(n);
409
395
  }
410
- if (isTemplateResult(current)) {
411
- hydrateTemplateResult(current, range, cleanups, mountHooks, markerCursor);
396
+ if (isTemplateResult(value)) {
397
+ hydrateTemplateResult(value, range, cleanups, mountHooks, markerCursor);
412
398
  }
413
- else if (Array.isArray(current)) {
414
- // Direct (non-reactive) array — hydrate each item so its inner marker
415
- // pairs are consumed and the cursor stays aligned (same as a reactive
416
- // array's first run).
417
- hydrateArrayItems(current, range, cleanups, mountHooks, markerCursor);
399
+ else if (Array.isArray(value)) {
400
+ // Direct array — hydrate each item so its inner marker pairs are
401
+ // consumed and the cursor stays aligned.
402
+ hydrateArrayItems(value, range, cleanups, mountHooks, markerCursor);
418
403
  }
419
- return;
420
- }
421
- // Scalar: a reactive scalar updates the range's text node on change; a static
422
- // scalar is already rendered between the markers (nothing to wire).
423
- if (reactiveFn) {
424
- const textNode = reactiveTextNode(pair);
425
- const dispose = effect(() => {
426
- const v = reactiveFn();
427
- textNode.data = v == null || v === false ? "" : String(v);
428
- });
429
- cleanups.push(dispose);
430
404
  }
405
+ // else: static scalar — already rendered between the markers.
431
406
  }
432
407
  /**
433
408
  * Pre-marker fallback — best-effort hydration when the SSR markup carries no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/aurora",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Aurora — reactive UI runtime for the Ream framework. Tagged-template DOM, signal-based state, isomorphic SSR + hydration, zero build step.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/hydrate.ts CHANGED
@@ -481,33 +481,11 @@ function hydrateSlot(
481
481
  }
482
482
 
483
483
  /**
484
- * Hydrate a text slot. SSR inlined the value as a text node (or skipped
485
- * it for null/false/undefined). We locate the **first text node sibling
486
- * preceding the path's terminal index** that's where SSR wrote the
487
- * value and wire an effect that overwrites its `data` on changes.
488
- *
489
- * For reactive values (signals/functions), the effect updates the
490
- * existing text node in place. For nested TemplateResults, we
491
- * recursively hydrate against the captured sibling range.
492
- */
493
- /**
494
- * First text node inside a marker pair's range, or a fresh empty one inserted
495
- * before the end marker (when the SSR value was empty → no text node yet).
484
+ * Hydrate a text slot against its SSR boundary-marker pair. A reactive slot
485
+ * (signal/function) always goes through the swap-capable structured path so a
486
+ * value that changes type (scalar template array) re-renders correctly; a
487
+ * direct template/array adopts its SSR range once; a static scalar is left as-is.
496
488
  */
497
- function reactiveTextNode(pair: MarkerPair): Text {
498
- for (
499
- let n = pair.start.nextSibling;
500
- n !== null && n !== pair.end;
501
- n = n.nextSibling
502
- ) {
503
- if (n.nodeType === 3 /* TEXT */) return n as Text;
504
- }
505
- const doc = pair.end.ownerDocument ?? document;
506
- const fresh = doc.createTextNode("");
507
- pair.end.parentNode?.insertBefore(fresh, pair.end);
508
- return fresh;
509
- }
510
-
511
489
  function hydrateTextSlot(
512
490
  commentMarker: Node,
513
491
  value: unknown,
@@ -536,21 +514,26 @@ function hydrateTextSlot(
536
514
  isSignal(value) || typeof value === "function"
537
515
  ? (value as () => unknown)
538
516
  : null;
539
- const current = reactiveFn ? reactiveFn() : value;
540
517
 
541
- // Structured value (nested template / array): reactive swap on change;
542
- // direct adopt the SSR range once (inner bindings wired against it).
543
- if (isTemplateResult(current) || Array.isArray(current)) {
544
- if (reactiveFn) {
545
- hydrateReactiveStructured(
546
- reactiveFn,
547
- pair,
548
- cleanups,
549
- mountHooks,
550
- markerCursor,
551
- );
552
- return;
553
- }
518
+ // Reactive slot — its value TYPE can change across renders (scalar template
519
+ // array), e.g. `${() => collapsed() ? '' : html`<span>…</span>`}`. Always
520
+ // use the swap-capable structured path: its effect re-renders the value
521
+ // (whatever type) into the marker range via renderValueToNodes. Locking a
522
+ // reactive slot to a scalar text-node effect (based on its FIRST value) would
523
+ // String() a later template/array into "[object Object]".
524
+ if (reactiveFn) {
525
+ hydrateReactiveStructured(
526
+ reactiveFn,
527
+ pair,
528
+ cleanups,
529
+ mountHooks,
530
+ markerCursor,
531
+ );
532
+ return;
533
+ }
534
+
535
+ // Non-reactive (direct) value — adopt the SSR range once.
536
+ if (isTemplateResult(value) || Array.isArray(value)) {
554
537
  const range: ChildNode[] = [];
555
538
  for (
556
539
  let n = pair.start.nextSibling;
@@ -559,27 +542,15 @@ function hydrateTextSlot(
559
542
  ) {
560
543
  range.push(n as ChildNode);
561
544
  }
562
- if (isTemplateResult(current)) {
563
- hydrateTemplateResult(current, range, cleanups, mountHooks, markerCursor);
564
- } else if (Array.isArray(current)) {
565
- // Direct (non-reactive) array — hydrate each item so its inner marker
566
- // pairs are consumed and the cursor stays aligned (same as a reactive
567
- // array's first run).
568
- hydrateArrayItems(current, range, cleanups, mountHooks, markerCursor);
545
+ if (isTemplateResult(value)) {
546
+ hydrateTemplateResult(value, range, cleanups, mountHooks, markerCursor);
547
+ } else if (Array.isArray(value)) {
548
+ // Direct array — hydrate each item so its inner marker pairs are
549
+ // consumed and the cursor stays aligned.
550
+ hydrateArrayItems(value, range, cleanups, mountHooks, markerCursor);
569
551
  }
570
- return;
571
- }
572
-
573
- // Scalar: a reactive scalar updates the range's text node on change; a static
574
- // scalar is already rendered between the markers (nothing to wire).
575
- if (reactiveFn) {
576
- const textNode = reactiveTextNode(pair);
577
- const dispose = effect(() => {
578
- const v = reactiveFn();
579
- textNode.data = v == null || v === false ? "" : String(v);
580
- });
581
- cleanups.push(dispose);
582
552
  }
553
+ // else: static scalar — already rendered between the markers.
583
554
  }
584
555
 
585
556
  /**