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.
- package/Node/component.d.ts +1 -1
- package/Node/vNode.js +2 -4
- package/SYNTAX_PRIMER.md +43 -1
- package/Store/Tree/observableNode.js +1 -1
- package/Utils/decorators.js +1 -1
- package/Utils/index.d.ts +1 -0
- package/Utils/index.js +3 -0
- package/index.d.ts +1 -0
- package/package.json +1 -1
package/Node/component.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 =
|
|
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)(
|
|
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) {
|
package/Utils/decorators.js
CHANGED
|
@@ -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(
|
|
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
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