ngx-blocks-studio 0.2.1 → 0.2.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/README.md
CHANGED
|
@@ -113,7 +113,7 @@ JSON route files can include `children`, `data`, `title`, and guard arrays (`can
|
|
|
113
113
|
|
|
114
114
|
## Example: render a block with `[block]`
|
|
115
115
|
|
|
116
|
-
Register the component key, then pass a **block description** and a shared **`BlockRegistry`** so nested blocks and cross-block refs resolve.
|
|
116
|
+
Register the component key, then pass a **block description** and a shared **`BlockRegistry`** (`BlockRegistryImpl` or `ChainedBlockRegistry`) so nested blocks and cross-block refs resolve. See `docs/core/block-registry.md`.
|
|
117
117
|
|
|
118
118
|
```typescript
|
|
119
119
|
ComponentRegistry.getInstance().register('MyPanel', MyPanelComponent);
|
|
@@ -153,4 +153,4 @@ ComponentRegistry.getInstance().register('MyPanel', MyPanelComponent);
|
|
|
153
153
|
|
|
154
154
|
## API surface (where to look in docs)
|
|
155
155
|
|
|
156
|
-
When you have the repo: **Registry** (registries
|
|
156
|
+
When you have the repo, see `docs/` in the repository root: **Registry** (type registries), **Block instance registry** (`BlockRegistryImpl`, `ChainedBlockRegistry`, `BlockRegistryService`), **Block loader** (`BlockDirective`, refs, outputs), **Route loader**. The source tree under `projects/blocks-studio/src/lib` matches the public exports.
|
|
@@ -1414,6 +1414,87 @@ function resolveRefPath(refPath, ctx) {
|
|
|
1414
1414
|
// can consistently handle signals and functions at the leaf.
|
|
1415
1415
|
return { target: current, path: effectiveParts.slice(-1) };
|
|
1416
1416
|
}
|
|
1417
|
+
/** Root value and property keys for a resolved ref (for path reactivity checks). */
|
|
1418
|
+
function getRefPathWalk(refPath, ctx) {
|
|
1419
|
+
const parsed = parseRefPath(refPath);
|
|
1420
|
+
const { scopeKey, blockId, serviceOrModel, pathParts } = parsed;
|
|
1421
|
+
if (!serviceOrModel) {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
const registry = scopeKey != null ? (ctx.getRegistryForScope?.(scopeKey) ?? null) : ctx.registry;
|
|
1425
|
+
if (scopeKey != null && registry == null) {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
let instance;
|
|
1429
|
+
if (blockId != null) {
|
|
1430
|
+
if (scopeKey == null &&
|
|
1431
|
+
ctx.currentBlockId != null &&
|
|
1432
|
+
blockId === ctx.currentBlockId &&
|
|
1433
|
+
ctx.currentInstance != null) {
|
|
1434
|
+
instance = ctx.currentInstance;
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
if (registry == null) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const handle = registry.get(blockId);
|
|
1441
|
+
if (!handle) {
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
instance = handle.instance;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
instance = ctx.currentInstance ?? undefined;
|
|
1449
|
+
}
|
|
1450
|
+
if (instance == null)
|
|
1451
|
+
return null;
|
|
1452
|
+
if (serviceOrModel === 'model') {
|
|
1453
|
+
const base = instance.model;
|
|
1454
|
+
if (!base) {
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
return { start: base, parts: pathParts };
|
|
1458
|
+
}
|
|
1459
|
+
const service = instance.services?.[serviceOrModel];
|
|
1460
|
+
const shouldModelFallback = blockId != null && service == null && instance.model != null;
|
|
1461
|
+
const base = shouldModelFallback ? instance.model : service;
|
|
1462
|
+
const effectiveParts = shouldModelFallback ? [serviceOrModel, ...pathParts] : pathParts;
|
|
1463
|
+
if (!base) {
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
return { start: base, parts: effectiveParts };
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* True when any segment on the ref path is (or ends at) a signal or callable leaf.
|
|
1470
|
+
*/
|
|
1471
|
+
function refPathHasSignalAlongPath(refPath, ctx) {
|
|
1472
|
+
const walk = getRefPathWalk(refPath, ctx);
|
|
1473
|
+
if (walk == null) {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
let current = walk.start;
|
|
1477
|
+
if (current != null && isSignal(current)) {
|
|
1478
|
+
return true;
|
|
1479
|
+
}
|
|
1480
|
+
const parts = walk.parts;
|
|
1481
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1482
|
+
if (current == null || typeof current !== 'object') {
|
|
1483
|
+
return false;
|
|
1484
|
+
}
|
|
1485
|
+
const key = parts[i];
|
|
1486
|
+
const next = current[key];
|
|
1487
|
+
const isLast = i === parts.length - 1;
|
|
1488
|
+
if (isLast) {
|
|
1489
|
+
return next != null && (isSignal(next) || typeof next === 'function');
|
|
1490
|
+
}
|
|
1491
|
+
if (next != null && isSignal(next)) {
|
|
1492
|
+
return true;
|
|
1493
|
+
}
|
|
1494
|
+
current = next;
|
|
1495
|
+
}
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1417
1498
|
/**
|
|
1418
1499
|
* Get a value from an object by dot-notation path (e.g. "name.firstName" -> obj.name?.firstName).
|
|
1419
1500
|
* Returns undefined if any segment is null/undefined or not an object.
|
|
@@ -1794,7 +1875,10 @@ function interpretFlow(nodes, ctx, scope = {}, options) {
|
|
|
1794
1875
|
}
|
|
1795
1876
|
/* v8 ignore start -- exhaustive node.type chain; non-for nodes handled above */
|
|
1796
1877
|
if (node.type === 'for') {
|
|
1797
|
-
const
|
|
1878
|
+
const rawExpr = node.refPath.trim();
|
|
1879
|
+
const arr = options?.resolveCondition
|
|
1880
|
+
? options.resolveCondition(rawExpr, scope)
|
|
1881
|
+
: resolveRefOrModel(rawExpr, ctx);
|
|
1798
1882
|
const list = Array.isArray(arr) ? arr : [];
|
|
1799
1883
|
for (let idx = 0; idx < list.length; idx++) {
|
|
1800
1884
|
out += interpretFlow(node.body, ctx, {
|
|
@@ -2081,6 +2165,8 @@ function evalExpr(expr, ctx) {
|
|
|
2081
2165
|
if (expr.name === '$event' || expr.name === 'event' || expr.name === 'payload' || expr.name === 'value') {
|
|
2082
2166
|
return ctx.payload;
|
|
2083
2167
|
}
|
|
2168
|
+
if (ctx.scope != null && expr.name in ctx.scope)
|
|
2169
|
+
return ctx.scope[expr.name];
|
|
2084
2170
|
if (expr.name in ctx.model)
|
|
2085
2171
|
return ctx.model[expr.name];
|
|
2086
2172
|
if (ctx.services != null && expr.name in ctx.services)
|
|
@@ -2178,12 +2264,13 @@ function evalExpr(expr, ctx) {
|
|
|
2178
2264
|
return evalExpr(expr.test, ctx) ? evalExpr(expr.consequent, ctx) : evalExpr(expr.alternate, ctx);
|
|
2179
2265
|
}
|
|
2180
2266
|
}
|
|
2181
|
-
function evaluateSafeExpression(expression, resolverContext, payload, resolveRefValue) {
|
|
2267
|
+
function evaluateSafeExpression(expression, resolverContext, payload, resolveRefValue, scope) {
|
|
2182
2268
|
const ast = new Parser(tokenize(expression)).parse();
|
|
2183
2269
|
return evalExpr(ast, {
|
|
2184
2270
|
payload,
|
|
2185
2271
|
model: (resolverContext.currentInstance?.model?.() ?? {}),
|
|
2186
2272
|
services: resolverContext.currentInstance?.services,
|
|
2273
|
+
scope,
|
|
2187
2274
|
resolveRefValue: (ref) => resolveRefValue(ref, resolverContext),
|
|
2188
2275
|
});
|
|
2189
2276
|
}
|
|
@@ -2201,12 +2288,12 @@ function resolveReferenceValue(ref, ctx) {
|
|
|
2201
2288
|
* Safe expression evaluation (same as output `{{ }}` / `@if (...)` when used with this module).
|
|
2202
2289
|
* Falls back to `resolveReferenceValue` if parsing fails (treat whole string as a ref path).
|
|
2203
2290
|
*/
|
|
2204
|
-
function evaluateExpression(expression, ctx, payload) {
|
|
2291
|
+
function evaluateExpression(expression, ctx, payload, scope) {
|
|
2205
2292
|
const trimmed = expression.trim();
|
|
2206
2293
|
if (!trimmed)
|
|
2207
2294
|
return undefined;
|
|
2208
2295
|
try {
|
|
2209
|
-
return evaluateSafeExpression(trimmed, ctx, payload, resolveReferenceValue);
|
|
2296
|
+
return evaluateSafeExpression(trimmed, ctx, payload, resolveReferenceValue, scope);
|
|
2210
2297
|
}
|
|
2211
2298
|
catch {
|
|
2212
2299
|
return resolveReferenceValue(trimmed, ctx);
|
|
@@ -2254,14 +2341,14 @@ function mustacheSegmentToString(segment, ctx, payload) {
|
|
|
2254
2341
|
}
|
|
2255
2342
|
/**
|
|
2256
2343
|
* Unified template resolution: flow (`@if`, `@for`, `${}`) plus `{{ }}` in flow text segments.
|
|
2257
|
-
* `@if`
|
|
2344
|
+
* `@if` / `@for (… of …)` use `evaluateExpression` (safe expressions + ref fallback).
|
|
2258
2345
|
*/
|
|
2259
2346
|
function resolveTemplateString(value, ctx, payload = undefined, options) {
|
|
2260
2347
|
const escapeFlowRefs = options?.escapeFlowRefs !== false;
|
|
2261
2348
|
if (looksLikeFlowTemplate(value)) {
|
|
2262
2349
|
return interpretFlow(parseFlowTemplate(value), ctx, {}, {
|
|
2263
2350
|
escapeRefValues: escapeFlowRefs,
|
|
2264
|
-
resolveCondition: (expr) => evaluateExpression(expr, ctx, payload),
|
|
2351
|
+
resolveCondition: (expr, scope) => evaluateExpression(expr, ctx, payload, scope),
|
|
2265
2352
|
interpolateText: (segment) => mustacheSegmentToString(segment, ctx, payload),
|
|
2266
2353
|
});
|
|
2267
2354
|
}
|
|
@@ -2614,6 +2701,147 @@ function resolveNestedTemplateValues(value, ctx, path = '', visited = new WeakSe
|
|
|
2614
2701
|
}
|
|
2615
2702
|
return value;
|
|
2616
2703
|
}
|
|
2704
|
+
function parseSingleMustacheExpression(value) {
|
|
2705
|
+
const trimmed = value.trim();
|
|
2706
|
+
if (!trimmed.includes('{{') || !trimmed.includes('}}')) {
|
|
2707
|
+
return undefined;
|
|
2708
|
+
}
|
|
2709
|
+
const re = /\{\{([\s\S]*?)\}\}/g;
|
|
2710
|
+
const matches = [...trimmed.matchAll(re)];
|
|
2711
|
+
if (matches.length !== 1 || matches[0][0] !== trimmed) {
|
|
2712
|
+
return undefined;
|
|
2713
|
+
}
|
|
2714
|
+
return matches[0][1].trim();
|
|
2715
|
+
}
|
|
2716
|
+
/** Single full-string mustache / two-way ref for nested input materialization. */
|
|
2717
|
+
function parseTemplateInputRefLeaf(str, ctx) {
|
|
2718
|
+
const twoWayKind = classifyTwoWayString(str);
|
|
2719
|
+
if (twoWayKind === 'two-way') {
|
|
2720
|
+
const refPath = parseTwoWayRef(str);
|
|
2721
|
+
return refPath ? { refPath, embedLeafSignal: false } : undefined;
|
|
2722
|
+
}
|
|
2723
|
+
const expr = parseSingleMustacheExpression(str);
|
|
2724
|
+
if (expr == null) {
|
|
2725
|
+
return undefined;
|
|
2726
|
+
}
|
|
2727
|
+
const refCall = /^ref\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*(true|false))?\s*\)$/.exec(expr);
|
|
2728
|
+
if (refCall) {
|
|
2729
|
+
return {
|
|
2730
|
+
refPath: refCall[1],
|
|
2731
|
+
embedLeafSignal: refCall[2] === 'true',
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
// Only dotted / BlockId:path shapes — not ternaries (`:`) or other safe expressions.
|
|
2735
|
+
if (!isSimpleDottedRefPathExpression(expr)) {
|
|
2736
|
+
return undefined;
|
|
2737
|
+
}
|
|
2738
|
+
return resolveRefPath(expr, ctx) != null ? { refPath: expr, embedLeafSignal: false } : undefined;
|
|
2739
|
+
}
|
|
2740
|
+
/** `BlockId:service.path` or `service.path` only (no `ref()`, ternaries, or operators). */
|
|
2741
|
+
function isSimpleDottedRefPathExpression(expr) {
|
|
2742
|
+
const t = expr.trim();
|
|
2743
|
+
if (!t || /[?+]|===|!==|&&|\|\||\bref\s*\(/.test(t)) {
|
|
2744
|
+
return false;
|
|
2745
|
+
}
|
|
2746
|
+
return /^[\w./-]+:[\w.]+$|^[\w.-]+(?:\.[\w.-]+)+$|^[\w.-]+$/.test(t);
|
|
2747
|
+
}
|
|
2748
|
+
function materializeEmbedRefLeaf(ctx, refPath) {
|
|
2749
|
+
const refSignal = getRefSignal(refPath, ctx);
|
|
2750
|
+
if (refSignal != null) {
|
|
2751
|
+
return refSignal;
|
|
2752
|
+
}
|
|
2753
|
+
if (!refPathHasSignalAlongPath(refPath, ctx)) {
|
|
2754
|
+
return getRefValue(refPath, ctx);
|
|
2755
|
+
}
|
|
2756
|
+
return computed(() => getRefValue(refPath, ctx));
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Stable nested graph at bind time: literals and unresolved template strings stay as-is;
|
|
2760
|
+
* only `{{ ref('path', true) }}` embeds the leaf {@link Signal} (or reactive fallback).
|
|
2761
|
+
*/
|
|
2762
|
+
function materializeNestedReactiveInput(value, ctx, path = '', visited = new WeakSet(), options = {}) {
|
|
2763
|
+
if (typeof value === 'string') {
|
|
2764
|
+
if (!stringNeedsInputResolve(value)) {
|
|
2765
|
+
return value;
|
|
2766
|
+
}
|
|
2767
|
+
const refLeaf = parseTemplateInputRefLeaf(value, ctx);
|
|
2768
|
+
if (refLeaf?.embedLeafSignal) {
|
|
2769
|
+
return materializeEmbedRefLeaf(ctx, refLeaf.refPath);
|
|
2770
|
+
}
|
|
2771
|
+
return value;
|
|
2772
|
+
}
|
|
2773
|
+
if (Array.isArray(value)) {
|
|
2774
|
+
return value.map((item, index) => {
|
|
2775
|
+
const childPath = path ? `${path}[${index}]` : `[${index}]`;
|
|
2776
|
+
return materializeNestedReactiveInput(item, ctx, childPath, visited, options);
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
if (value != null && typeof value === 'object') {
|
|
2780
|
+
if (!isPlainObject(value))
|
|
2781
|
+
return value;
|
|
2782
|
+
if (looksLikeBlockDescriptor(value))
|
|
2783
|
+
return value;
|
|
2784
|
+
if (visited.has(value))
|
|
2785
|
+
return value;
|
|
2786
|
+
visited.add(value);
|
|
2787
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => {
|
|
2788
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
2789
|
+
return [key, materializeNestedReactiveInput(item, ctx, childPath, visited, options)];
|
|
2790
|
+
}));
|
|
2791
|
+
}
|
|
2792
|
+
return value;
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Resolve a {@link materializeNestedReactiveInput} graph for block input binding:
|
|
2796
|
+
* template strings become plain values; embedded {@link Signal}s are kept as references.
|
|
2797
|
+
*/
|
|
2798
|
+
function resolveMaterializedNestedInput(value, ctx, path = '', visited = new WeakSet(), options = {}) {
|
|
2799
|
+
if (isSignal(value)) {
|
|
2800
|
+
return value;
|
|
2801
|
+
}
|
|
2802
|
+
if (typeof value === 'string') {
|
|
2803
|
+
if (!stringNeedsInputResolve(value)) {
|
|
2804
|
+
return value;
|
|
2805
|
+
}
|
|
2806
|
+
return resolveStringLeaf(value, ctx, path || 'input', options);
|
|
2807
|
+
}
|
|
2808
|
+
if (Array.isArray(value)) {
|
|
2809
|
+
return value.map((item, index) => {
|
|
2810
|
+
const childPath = path ? `${path}[${index}]` : `[${index}]`;
|
|
2811
|
+
return resolveMaterializedNestedInput(item, ctx, childPath, visited, options);
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
if (value != null && typeof value === 'object') {
|
|
2815
|
+
if (!isPlainObject(value))
|
|
2816
|
+
return value;
|
|
2817
|
+
if (looksLikeBlockDescriptor(value))
|
|
2818
|
+
return value;
|
|
2819
|
+
if (visited.has(value))
|
|
2820
|
+
return value;
|
|
2821
|
+
visited.add(value);
|
|
2822
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => {
|
|
2823
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
2824
|
+
return [key, resolveMaterializedNestedInput(item, ctx, childPath, visited, options)];
|
|
2825
|
+
}));
|
|
2826
|
+
}
|
|
2827
|
+
return value;
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Deep-read values from {@link materializeNestedReactiveInput}: unwraps embedded
|
|
2831
|
+
* `Signal`s / `computed`s and recurses through plain objects and arrays.
|
|
2832
|
+
*/
|
|
2833
|
+
function readNestedInputValue(value) {
|
|
2834
|
+
if (isSignal(value)) {
|
|
2835
|
+
return readNestedInputValue(value());
|
|
2836
|
+
}
|
|
2837
|
+
if (Array.isArray(value)) {
|
|
2838
|
+
return value.map((item) => readNestedInputValue(item));
|
|
2839
|
+
}
|
|
2840
|
+
if (isPlainObject(value)) {
|
|
2841
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, readNestedInputValue(item)]));
|
|
2842
|
+
}
|
|
2843
|
+
return value;
|
|
2844
|
+
}
|
|
2617
2845
|
|
|
2618
2846
|
const NESTED_INPUT_RESOLVE_OPTIONS = {
|
|
2619
2847
|
resolveTwoWayStringsAsReadonly: true,
|
|
@@ -2730,7 +2958,8 @@ function resolveBlockInputsAndOutputs(description, inputKeys, outputKeys, twoWay
|
|
|
2730
2958
|
}
|
|
2731
2959
|
if (Array.isArray(value) || isPlainObject(value)) {
|
|
2732
2960
|
if (valueNeedsInputResolve(value)) {
|
|
2733
|
-
|
|
2961
|
+
const materialized = materializeNestedReactiveInput(value, ctx, key, new WeakSet(), NESTED_INPUT_RESOLVE_OPTIONS);
|
|
2962
|
+
resolvedInputs[key] = computed(() => resolveMaterializedNestedInput(materialized, ctx, key, new WeakSet(), NESTED_INPUT_RESOLVE_OPTIONS));
|
|
2734
2963
|
}
|
|
2735
2964
|
else if (inputKeys.has(key)) {
|
|
2736
2965
|
resolvedInputs[key] = () => value;
|
|
@@ -2754,6 +2983,12 @@ function resolveBlockInputsAndOutputs(description, inputKeys, outputKeys, twoWay
|
|
|
2754
2983
|
return { resolvedInputs, resolvedOutputs, resolvedTwoWay };
|
|
2755
2984
|
}
|
|
2756
2985
|
|
|
2986
|
+
function isBlockLoadStale(options) {
|
|
2987
|
+
if (options?.loadGeneration == null || options.getCurrentLoadGeneration == null) {
|
|
2988
|
+
return false;
|
|
2989
|
+
}
|
|
2990
|
+
return options.loadGeneration !== options.getCurrentLoadGeneration();
|
|
2991
|
+
}
|
|
2757
2992
|
const INTERPOLATE_MAX_PLACEHOLDERS = 200;
|
|
2758
2993
|
/** Builds a two-way binding when only a read getter exists for the key (used by {@link createInputBindingsWithWarnings}). */
|
|
2759
2994
|
function linkedTwoWayFromInputGetter(resolvedInputs, key) {
|
|
@@ -2794,9 +3029,13 @@ class BlockLoaderService {
|
|
|
2794
3029
|
serviceRegistry = ServiceRegistry.getInstance();
|
|
2795
3030
|
blockRegistryService = inject(BlockRegistryService);
|
|
2796
3031
|
async load(description, viewContainerRef, model, options) {
|
|
3032
|
+
const blockId = (id) => id != null && id !== '' ? id : null;
|
|
2797
3033
|
let resolved = description;
|
|
2798
3034
|
if (isBlockReference(description)) {
|
|
2799
3035
|
resolved = await resolveBlockReference(description, options?.blockDefinitions);
|
|
3036
|
+
if (isBlockLoadStale(options)) {
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
2800
3039
|
}
|
|
2801
3040
|
const parsed = safeParseBlockDescription(resolved);
|
|
2802
3041
|
if (!parsed.success) {
|
|
@@ -2804,10 +3043,10 @@ class BlockLoaderService {
|
|
|
2804
3043
|
}
|
|
2805
3044
|
const desc = parsed.data;
|
|
2806
3045
|
const registry = options?.registry ?? new BlockRegistryImpl();
|
|
2807
|
-
if (desc.id != null && desc.id !== '' && registry.hasLocal(desc.id)) {
|
|
2808
|
-
throw new Error(`Block id "${desc.id}" is already registered.`);
|
|
2809
|
-
}
|
|
2810
3046
|
const componentType = await this.componentRegistry.get(desc.component);
|
|
3047
|
+
if (isBlockLoadStale(options)) {
|
|
3048
|
+
return null;
|
|
3049
|
+
}
|
|
2811
3050
|
if (!componentType) {
|
|
2812
3051
|
throw new Error(`Component "${desc.component}" is not registered.`);
|
|
2813
3052
|
}
|
|
@@ -2823,13 +3062,16 @@ class BlockLoaderService {
|
|
|
2823
3062
|
this.resolveContextModel(ctx, model);
|
|
2824
3063
|
const resolvedModel = blockInstance.model;
|
|
2825
3064
|
const handle = { instance: blockInstance };
|
|
2826
|
-
//... Register the block instance with the registry if it has an id
|
|
2827
|
-
if (desc.id != null && desc.id !== '')
|
|
2828
|
-
registry.register(desc.id, handle);
|
|
2829
3065
|
//... Resolve services for the block
|
|
2830
3066
|
await this.resolveServices(viewContainerRef, blockInstance, desc, resolvedModel, model);
|
|
3067
|
+
if (isBlockLoadStale(options)) {
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
2831
3070
|
const directiveIds = normalizeDirectives(desc.directives);
|
|
2832
3071
|
const directiveTypes = await this.getDirectiveTypes(directiveIds);
|
|
3072
|
+
if (isBlockLoadStale(options)) {
|
|
3073
|
+
return null;
|
|
3074
|
+
}
|
|
2833
3075
|
const nonNullDirectiveTypes = directiveTypes.filter((d) => d != null);
|
|
2834
3076
|
// get inputs and outputs from the component and host directives
|
|
2835
3077
|
const componentMetadata = getBlockInputsAndOutputs([componentType]);
|
|
@@ -2861,11 +3103,36 @@ class BlockLoaderService {
|
|
|
2861
3103
|
...createInputBindingsWithWarnings(`directive "${d.type.name}"`, d.metadata.inputs, d.metadata.twoWay, d.metadata.outputs, initialResolved.resolvedInputs, initialResolved.resolvedTwoWay, initialResolved.resolvedOutputs),
|
|
2862
3104
|
],
|
|
2863
3105
|
}));
|
|
2864
|
-
const
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
3106
|
+
const id = blockId(desc.id);
|
|
3107
|
+
try {
|
|
3108
|
+
if (id != null) {
|
|
3109
|
+
if (registry.hasLocal(id)) {
|
|
3110
|
+
registry.unregister(id);
|
|
3111
|
+
}
|
|
3112
|
+
if (isBlockLoadStale(options)) {
|
|
3113
|
+
return null;
|
|
3114
|
+
}
|
|
3115
|
+
registry.register(id, handle);
|
|
3116
|
+
}
|
|
3117
|
+
const componentRef = viewContainerRef.createComponent(componentType, {
|
|
3118
|
+
directives: directiveAndBindings,
|
|
3119
|
+
bindings: componentBindings,
|
|
3120
|
+
});
|
|
3121
|
+
if (isBlockLoadStale(options)) {
|
|
3122
|
+
componentRef.destroy();
|
|
3123
|
+
if (id != null) {
|
|
3124
|
+
registry.unregister(id);
|
|
3125
|
+
}
|
|
3126
|
+
return null;
|
|
3127
|
+
}
|
|
3128
|
+
return componentRef;
|
|
3129
|
+
}
|
|
3130
|
+
catch (err) {
|
|
3131
|
+
if (id != null) {
|
|
3132
|
+
registry.unregister(id);
|
|
3133
|
+
}
|
|
3134
|
+
throw err;
|
|
3135
|
+
}
|
|
2869
3136
|
}
|
|
2870
3137
|
async resolveServices(viewContainerRef, blockInstance, desc, resolvedModel, model) {
|
|
2871
3138
|
// services are stored under `blockInstance.services`, so `model` no longer collides structurally,
|
|
@@ -3040,14 +3307,12 @@ function clearBlockDirective(state, viewContainerRef) {
|
|
|
3040
3307
|
state.lastRegistry?.unregister(state.lastId);
|
|
3041
3308
|
state.lastId = null;
|
|
3042
3309
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
state.loadedDescKey = null;
|
|
3050
|
-
}
|
|
3310
|
+
viewContainerRef.clear();
|
|
3311
|
+
state.loadResult = null;
|
|
3312
|
+
state.loadedComponent = null;
|
|
3313
|
+
state.loadedServicesKey = null;
|
|
3314
|
+
state.loadedReloadKey = null;
|
|
3315
|
+
state.loadedDescKey = null;
|
|
3051
3316
|
}
|
|
3052
3317
|
function runBlockDirectiveEffect(state, ctx, desc, inputDefs, blockRegistry, reloadKey) {
|
|
3053
3318
|
const runId = ++state.effectRunId;
|
|
@@ -3091,10 +3356,15 @@ function runBlockDirectiveEffect(state, ctx, desc, inputDefs, blockRegistry, rel
|
|
|
3091
3356
|
state.lastId = data.id ?? null;
|
|
3092
3357
|
const generation = ++state.loadGeneration;
|
|
3093
3358
|
ctx
|
|
3094
|
-
.loadBlock(resolved, registry, inputDefs ?? undefined)
|
|
3359
|
+
.loadBlock(resolved, registry, inputDefs ?? undefined, generation, () => state.loadGeneration)
|
|
3095
3360
|
.then((result) => {
|
|
3096
|
-
if (generation !== state.loadGeneration)
|
|
3361
|
+
if (generation !== state.loadGeneration) {
|
|
3362
|
+
result?.destroy();
|
|
3097
3363
|
return;
|
|
3364
|
+
}
|
|
3365
|
+
if (result == null) {
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3098
3368
|
state.loadResult = result;
|
|
3099
3369
|
state.loadedComponent = data.component;
|
|
3100
3370
|
state.loadedServicesKey = servicesKey;
|
|
@@ -3145,10 +3415,12 @@ class BlockDirective {
|
|
|
3145
3415
|
*/
|
|
3146
3416
|
reloadKey = input(null, /* @ts-ignore */
|
|
3147
3417
|
...(ngDevMode ? [{ debugName: "reloadKey" }] : /* istanbul ignore next */ []));
|
|
3148
|
-
loadBlock(resolved, registry, blockDefinitions) {
|
|
3418
|
+
loadBlock(resolved, registry, blockDefinitions, loadGeneration, getCurrentLoadGeneration) {
|
|
3149
3419
|
return this.loader.load(resolved, this.viewContainerRef, this.model, {
|
|
3150
3420
|
registry,
|
|
3151
3421
|
blockDefinitions,
|
|
3422
|
+
loadGeneration,
|
|
3423
|
+
getCurrentLoadGeneration,
|
|
3152
3424
|
});
|
|
3153
3425
|
}
|
|
3154
3426
|
constructor() {
|
|
@@ -3179,5 +3451,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.3", ngImpor
|
|
|
3179
3451
|
* Generated bundle index. Do not edit.
|
|
3180
3452
|
*/
|
|
3181
3453
|
|
|
3182
|
-
export { BlockDefinitionsRegistry, BlockDirective, BlockLoaderService, BlockRegistryImpl, BlockRegistryService, ChainedBlockRegistry, ComponentRegistry, DirectiveRegistry, GuardRegistry, INTERPOLATE_MAX_PLACEHOLDERS, RegistryMetadataStore, RouteLoader, ServiceRegistry, buildComputedForTemplate, classifyTwoWayString, collectRefPaths, createInputBindingsWithWarnings, createOutputHandler, deepMergeBlockDefinition, evaluateExpression, extractReadonlyRefs, getRefPathFromReadonly, getRefSignal, getRefValue, getValueByPath, interpretFlow, isBlockDefinitionLoader, isBlockReference, isInvalidTwoWayMix, isOutputCallObject, isOutputReference, isPlainObject, isTwoWayRefString, linkedTwoWayFromInputGetter, looksLikeBlockDescriptor, looksLikeFlowTemplate, mustacheSegmentToString, normalizeDirectives, normalizeServices, parseFlowTemplate, parseRefPath, parseTwoWayRef, refPathResolvesToSignal, resolveBlockReference, resolveMustacheInterpolatedString, resolveNestedTemplateValues, resolveRefOrModel, resolveRefPath, resolveReferenceValue, resolveTemplateString, safeParseBlockDescription, setRefValue, shouldDeferInputTemplateResolution, splitCallableRef, valueContainsMustacheInTree, valueNeedsInputResolve };
|
|
3454
|
+
export { BlockDefinitionsRegistry, BlockDirective, BlockLoaderService, BlockRegistryImpl, BlockRegistryService, ChainedBlockRegistry, ComponentRegistry, DirectiveRegistry, GuardRegistry, INTERPOLATE_MAX_PLACEHOLDERS, RegistryMetadataStore, RouteLoader, ServiceRegistry, buildComputedForTemplate, classifyTwoWayString, collectRefPaths, createInputBindingsWithWarnings, createOutputHandler, deepMergeBlockDefinition, evaluateExpression, extractReadonlyRefs, getRefPathFromReadonly, getRefSignal, getRefValue, getValueByPath, interpretFlow, isBlockDefinitionLoader, isBlockReference, isInvalidTwoWayMix, isOutputCallObject, isOutputReference, isPlainObject, isTwoWayRefString, linkedTwoWayFromInputGetter, looksLikeBlockDescriptor, looksLikeFlowTemplate, materializeNestedReactiveInput, mustacheSegmentToString, normalizeDirectives, normalizeServices, parseFlowTemplate, parseRefPath, parseTemplateInputRefLeaf, parseTwoWayRef, readNestedInputValue, refPathHasSignalAlongPath, refPathResolvesToSignal, resolveBlockReference, resolveMaterializedNestedInput, resolveMustacheInterpolatedString, resolveNestedTemplateValues, resolveRefOrModel, resolveRefPath, resolveReferenceValue, resolveTemplateString, safeParseBlockDescription, setRefValue, shouldDeferInputTemplateResolution, splitCallableRef, valueContainsMustacheInTree, valueNeedsInputResolve };
|
|
3183
3455
|
//# sourceMappingURL=ngx-blocks-studio.mjs.map
|