j-templates 8.0.0 → 8.0.2

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.
@@ -17,7 +17,7 @@ export declare class Component<D = void, T = void, E = {}> {
17
17
  /**
18
18
  * Returns the component's virtual node injector.
19
19
  */
20
- get Injector(): import("../Utils/injector").Injector;
20
+ get Injector(): import("../Utils").Injector;
21
21
  /**
22
22
  * Indicates whether the component has been destroyed.
23
23
  */
package/Node/vNode.js CHANGED
@@ -17,9 +17,7 @@ var vNode;
17
17
  definition,
18
18
  type: definition.type,
19
19
  injector: definition.componentFactory
20
- ? injector_1.Injector.Scope(injector_1.Injector.Current(), function () {
21
- return new injector_1.Injector();
22
- })
20
+ ? new injector_1.Injector()
23
21
  : (injector_1.Injector.Current() ?? new injector_1.Injector()),
24
22
  parentNode: null,
25
23
  node: definition.node ?? null,
@@ -134,7 +132,7 @@ function InitNode(vnode, parentNode) {
134
132
  nodeConfig_1.NodeConfig.attributeAssignment(node, attrs);
135
133
  }
136
134
  if (componentFactory) {
137
- vnode.component = componentFactory(vnode);
135
+ vnode.component = injector_1.Injector.Scope(vnode.injector, componentFactory, vnode);
138
136
  vnode.component.Bound();
139
137
  Children(vnode, ComponentChildren.bind(vnode.component));
140
138
  }
package/SYNTAX_PRIMER.md CHANGED
@@ -594,7 +594,7 @@ Template() {
594
594
 
595
595
  > ⚠️ **Falsy edge case:** the falsy-collapse rule applies broadly — **`0`, `""`, and `NaN` also collapse to `[]`**, not just `false`/`null`/`undefined`. Only *truthy* non-array values are wrapped as `[value]`.
596
596
 
597
- **Also accepts** `Promise<T>` and `Promise<T[]>` for async data. While a Promise is pending, the scope evaluates to `null` (falsy), so the element renders nothing until resolved — there is no built-in placeholder; implement one yourself if needed.
597
+ **Also accepts** `Promise<T>` and `Promise<T[]>` for async data. While a Promise is pending, the scope evaluates to `null` (falsy), so the element renders nothing until resolved — there is no built-in placeholder; implement one yourself if needed. While a newer promise is pending, the last resolved value is kept.
598
598
 
599
599
  The `false`/`true` behavior makes `data:` a clean conditional rendering mechanism. **The child function is invoked with the truthy value as its data argument** — e.g. `data: () => this.isLoading` calls the child with `true` when loading. It renders its child once when true and nothing when false, with its own isolated reactive scope.
600
600
 
@@ -979,6 +979,8 @@ get userData(): User | null { return getUserSync(this.Data.userId); }
979
979
 
980
980
  **Pattern 3 (@ComputedAsync):** Getter must be synchronous. Returns default value initially, then computed value with same reference via ApplyDiff.
981
981
 
982
+ Shared async-scope behavior — typing, pending/old-value semantics, batching, and `scope()` composition — is covered in [Async Scopes](#async-scopes).
983
+
982
984
  ### State Location
983
985
 
984
986
  | State Type | Location | API |
@@ -995,6 +997,46 @@ get userData(): User | null { return getUserSync(this.Data.userId); }
995
997
 
996
998
  ---
997
999
 
1000
+ ## Async Scopes
1001
+
1002
+ Any function that supplies a scope value can be `async` — a `@Scope()` getter, a `props` or `data` function, or a `scope()`/`gate()`/`peek()`/`mapped()` callback. Services use the same mechanism through `ObservableScope.Create(async)` (see [Async Patterns](#async-patterns)).
1003
+
1004
+ **Typing unwraps the resolved value.** The helper signatures declare `() => T | Promise<T>`, so TypeScript infers the resolved value, not the promise:
1005
+
1006
+ ```typescript
1007
+ scope(async () => (await fetch(url)).text()); // typed string, not Promise<string>
1008
+ ```
1009
+
1010
+ The same unwrap applies to config functions — `FunctionOr<T>` for `props`/`attrs`/`on`, and the `Promise<Array<T>> | Promise<T>` union for `data:`. **The `async` keyword is required:** `IsAsync` detects it via `Symbol.toStringTag`. A plain function that merely *returns* a promise (`() => fetch(url)`) is treated as a synchronous scope whose value is the promise itself — it is never resolved (see Traps #17).
1011
+
1012
+ **Pending behavior.** An async scope renders nothing until its first result arrives: the initial value is `null`, so async `data:` collapses to no children and an async `props:` function applies nothing (the assignment closures skip null inputs). After the first result, the scope **keeps the last result while a new one is pending** — the value is replaced only when the promise resolves, and a resolution that arrives after a newer re-run has started is discarded.
1013
+
1014
+ ```typescript
1015
+ // Valid: nothing is applied to the element until the promise resolves.
1016
+ div({ props: async () => ({ innerHTML: await getMarkup() }) });
1017
+ ```
1018
+
1019
+ **Batching.** Async scopes are greedy — a dependency change marks the scope dirty and queues the re-run on the microtask queue (batched), instead of re-evaluating and propagating synchronously. This is the `greedy: true` behavior noted in Async Patterns.
1020
+
1021
+ **Composition with `scope()`.** `scope()` can compose an async value inside a synchronous scope. The inner scope registers as a dependency of the enclosing watch context, so the **outer scope re-runs when the inner one resolves**:
1022
+
1023
+ ```typescript
1024
+ Template() {
1025
+ return div({
1026
+ props: () => ({
1027
+ innerText: scope(async () => (await fetch(`/api/summary?open=${this.openCount}`)).text()),
1028
+ className: "summary-container",
1029
+ }),
1030
+ });
1031
+ }
1032
+ ```
1033
+
1034
+ `scope()` must be called inside a watch context — a `@Scope()` getter, a children function, or a `props:`/`data:` function (see Traps #11).
1035
+
1036
+ **Dependency capture.** Only reactive reads before the first `await` are tracked — reads after it happen outside the watch context and don't re-run the scope (see Traps #2). Read every reactive value the async scope depends on before the first `await`.
1037
+
1038
+ ---
1039
+
998
1040
  ## Scope Selection Decision Tree
999
1041
 
1000
1042
  Need derived data?
@@ -374,7 +374,7 @@ var ObservableNode;
374
374
  // Replacing rootNode
375
375
  const rootPatch = diffResult[0].value;
376
376
  const rootType = (0, json_2.JsonType)(root);
377
- const rootPatchType = (0, json_2.JsonType)(root);
377
+ const rootPatchType = (0, json_2.JsonType)(rootPatch);
378
378
  if (rootType !== rootPatchType)
379
379
  throw new Error("Unable to change type of Root ObservableNode: " + rootType);
380
380
  switch (rootType) {
@@ -846,7 +846,7 @@ function WatchDecorator(target, propertyKey, descriptor, scopeFunction) {
846
846
  return scopeFunction(instance);
847
847
  }
848
848
  const scope = observableScope_1.ObservableScope.Gated(scopeFunctionWrapper);
849
- const propertyMap = GetScopeMapForInstance(this);
849
+ const propertyMap = GetScopeMapForInstance(instance);
850
850
  propertyMap[propertyKey] = [scope, undefined];
851
851
  observableScope_1.ObservableScope.Watch(scope, function (scope) {
852
852
  instance[propertyKey](observableScope_1.ObservableScope.Value(scope));
package/Utils/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./decorators";
2
2
  export * from "./animation";
3
+ export { Injector } from "./injector";
3
4
  export { IDestroyable } from "./utils.types";
package/Utils/index.js CHANGED
@@ -14,5 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Injector = void 0;
17
18
  __exportStar(require("./decorators"), exports);
18
19
  __exportStar(require("./animation"), exports);
20
+ var injector_1 = require("./injector");
21
+ Object.defineProperty(exports, "Injector", { enumerable: true, get: function () { return injector_1.Injector; } });
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Component } from './Node/component';
2
2
  export { InlineScope as scope, GateScope as gate, PeekScope as peek, MappedScope as mapped } from './Store/Tree/observableScope';
3
+ export { vNode } from './Node/vNode.types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "8.0.0",
3
+ "version": "8.0.2",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",