dalila 1.5.3 → 1.5.5

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.
@@ -582,6 +582,16 @@ export function createRouter(config) {
582
582
  replace: true
583
583
  };
584
584
  }
585
+ /**
586
+ * Mount nodes into outlet and remove loading state
587
+ */
588
+ function mountToOutlet(...nodes) {
589
+ outletElement.replaceChildren(...nodes);
590
+ queueMicrotask(() => {
591
+ outletElement.removeAttribute('d-loading');
592
+ outletElement.setAttribute('d-ready', '');
593
+ });
594
+ }
585
595
  /**
586
596
  * Core navigation pipeline.
587
597
  *
@@ -685,7 +695,7 @@ export function createRouter(config) {
685
695
  const result = withScope(errorScope, () => errorFn(errorCtx, error));
686
696
  const wrapped = wrapWithLayouts(matchStack, errorCtx, result, [], errorIndex !== -1 ? errorIndex : matchStack.length - 1, true);
687
697
  const nodes = Array.isArray(wrapped) ? wrapped : [wrapped];
688
- outletElement.replaceChildren(...nodes);
698
+ mountToOutlet(...nodes);
689
699
  }
690
700
  catch (renderError) {
691
701
  console.error('[Dalila] Error view failed:', renderError);
@@ -796,10 +806,10 @@ export function createRouter(config) {
796
806
  if (notFoundView) {
797
807
  const result = withScope(scope, () => notFoundView(ctx));
798
808
  const nodes = Array.isArray(result) ? result : [result];
799
- outletElement.replaceChildren(...nodes);
809
+ mountToOutlet(...nodes);
800
810
  }
801
811
  else {
802
- outletElement.replaceChildren();
812
+ mountToOutlet();
803
813
  }
804
814
  }
805
815
  catch (error) {
@@ -827,16 +837,16 @@ export function createRouter(config) {
827
837
  const result = withScope(scope, () => boundaryMatch.route.notFound(boundaryCtx));
828
838
  const wrapped = wrapWithLayouts(matchStack, boundaryCtx, result, [], notFoundIndex, true);
829
839
  const nodes = Array.isArray(wrapped) ? wrapped : [wrapped];
830
- outletElement.replaceChildren(...nodes);
840
+ mountToOutlet(...nodes);
831
841
  }
832
842
  else if (notFoundView) {
833
843
  const result = withScope(scope, () => notFoundView(ctx));
834
844
  const wrapped = wrapWithLayouts(matchStack, ctx, result, [], matchStack.length - 1, true);
835
845
  const nodes = Array.isArray(wrapped) ? wrapped : [wrapped];
836
- outletElement.replaceChildren(...nodes);
846
+ mountToOutlet(...nodes);
837
847
  }
838
848
  else {
839
- outletElement.replaceChildren();
849
+ mountToOutlet();
840
850
  }
841
851
  }
842
852
  catch (error) {
@@ -878,12 +888,12 @@ export function createRouter(config) {
878
888
  const result = withScope(scope, () => pendingMatch.route.pending(pendingCtx));
879
889
  const wrapped = wrapWithLayouts(matchStack, pendingCtx, result, [], pendingIndex, true);
880
890
  const nodes = Array.isArray(wrapped) ? wrapped : [wrapped];
881
- outletElement.replaceChildren(...nodes);
891
+ mountToOutlet(...nodes);
882
892
  }
883
893
  else if (pendingView) {
884
894
  const result = withScope(scope, () => pendingView(ctx));
885
895
  const nodes = Array.isArray(result) ? result : [result];
886
- outletElement.replaceChildren(...nodes);
896
+ mountToOutlet(...nodes);
887
897
  }
888
898
  }
889
899
  catch (error) {
@@ -923,7 +933,7 @@ export function createRouter(config) {
923
933
  const result = withScope(scope, () => errorFn(errorCtx, error));
924
934
  const wrapped = wrapWithLayouts(matchStack, errorCtx, result, [], errorIndex !== -1 ? errorIndex : matchStack.length - 1, true);
925
935
  const nodes = Array.isArray(wrapped) ? wrapped : [wrapped];
926
- outletElement.replaceChildren(...nodes);
936
+ mountToOutlet(...nodes);
927
937
  }
928
938
  catch (err) {
929
939
  console.error('[Dalila] Error view failed:', err);
@@ -1014,7 +1024,7 @@ export function createRouter(config) {
1014
1024
  }
1015
1025
  if (content) {
1016
1026
  const nodes = Array.isArray(content) ? content : [content];
1017
- outletElement.replaceChildren(...nodes);
1027
+ mountToOutlet(...nodes);
1018
1028
  }
1019
1029
  }
1020
1030
  catch (error) {
@@ -16,11 +16,17 @@ export interface BindOptions {
16
16
  */
17
17
  rawTextSelectors?: string;
18
18
  /**
19
- * Internal flag — set by bindEach for clone bindings.
20
- * Skips HMR context registration and d-ready/d-loading lifecycle.
19
+ * Internal flag — set by fromHtml for router/template rendering.
20
+ * Skips HMR context registration but KEEPS d-ready/d-loading lifecycle.
21
21
  * @internal
22
22
  */
23
23
  _internal?: boolean;
24
+ /**
25
+ * Internal flag — set by bindEach for clone bindings.
26
+ * Skips both HMR context registration AND d-ready/d-loading lifecycle.
27
+ * @internal
28
+ */
29
+ _skipLifecycle?: boolean;
24
30
  }
25
31
  export interface BindContext {
26
32
  [key: string]: unknown;
@@ -293,7 +293,7 @@ function bindEach(root, ctx, cleanups) {
293
293
  // Mark BEFORE bind() so the parent's subsequent global passes
294
294
  // (text, attrs, events …) skip this subtree entirely.
295
295
  clone.setAttribute('data-dalila-internal-bound', '');
296
- const dispose = bind(clone, itemCtx, { _internal: true });
296
+ const dispose = bind(clone, itemCtx, { _skipLifecycle: true });
297
297
  currentDisposes.push(dispose);
298
298
  comment.parentNode?.insertBefore(clone, comment);
299
299
  currentClones.push(clone);
@@ -548,7 +548,7 @@ export function bind(root, ctx, options = {}) {
548
548
  });
549
549
  // Bindings complete: remove loading state and mark as ready.
550
550
  // Only the top-level bind owns this lifecycle — d-each clones skip it.
551
- if (!options._internal) {
551
+ if (!options._skipLifecycle) {
552
552
  queueMicrotask(() => {
553
553
  htmlRoot.removeAttribute('d-loading');
554
554
  htmlRoot.setAttribute('d-ready', '');
@@ -31,6 +31,12 @@ export function fromHtml(html, options = {}) {
31
31
  const container = document.createElement('div');
32
32
  container.style.display = 'contents';
33
33
  container.appendChild(template.content);
34
+ // Bind BEFORE inserting children so the layout's bind() only processes
35
+ // the layout's own HTML — children are already bound by their own fromHtml() call.
36
+ const dispose = bind(container, data ?? {}, { _internal: true });
37
+ if (scope) {
38
+ scope.onCleanup(dispose);
39
+ }
34
40
  if (children) {
35
41
  const slot = container.querySelector('[data-slot="children"]');
36
42
  if (slot) {
@@ -42,10 +48,5 @@ export function fromHtml(html, options = {}) {
42
48
  }
43
49
  }
44
50
  }
45
- // Router/template rendering should not register global HMR bind context.
46
- const dispose = bind(container, data ?? {}, { _internal: true });
47
- if (scope) {
48
- scope.onCleanup(dispose);
49
- }
50
51
  return container;
51
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dalila",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "DOM-first reactive framework based on signals",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",