@praxisjs/runtime 0.2.7 → 0.2.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @praxisjs/runtime
2
2
 
3
+ ## 0.2.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c353ba: Add `untrack` utility and isolate component mounting from outer reactive contexts
8
+
9
+ **`@praxisjs/core`** exports two new functions from the public API:
10
+
11
+ - `peek(signal)` — reads a signal once without subscribing to it (was already in `/internal`, now public)
12
+ - `untrack(fn)` — runs a function with no active effect, suppressing all signal tracking inside it
13
+
14
+ ```ts
15
+ import { peek, untrack } from "@praxisjs/core";
16
+
17
+ // read a signal without creating a dependency
18
+ if (peek(this.max) > peek(this.count)) {
19
+ this.count++;
20
+ }
21
+
22
+ // suppress tracking for a block of reads
23
+ const snapshot = untrack(() => this.totalCost);
24
+ ```
25
+
26
+ **`@praxisjs/runtime`** — `mountComponent` now runs entirely inside `untrack`. This fixes a bug where components mounted inside a reactive context (e.g. the router) would accidentally subscribe the outer effect to any signal read during construction or render. The symptoms were:
27
+
28
+ - Eager reads like `description={this.count}` in JSX causing the router to re-mount the component on every state change, resetting state to its initial value
29
+ - `@Debug()` (and any decorator that reads a signal in its `addInitializer`) triggering the same re-mount loop
30
+
31
+ Reactive subscriptions set up via `{() => signal}` in JSX are unaffected — each arrow function creates its own isolated effect.
32
+
33
+ - Updated dependencies [6c353ba]
34
+ - @praxisjs/core@1.2.0
35
+ - @praxisjs/decorators@0.7.1
36
+
37
+ ## 0.2.8
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies [2b8c768]
42
+ - @praxisjs/decorators@0.7.0
43
+
3
44
  ## 0.2.7
4
45
 
5
46
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAG,MAAM,2BAA2B,CAAC;AAKpF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,wBAAgB,cAAc,CAC5B,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,EAAE,KAAK,GACjB,IAAI,EAAE,CA6CR;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAG,MAAM,2BAA2B,CAAC;AAKpF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,wBAAgB,cAAc,CAC5B,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,EAAE,KAAK,GACjB,IAAI,EAAE,CA+CR;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
package/dist/component.js CHANGED
@@ -1,42 +1,45 @@
1
+ import { untrack } from "@praxisjs/core/internal";
1
2
  import { initSlots } from "@praxisjs/decorators";
2
3
  import { isComponent } from "@praxisjs/shared/internal";
3
4
  import { mountChildren } from "./children";
4
5
  import { runInScope } from "./context";
5
6
  export function mountComponent(ctor, props, parentScope) {
6
- const scope = parentScope.fork();
7
- const instance = new ctor({ ...props });
8
- const rawChildren = props.children;
9
- if (rawChildren != null) {
10
- initSlots(instance, rawChildren);
11
- }
12
- const start = document.createComment(`[${ctor.name}]`);
13
- const end = document.createComment(`[/${ctor.name}]`);
14
- // Expose anchor so decorators like @Virtual can find the parent element
15
- instance._anchor = end;
16
- instance.onBeforeMount?.();
17
- const container = document.createDocumentFragment();
18
- container.appendChild(start);
19
- let dom = null;
20
- runInScope(scope, () => {
21
- try {
22
- dom = instance.render();
7
+ return untrack(() => {
8
+ const scope = parentScope.fork();
9
+ const instance = new ctor({ ...props });
10
+ const rawChildren = props.children;
11
+ if (rawChildren != null) {
12
+ initSlots(instance, rawChildren);
23
13
  }
24
- catch (e) {
25
- instance.onError?.(e instanceof Error ? e : new Error(String(e)));
26
- }
27
- });
28
- mountChildren(container, dom, scope);
29
- container.appendChild(end);
30
- queueMicrotask(() => {
31
- instance._mounted = true;
32
- instance.onMount?.();
33
- });
34
- scope.add(() => {
35
- instance.onUnmount?.();
36
- instance._mounted = false;
14
+ const start = document.createComment(`[${ctor.name}]`);
15
+ const end = document.createComment(`[/${ctor.name}]`);
16
+ // Expose anchor so decorators like @Virtual can find the parent element
17
+ instance._anchor = end;
18
+ instance.onBeforeMount?.();
19
+ const container = document.createDocumentFragment();
20
+ container.appendChild(start);
21
+ let dom = null;
22
+ runInScope(scope, () => {
23
+ try {
24
+ dom = instance.render();
25
+ }
26
+ catch (e) {
27
+ instance.onError?.(e instanceof Error ? e : new Error(String(e)));
28
+ }
29
+ });
30
+ mountChildren(container, dom, scope);
31
+ container.appendChild(end);
32
+ queueMicrotask(() => {
33
+ instance._mounted = true;
34
+ instance.onMount?.();
35
+ });
36
+ scope.add(() => {
37
+ instance.onUnmount?.();
38
+ instance._mounted = false;
39
+ });
40
+ // Return the nodes from the fragment as an array so the caller can append them
41
+ return Array.from(container.childNodes);
37
42
  });
38
- // Return the nodes from the fragment as an array so the caller can append them
39
- return Array.from(container.childNodes);
40
43
  }
41
44
  export { isComponent };
42
45
  //# sourceMappingURL=component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAA8B,MAAM,2BAA2B,CAAC;AAEpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAIvC,MAAM,UAAU,cAAc,CAC5B,IAA0B,EAC1B,KAA8B,EAC9B,WAAkB;IAElB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;IAEjC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;IAExC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC;IACnC,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;QACxB,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAEtD,wEAAwE;IACxE,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAC;IAEvB,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC;IAE3B,MAAM,SAAS,GAAG,QAAQ,CAAC,sBAAsB,EAAE,CAAC;IACpD,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAE7B,IAAI,GAAG,GAAyB,IAAI,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;QACrB,IAAI,CAAC;YACH,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAE3B,cAAc,CAAC,GAAG,EAAE;QAClB,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;QACzB,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;QACvB,QAAQ,CAAC,QAAQ,GAAG,KAAK,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,+EAA+E;IAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAA8B,MAAM,2BAA2B,CAAC;AAEpF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAIvC,MAAM,UAAU,cAAc,CAC5B,IAA0B,EAC1B,KAA8B,EAC9B,WAAkB;IAElB,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;QAEjC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;QAExC,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC;QACnC,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YACxB,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAEtD,wEAAwE;QACxE,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAC;QAEvB,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC;QAE3B,MAAM,SAAS,GAAG,QAAQ,CAAC,sBAAsB,EAAE,CAAC;QACpD,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAE7B,IAAI,GAAG,GAAyB,IAAI,CAAC;QACrC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC;gBACH,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC1B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACrC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAE3B,cAAc,CAAC,GAAG,EAAE;YAClB,QAAQ,CAAC,QAAQ,GAAG,IAAI,CAAC;YACzB,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YACb,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACvB,QAAQ,CAAC,QAAQ,GAAG,KAAK,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,+EAA+E;QAC/E,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/runtime",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,8 +14,8 @@
14
14
  "typescript": "^5.9.3"
15
15
  },
16
16
  "dependencies": {
17
- "@praxisjs/core": "1.1.0",
18
- "@praxisjs/decorators": "0.6.1",
17
+ "@praxisjs/decorators": "0.7.1",
18
+ "@praxisjs/core": "1.2.0",
19
19
  "@praxisjs/shared": "0.2.0"
20
20
  },
21
21
  "scripts": {
package/src/component.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { untrack } from "@praxisjs/core/internal";
1
2
  import { initSlots } from "@praxisjs/decorators";
2
3
  import { isComponent, type ComponentConstructor } from "@praxisjs/shared/internal";
3
4
 
@@ -11,50 +12,52 @@ export function mountComponent(
11
12
  props: Record<string, unknown>,
12
13
  parentScope: Scope,
13
14
  ): Node[] {
14
- const scope = parentScope.fork();
15
+ return untrack(() => {
16
+ const scope = parentScope.fork();
15
17
 
16
- const instance = new ctor({ ...props });
18
+ const instance = new ctor({ ...props });
17
19
 
18
- const rawChildren = props.children;
19
- if (rawChildren != null) {
20
- initSlots(instance, rawChildren);
21
- }
20
+ const rawChildren = props.children;
21
+ if (rawChildren != null) {
22
+ initSlots(instance, rawChildren);
23
+ }
22
24
 
23
- const start = document.createComment(`[${ctor.name}]`);
24
- const end = document.createComment(`[/${ctor.name}]`);
25
+ const start = document.createComment(`[${ctor.name}]`);
26
+ const end = document.createComment(`[/${ctor.name}]`);
25
27
 
26
- // Expose anchor so decorators like @Virtual can find the parent element
27
- instance._anchor = end;
28
+ // Expose anchor so decorators like @Virtual can find the parent element
29
+ instance._anchor = end;
28
30
 
29
- instance.onBeforeMount?.();
31
+ instance.onBeforeMount?.();
30
32
 
31
- const container = document.createDocumentFragment();
32
- container.appendChild(start);
33
+ const container = document.createDocumentFragment();
34
+ container.appendChild(start);
33
35
 
34
- let dom: Node | Node[] | null = null;
35
- runInScope(scope, () => {
36
- try {
37
- dom = instance.render();
38
- } catch (e) {
39
- instance.onError?.(e instanceof Error ? e : new Error(String(e)));
40
- }
41
- });
36
+ let dom: Node | Node[] | null = null;
37
+ runInScope(scope, () => {
38
+ try {
39
+ dom = instance.render();
40
+ } catch (e) {
41
+ instance.onError?.(e instanceof Error ? e : new Error(String(e)));
42
+ }
43
+ });
42
44
 
43
- mountChildren(container, dom, scope);
44
- container.appendChild(end);
45
+ mountChildren(container, dom, scope);
46
+ container.appendChild(end);
45
47
 
46
- queueMicrotask(() => {
47
- instance._mounted = true;
48
- instance.onMount?.();
49
- });
48
+ queueMicrotask(() => {
49
+ instance._mounted = true;
50
+ instance.onMount?.();
51
+ });
50
52
 
51
- scope.add(() => {
52
- instance.onUnmount?.();
53
- instance._mounted = false;
54
- });
53
+ scope.add(() => {
54
+ instance.onUnmount?.();
55
+ instance._mounted = false;
56
+ });
55
57
 
56
- // Return the nodes from the fragment as an array so the caller can append them
57
- return Array.from(container.childNodes);
58
+ // Return the nodes from the fragment as an array so the caller can append them
59
+ return Array.from(container.childNodes);
60
+ });
58
61
  }
59
62
 
60
63
  export { isComponent };