ngx-blocks-studio 0.0.1 → 0.0.3
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.
Potentially problematic release.
This version of ngx-blocks-studio might be problematic. Click here for more details.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, Injectable, Injector, effect, ViewContainerRef, DestroyRef, input, Directive } from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, Injectable, isSignal, Injector, effect, ViewContainerRef, DestroyRef, input, Directive } from '@angular/core';
|
|
3
3
|
import { Router } from '@angular/router';
|
|
4
4
|
import { HttpClient } from '@angular/common/http';
|
|
5
5
|
import { firstValueFrom } from 'rxjs';
|
|
@@ -641,6 +641,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
641
641
|
}]
|
|
642
642
|
}] });
|
|
643
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Global registry of block id → definition. Register block configs at app init
|
|
646
|
+
* so they can be used as templates anywhere (e.g. nested blocks that reference
|
|
647
|
+
* { id: 'AppNav' } resolve without passing blockDefinitions down).
|
|
648
|
+
* Per-call blockDefinitions (e.g. from route data) override global entries.
|
|
649
|
+
*/
|
|
650
|
+
class BlockDefinitionsRegistry {
|
|
651
|
+
static instance;
|
|
652
|
+
definitions = new Map();
|
|
653
|
+
constructor() { }
|
|
654
|
+
static getInstance() {
|
|
655
|
+
if (!BlockDefinitionsRegistry.instance) {
|
|
656
|
+
BlockDefinitionsRegistry.instance = new BlockDefinitionsRegistry();
|
|
657
|
+
}
|
|
658
|
+
return BlockDefinitionsRegistry.instance;
|
|
659
|
+
}
|
|
660
|
+
/** Register a block template by id. Can be called before the block is needed. */
|
|
661
|
+
register(id, definition) {
|
|
662
|
+
this.definitions.set(id, definition);
|
|
663
|
+
}
|
|
664
|
+
/** Get one definition by id. */
|
|
665
|
+
get(id) {
|
|
666
|
+
return this.definitions.get(id);
|
|
667
|
+
}
|
|
668
|
+
/** Get all registered definitions (id → definition). Used to merge with per-call definitions. */
|
|
669
|
+
getAll() {
|
|
670
|
+
const out = {};
|
|
671
|
+
this.definitions.forEach((def, id) => {
|
|
672
|
+
out[id] = def;
|
|
673
|
+
});
|
|
674
|
+
return out;
|
|
675
|
+
}
|
|
676
|
+
unregister(id) {
|
|
677
|
+
return this.definitions.delete(id);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
644
681
|
/**
|
|
645
682
|
* Service entry: root-scoped (string id) or self-scoped ({ id, scope: "self" }).
|
|
646
683
|
*/
|
|
@@ -649,6 +686,7 @@ const ServiceEntrySchema = z.union([
|
|
|
649
686
|
z.object({
|
|
650
687
|
id: z.string().min(1),
|
|
651
688
|
scope: z.literal('self'),
|
|
689
|
+
alias: z.string().min(1).optional(),
|
|
652
690
|
}),
|
|
653
691
|
]);
|
|
654
692
|
/**
|
|
@@ -659,26 +697,29 @@ const OutputReferenceSchema = z.object({
|
|
|
659
697
|
reference: z.string().min(1),
|
|
660
698
|
method: z.string().min(1),
|
|
661
699
|
params: z.union([z.array(z.unknown()), z.record(z.string(), z.unknown())]).optional(),
|
|
662
|
-
then: z
|
|
700
|
+
then: z
|
|
701
|
+
.array(z.object({
|
|
663
702
|
reference: z.string().min(1),
|
|
664
703
|
method: z.string().min(1),
|
|
665
704
|
params: z.union([z.array(z.unknown()), z.record(z.string(), z.unknown())]).optional(),
|
|
666
|
-
}))
|
|
667
|
-
|
|
705
|
+
}))
|
|
706
|
+
.optional(),
|
|
707
|
+
onSuccess: z
|
|
708
|
+
.object({
|
|
668
709
|
reference: z.string().min(1),
|
|
669
710
|
method: z.string().min(1),
|
|
670
711
|
params: z.union([z.array(z.unknown()), z.record(z.string(), z.unknown())]).optional(),
|
|
671
|
-
})
|
|
672
|
-
|
|
712
|
+
})
|
|
713
|
+
.optional(),
|
|
714
|
+
onError: z
|
|
715
|
+
.object({
|
|
673
716
|
reference: z.string().min(1),
|
|
674
717
|
method: z.string().min(1),
|
|
675
718
|
params: z.union([z.array(z.unknown()), z.record(z.string(), z.unknown())]).optional(),
|
|
676
|
-
})
|
|
719
|
+
})
|
|
720
|
+
.optional(),
|
|
677
721
|
});
|
|
678
|
-
const OutputValueSchema = z.union([
|
|
679
|
-
z.record(z.string(), z.unknown()),
|
|
680
|
-
OutputReferenceSchema,
|
|
681
|
-
]);
|
|
722
|
+
const OutputValueSchema = z.union([z.record(z.string(), z.unknown()), OutputReferenceSchema]);
|
|
682
723
|
/**
|
|
683
724
|
* Block description: JSON-serializable descriptor for dynamic block loading.
|
|
684
725
|
* Refs in inputs use instance namespace: instance.FormState.firstName or UserForm.instance.FormState.firstName.
|
|
@@ -686,10 +727,10 @@ const OutputValueSchema = z.union([
|
|
|
686
727
|
const BlockDescriptionSchema = z.object({
|
|
687
728
|
component: z.string().min(1),
|
|
688
729
|
id: z.string().min(1).optional(),
|
|
689
|
-
services: z
|
|
690
|
-
ServiceEntrySchema,
|
|
691
|
-
|
|
692
|
-
|
|
730
|
+
services: z
|
|
731
|
+
.union([ServiceEntrySchema, z.array(ServiceEntrySchema)])
|
|
732
|
+
.optional()
|
|
733
|
+
.default([]),
|
|
693
734
|
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
694
735
|
outputs: z.record(z.string(), OutputValueSchema).optional(),
|
|
695
736
|
});
|
|
@@ -699,9 +740,6 @@ function normalizeServices(services) {
|
|
|
699
740
|
return [];
|
|
700
741
|
return Array.isArray(services) ? services : [services];
|
|
701
742
|
}
|
|
702
|
-
function parseBlockDescription(data) {
|
|
703
|
-
return BlockDescriptionSchema.parse(data);
|
|
704
|
-
}
|
|
705
743
|
function safeParseBlockDescription(data) {
|
|
706
744
|
return BlockDescriptionSchema.safeParse(data);
|
|
707
745
|
}
|
|
@@ -718,10 +756,6 @@ function isBlockReference(value) {
|
|
|
718
756
|
const id = value.id ?? value.blockId;
|
|
719
757
|
return typeof id === 'string' && id.length > 0;
|
|
720
758
|
}
|
|
721
|
-
/** @deprecated Use isBlockReference. Id-only is still supported as { id: string }. */
|
|
722
|
-
function isIdReference(value) {
|
|
723
|
-
return isBlockReference(value) && !value.blockDefinition;
|
|
724
|
-
}
|
|
725
759
|
/**
|
|
726
760
|
* Deep-merge override onto base. Only keys present in override are changed; nested objects
|
|
727
761
|
* are merged recursively so e.g. override.inputs.model does not remove base.inputs.rows.
|
|
@@ -747,60 +781,33 @@ function deepMergeBlockDefinition(base, override) {
|
|
|
747
781
|
return result;
|
|
748
782
|
}
|
|
749
783
|
/**
|
|
750
|
-
* Resolve a block reference to a full description using
|
|
751
|
-
*
|
|
784
|
+
* Resolve a block reference to a full description using optional per-call definitions
|
|
785
|
+
* and the global BlockDefinitionsRegistry. Per-call blockDefinitions take precedence
|
|
786
|
+
* over global entries. If blockDefinition is present, it is deep-merged onto the base;
|
|
787
|
+
* otherwise returns a shallow copy of the base.
|
|
752
788
|
*/
|
|
753
789
|
function resolveBlockReference(ref, blockDefinitions) {
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
790
|
+
const blockId = ref.blockId;
|
|
791
|
+
const id = ref.id;
|
|
792
|
+
const registry = BlockDefinitionsRegistry.getInstance();
|
|
793
|
+
if (!blockId || typeof blockId !== 'string')
|
|
794
|
+
throw new Error('Block reference must have blockId.');
|
|
795
|
+
let base;
|
|
796
|
+
if (blockDefinitions && Object.prototype.hasOwnProperty.call(blockDefinitions, blockId)) {
|
|
797
|
+
base = blockDefinitions[blockId];
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
base = registry.get(blockId);
|
|
801
|
+
}
|
|
758
802
|
if (base == null || typeof base !== 'object')
|
|
759
|
-
throw new Error(`Block "${
|
|
760
|
-
const baseObj = base;
|
|
803
|
+
throw new Error(`Block "${blockId}" has no definition.`);
|
|
804
|
+
const baseObj = { ...base, id };
|
|
761
805
|
const overrides = ref.blockDefinition;
|
|
762
806
|
if (overrides == null || typeof overrides !== 'object' || Object.keys(overrides).length === 0)
|
|
763
807
|
return { ...baseObj };
|
|
764
808
|
return deepMergeBlockDefinition(baseObj, overrides);
|
|
765
809
|
}
|
|
766
810
|
|
|
767
|
-
/**
|
|
768
|
-
* Global registry of block id → definition. Register block configs at app init
|
|
769
|
-
* so they can be used as templates anywhere (e.g. nested blocks that reference
|
|
770
|
-
* { id: 'AppNav' } resolve without passing blockDefinitions down).
|
|
771
|
-
* Per-call blockDefinitions (e.g. from route data) override global entries.
|
|
772
|
-
*/
|
|
773
|
-
class BlockDefinitionsRegistry {
|
|
774
|
-
static instance;
|
|
775
|
-
definitions = new Map();
|
|
776
|
-
constructor() { }
|
|
777
|
-
static getInstance() {
|
|
778
|
-
if (!BlockDefinitionsRegistry.instance) {
|
|
779
|
-
BlockDefinitionsRegistry.instance = new BlockDefinitionsRegistry();
|
|
780
|
-
}
|
|
781
|
-
return BlockDefinitionsRegistry.instance;
|
|
782
|
-
}
|
|
783
|
-
/** Register a block template by id. Can be called before the block is needed. */
|
|
784
|
-
register(id, definition) {
|
|
785
|
-
this.definitions.set(id, definition);
|
|
786
|
-
}
|
|
787
|
-
/** Get one definition by id. */
|
|
788
|
-
get(id) {
|
|
789
|
-
return this.definitions.get(id);
|
|
790
|
-
}
|
|
791
|
-
/** Get all registered definitions (id → definition). Used to merge with per-call definitions. */
|
|
792
|
-
getAll() {
|
|
793
|
-
const out = {};
|
|
794
|
-
this.definitions.forEach((def, id) => {
|
|
795
|
-
out[id] = def;
|
|
796
|
-
});
|
|
797
|
-
return out;
|
|
798
|
-
}
|
|
799
|
-
unregister(id) {
|
|
800
|
-
return this.definitions.delete(id);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
811
|
/**
|
|
805
812
|
* Default in-memory BlockRegistry.
|
|
806
813
|
*/
|
|
@@ -946,6 +953,34 @@ function resolveRefPath(refPath, ctx) {
|
|
|
946
953
|
}
|
|
947
954
|
return { target: current, path: rest.slice(-1) };
|
|
948
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Get a value from an object by dot-notation path (e.g. "name.firstName" -> obj.name?.firstName).
|
|
958
|
+
* Returns undefined if any segment is null/undefined or not an object.
|
|
959
|
+
*/
|
|
960
|
+
function getValueByPath(obj, path) {
|
|
961
|
+
if (obj == null)
|
|
962
|
+
return undefined;
|
|
963
|
+
const parts = path.trim().split('.').filter(Boolean);
|
|
964
|
+
let current = obj;
|
|
965
|
+
for (const part of parts) {
|
|
966
|
+
if (current == null || typeof current !== 'object')
|
|
967
|
+
return undefined;
|
|
968
|
+
current = current[part];
|
|
969
|
+
}
|
|
970
|
+
return current;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Return true if the value at ref path is an Angular signal (so reading it in an effect would be reactive).
|
|
974
|
+
*/
|
|
975
|
+
function refPathResolvesToSignal(refPath, ctx) {
|
|
976
|
+
const resolved = resolveRefPath(refPath, ctx);
|
|
977
|
+
if (resolved == null)
|
|
978
|
+
return false;
|
|
979
|
+
const val = resolved.path.length === 0
|
|
980
|
+
? resolved.target
|
|
981
|
+
: resolved.target?.[resolved.path[0]];
|
|
982
|
+
return val != null && isSignal(val);
|
|
983
|
+
}
|
|
949
984
|
/**
|
|
950
985
|
* Get value at ref path (read-only). Returns undefined if not found.
|
|
951
986
|
*/
|
|
@@ -958,11 +993,26 @@ function getRefValue(refPath, ctx) {
|
|
|
958
993
|
const key = resolved.path[0];
|
|
959
994
|
const val = obj?.[key];
|
|
960
995
|
// Unwrap Angular signals and other getter functions by calling them
|
|
996
|
+
if (val != null && isSignal(val)) {
|
|
997
|
+
return val();
|
|
998
|
+
}
|
|
961
999
|
if (typeof val === 'function') {
|
|
962
1000
|
return val();
|
|
963
1001
|
}
|
|
964
1002
|
return val;
|
|
965
1003
|
}
|
|
1004
|
+
function getRefSignal(refPath, ctx) {
|
|
1005
|
+
const resolved = resolveRefPath(refPath, ctx);
|
|
1006
|
+
if (resolved == null)
|
|
1007
|
+
return undefined;
|
|
1008
|
+
const val = resolved.path.length === 0
|
|
1009
|
+
? resolved.target
|
|
1010
|
+
: resolved.target?.[resolved.path[0]];
|
|
1011
|
+
if (val != null && isSignal(val)) {
|
|
1012
|
+
return val;
|
|
1013
|
+
}
|
|
1014
|
+
return undefined;
|
|
1015
|
+
}
|
|
966
1016
|
/**
|
|
967
1017
|
* Set value at ref path (write). No-op if target is not writable.
|
|
968
1018
|
*/
|
|
@@ -973,9 +1023,9 @@ function setRefValue(refPath, ctx, value) {
|
|
|
973
1023
|
const obj = resolved.target;
|
|
974
1024
|
const key = resolved.path[0];
|
|
975
1025
|
const val = obj?.[key];
|
|
976
|
-
// Angular signals are callable (typeof 'function') but have .set; treat any value with .set as writable so we don't overwrite the signal with a string
|
|
977
1026
|
const writable = val != null &&
|
|
978
|
-
|
|
1027
|
+
isSignal(val) &&
|
|
1028
|
+
typeof val.set === 'function'
|
|
979
1029
|
? val
|
|
980
1030
|
: null;
|
|
981
1031
|
if (writable) {
|
|
@@ -1074,14 +1124,10 @@ class BlockLoaderService {
|
|
|
1074
1124
|
injector = inject(Injector);
|
|
1075
1125
|
componentRegistry = ComponentRegistry.getInstance();
|
|
1076
1126
|
serviceRegistry = ServiceRegistry.getInstance();
|
|
1077
|
-
async load(description, viewContainerRef, options) {
|
|
1127
|
+
async load(description, viewContainerRef, model, options) {
|
|
1078
1128
|
let resolved = description;
|
|
1079
1129
|
if (isBlockReference(description)) {
|
|
1080
|
-
|
|
1081
|
-
...BlockDefinitionsRegistry.getInstance().getAll(),
|
|
1082
|
-
...(options?.blockDefinitions ?? {}),
|
|
1083
|
-
};
|
|
1084
|
-
resolved = resolveBlockReference(description, definitions);
|
|
1130
|
+
resolved = resolveBlockReference(description, options?.blockDefinitions);
|
|
1085
1131
|
}
|
|
1086
1132
|
const parsed = safeParseBlockDescription(resolved);
|
|
1087
1133
|
if (!parsed.success) {
|
|
@@ -1106,26 +1152,25 @@ class BlockLoaderService {
|
|
|
1106
1152
|
const blockInstance = {};
|
|
1107
1153
|
for (let i = 0; i < selfServices.length; i++) {
|
|
1108
1154
|
const id = selfServices[i].id;
|
|
1155
|
+
const alias = selfServices[i].alias ?? id;
|
|
1109
1156
|
const serviceType = serviceTypes[i];
|
|
1110
1157
|
if (serviceType) {
|
|
1111
1158
|
const svc = componentRef.injector.get(serviceType);
|
|
1112
1159
|
if (svc != null)
|
|
1113
|
-
blockInstance[
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
if (desc.inputs?.['model'] != null) {
|
|
1117
|
-
const model = desc.inputs['model'];
|
|
1118
|
-
for (const svc of Object.values(blockInstance)) {
|
|
1119
|
-
if (svc != null && typeof svc['setModel'] === 'function') {
|
|
1120
|
-
svc['setModel'](model);
|
|
1121
|
-
}
|
|
1160
|
+
blockInstance[alias] = svc;
|
|
1122
1161
|
}
|
|
1123
1162
|
}
|
|
1124
|
-
const ctx = {
|
|
1163
|
+
const ctx = {
|
|
1164
|
+
registry,
|
|
1165
|
+
currentBlockId: desc.id ?? undefined,
|
|
1166
|
+
currentInstance: blockInstance,
|
|
1167
|
+
};
|
|
1125
1168
|
const handle = { instance: blockInstance };
|
|
1126
1169
|
if (desc.id != null && desc.id !== '')
|
|
1127
1170
|
registry.register(desc.id, handle);
|
|
1128
|
-
|
|
1171
|
+
this.applyInitialModel(desc, ctx, blockInstance, model);
|
|
1172
|
+
let currentEffectRefs = this.setInputs(componentRef, desc, ctx, model);
|
|
1173
|
+
currentEffectRefs = currentEffectRefs.concat(this.applyModelReactivity(desc, ctx, blockInstance, model));
|
|
1129
1174
|
const subscriptions = this.wireOutputs(componentRef, desc, registry, options?.outputHandlers);
|
|
1130
1175
|
const doDestroy = () => {
|
|
1131
1176
|
if (desc.id != null && desc.id !== '')
|
|
@@ -1142,7 +1187,9 @@ class BlockLoaderService {
|
|
|
1142
1187
|
const p = safeParseBlockDescription(newDesc);
|
|
1143
1188
|
if (p.success) {
|
|
1144
1189
|
currentEffectRefs.forEach((ref) => ref.destroy());
|
|
1145
|
-
|
|
1190
|
+
this.applyInitialModel(p.data, ctx, blockInstance, model);
|
|
1191
|
+
currentEffectRefs = this.setInputs(componentRef, p.data, ctx, model);
|
|
1192
|
+
currentEffectRefs = currentEffectRefs.concat(this.applyModelReactivity(p.data, ctx, blockInstance, model));
|
|
1146
1193
|
}
|
|
1147
1194
|
};
|
|
1148
1195
|
return { componentRef, destroy: doDestroy, updateInputs };
|
|
@@ -1154,17 +1201,22 @@ class BlockLoaderService {
|
|
|
1154
1201
|
return Promise.all(selfServices.map((e) => this.serviceRegistry.getType(e.id)));
|
|
1155
1202
|
}
|
|
1156
1203
|
buildChildInjectorFromTypes(serviceTypes) {
|
|
1157
|
-
const providers = serviceTypes
|
|
1158
|
-
|
|
1204
|
+
const providers = serviceTypes
|
|
1205
|
+
.filter((t) => t != null)
|
|
1206
|
+
.map((t) => ({ provide: t, useClass: t }));
|
|
1207
|
+
return providers.length === 0
|
|
1208
|
+
? this.injector
|
|
1209
|
+
: Injector.create({ providers, parent: this.injector });
|
|
1159
1210
|
}
|
|
1160
1211
|
/** Set inputs and wire template/two-way effects. Single pass over inputs for large configs. */
|
|
1161
|
-
setInputs(componentRef, desc, ctx) {
|
|
1212
|
+
setInputs(componentRef, desc, ctx, blockModel) {
|
|
1162
1213
|
const effectRefs = [];
|
|
1163
1214
|
const inputs = desc.inputs ?? {};
|
|
1164
1215
|
const inst = componentRef.instance;
|
|
1165
1216
|
for (const [key, value] of Object.entries(inputs)) {
|
|
1166
|
-
if (key === 'model')
|
|
1217
|
+
if (key === 'model') {
|
|
1167
1218
|
continue;
|
|
1219
|
+
}
|
|
1168
1220
|
if (typeof value === 'string') {
|
|
1169
1221
|
const twoWayKind = classifyTwoWayString(value);
|
|
1170
1222
|
if (twoWayKind === 'invalid-mix') {
|
|
@@ -1174,13 +1226,8 @@ class BlockLoaderService {
|
|
|
1174
1226
|
if (twoWayKind === 'two-way') {
|
|
1175
1227
|
const refPath = parseTwoWayRef(value);
|
|
1176
1228
|
if (refPath) {
|
|
1177
|
-
const initial = getRefValue(refPath, ctx);
|
|
1178
|
-
if (componentRef.setInput)
|
|
1179
|
-
componentRef.setInput(key, initial);
|
|
1180
|
-
else
|
|
1181
|
-
inst[key] = initial;
|
|
1182
1229
|
const modelSig = inst[key];
|
|
1183
|
-
if (modelSig != null &&
|
|
1230
|
+
if (modelSig != null && isSignal(modelSig)) {
|
|
1184
1231
|
effectRefs.push(effect(() => {
|
|
1185
1232
|
const fromRef = getRefValue(refPath, ctx);
|
|
1186
1233
|
if (fromRef === undefined)
|
|
@@ -1200,14 +1247,15 @@ class BlockLoaderService {
|
|
|
1200
1247
|
}
|
|
1201
1248
|
}
|
|
1202
1249
|
const str = value;
|
|
1203
|
-
if (typeof value === 'string' &&
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
componentRef.setInput(key, initial);
|
|
1207
|
-
else
|
|
1208
|
-
inst[key] = initial;
|
|
1250
|
+
if (typeof value === 'string' &&
|
|
1251
|
+
str.indexOf('{{') !== -1 &&
|
|
1252
|
+
str.indexOf('}}', str.indexOf('{{')) !== -1) {
|
|
1209
1253
|
effectRefs.push(effect(() => {
|
|
1210
|
-
const
|
|
1254
|
+
const modelSig = ctx.currentInstance?.['model'];
|
|
1255
|
+
const model = modelSig != null && isSignal(modelSig)
|
|
1256
|
+
? modelSig()
|
|
1257
|
+
: modelSig;
|
|
1258
|
+
const resolved = this.interpolateTemplateMixed(str, ctx, model);
|
|
1211
1259
|
if (componentRef.setInput)
|
|
1212
1260
|
componentRef.setInput(key, resolved);
|
|
1213
1261
|
else
|
|
@@ -1223,6 +1271,88 @@ class BlockLoaderService {
|
|
|
1223
1271
|
}
|
|
1224
1272
|
return effectRefs;
|
|
1225
1273
|
}
|
|
1274
|
+
/** Resolve model from desc (interpolate ref if string) and set on services and blockInstance. */
|
|
1275
|
+
applyInitialModel(desc, ctx, blockInstance, blockModel) {
|
|
1276
|
+
const rawModel = desc.inputs?.['model'];
|
|
1277
|
+
if (rawModel === undefined || rawModel === null) {
|
|
1278
|
+
const model = blockModel();
|
|
1279
|
+
for (const svc of Object.values(blockInstance)) {
|
|
1280
|
+
if (svc != null && typeof svc['setModel'] === 'function') {
|
|
1281
|
+
svc['setModel'](model);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
blockInstance['model'] = blockModel;
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
let model = rawModel;
|
|
1288
|
+
if (typeof rawModel === 'string') {
|
|
1289
|
+
const refPath = rawModel.trim().replace(/^\{\{\s*|\s*\}\}$/g, '');
|
|
1290
|
+
if (refPath.length > 0) {
|
|
1291
|
+
const refSignal = getRefSignal(refPath, ctx);
|
|
1292
|
+
if (refSignal != null) {
|
|
1293
|
+
model = refSignal();
|
|
1294
|
+
blockInstance['model'] = refSignal;
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
model = getRefValue(refPath, ctx);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
for (const svc of Object.values(blockInstance)) {
|
|
1302
|
+
if (svc != null && typeof svc['setModel'] === 'function') {
|
|
1303
|
+
svc['setModel'](model);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
/** If model is a ref string, return an effect that keeps setModel/blockInstance in sync with the ref. */
|
|
1308
|
+
applyModelReactivity(desc, ctx, blockInstance, blockModel) {
|
|
1309
|
+
const rawModel = desc.inputs?.['model'];
|
|
1310
|
+
const refPath = rawModel != null && typeof rawModel === 'string'
|
|
1311
|
+
? rawModel.trim().replace(/^\{\{\s*|\s*\}\}$/g, '')
|
|
1312
|
+
: '';
|
|
1313
|
+
if (refPath.length === 0) {
|
|
1314
|
+
return [];
|
|
1315
|
+
}
|
|
1316
|
+
const effectRef = effect(() => {
|
|
1317
|
+
const model = refPath ? getRefValue(refPath, ctx) : blockModel();
|
|
1318
|
+
for (const svc of Object.values(blockInstance)) {
|
|
1319
|
+
if (svc != null && typeof svc['setModel'] === 'function') {
|
|
1320
|
+
svc['setModel'](model);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}, { ...(ngDevMode ? { debugName: "effectRef" } : {}), injector: this.injector });
|
|
1324
|
+
return [effectRef];
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Replace each {{ refPath }} with a value: resolve as ref first (e.g. Person.instance.AuthState.firstName),
|
|
1328
|
+
* else as model path (e.g. firstName, instance.model.firstName). Allows mixing both in one template.
|
|
1329
|
+
*/
|
|
1330
|
+
interpolateTemplateMixed(template, ctx, model) {
|
|
1331
|
+
const parts = [];
|
|
1332
|
+
let s = template;
|
|
1333
|
+
for (let i = 0; i < BlockLoaderService.INTERPOLATE_MAX_PLACEHOLDERS; i++) {
|
|
1334
|
+
const start = s.indexOf('{{');
|
|
1335
|
+
if (start === -1) {
|
|
1336
|
+
parts.push(s);
|
|
1337
|
+
break;
|
|
1338
|
+
}
|
|
1339
|
+
parts.push(s.slice(0, start));
|
|
1340
|
+
const end = s.indexOf('}}', start);
|
|
1341
|
+
if (end === -1) {
|
|
1342
|
+
parts.push(s.slice(start));
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
const ref = s.slice(start + 2, end).trim();
|
|
1346
|
+
const val = ref
|
|
1347
|
+
? resolveRefPath(ref, ctx) != null
|
|
1348
|
+
? getRefValue(ref, ctx)
|
|
1349
|
+
: getValueByPath(model, ref)
|
|
1350
|
+
: null;
|
|
1351
|
+
parts.push(val != null ? String(val) : '');
|
|
1352
|
+
s = s.slice(end + 2);
|
|
1353
|
+
}
|
|
1354
|
+
return parts.join('');
|
|
1355
|
+
}
|
|
1226
1356
|
/** Replace all {{ refPath }} with resolved values. Uses parts array + join to avoid N string concats. */
|
|
1227
1357
|
static INTERPOLATE_MAX_PLACEHOLDERS = 200;
|
|
1228
1358
|
interpolateTemplate(template, ctx) {
|
|
@@ -1287,9 +1417,7 @@ class BlockLoaderService {
|
|
|
1287
1417
|
const resolvedPairs = [];
|
|
1288
1418
|
let changed = false;
|
|
1289
1419
|
for (const [k, v] of entries) {
|
|
1290
|
-
const childOptions = isNestedBlockDescriptor && k === 'inputs'
|
|
1291
|
-
? { preserveTwoWayRefs: true }
|
|
1292
|
-
: options;
|
|
1420
|
+
const childOptions = isNestedBlockDescriptor && k === 'inputs' ? { preserveTwoWayRefs: true } : options;
|
|
1293
1421
|
const resolved = this.resolveInputValue(v, ctx, childOptions);
|
|
1294
1422
|
if (resolved !== v)
|
|
1295
1423
|
changed = true;
|
|
@@ -1311,7 +1439,8 @@ class BlockLoaderService {
|
|
|
1311
1439
|
for (const [outputKey, outputValue] of Object.entries(outputs)) {
|
|
1312
1440
|
const handler = createOutputHandler(outputValue, outputKey, registry, outputHandlers);
|
|
1313
1441
|
const emitter = inst[outputKey];
|
|
1314
|
-
if (emitter != null &&
|
|
1442
|
+
if (emitter != null &&
|
|
1443
|
+
typeof emitter.subscribe === 'function') {
|
|
1315
1444
|
const sub = emitter.subscribe((v) => handler(v));
|
|
1316
1445
|
subs.push(sub);
|
|
1317
1446
|
}
|
|
@@ -1331,7 +1460,11 @@ function getServicesKey(services) {
|
|
|
1331
1460
|
const arr = normalizeServices(services);
|
|
1332
1461
|
if (arr.length === 0)
|
|
1333
1462
|
return '';
|
|
1334
|
-
return arr
|
|
1463
|
+
return arr
|
|
1464
|
+
.map((s) => typeof s === 'string'
|
|
1465
|
+
? s
|
|
1466
|
+
: `${s.id}:${s.scope ?? ''}`)
|
|
1467
|
+
.join(',');
|
|
1335
1468
|
}
|
|
1336
1469
|
class BlockDirective {
|
|
1337
1470
|
viewContainerRef = inject(ViewContainerRef);
|
|
@@ -1345,6 +1478,8 @@ class BlockDirective {
|
|
|
1345
1478
|
blockRegistry = input(null, ...(ngDevMode ? [{ debugName: "blockRegistry" }] : []));
|
|
1346
1479
|
/** Map id → full description; used when description is a block reference (id/blockId). */
|
|
1347
1480
|
blockDefinitions = input(null, ...(ngDevMode ? [{ debugName: "blockDefinitions" }] : []));
|
|
1481
|
+
/** Model for the block. */
|
|
1482
|
+
model = input(undefined, ...(ngDevMode ? [{ debugName: "model" }] : []));
|
|
1348
1483
|
loadResult = null;
|
|
1349
1484
|
loadedComponent = null;
|
|
1350
1485
|
loadedServicesKey = null;
|
|
@@ -1357,8 +1492,6 @@ class BlockDirective {
|
|
|
1357
1492
|
const desc = this.description();
|
|
1358
1493
|
const outputHandlers = this.outputHandlers();
|
|
1359
1494
|
const inputDefs = this.blockDefinitions();
|
|
1360
|
-
const global = BlockDefinitionsRegistry.getInstance().getAll();
|
|
1361
|
-
const definitions = { ...global, ...(inputDefs ?? {}) };
|
|
1362
1495
|
if (desc == null) {
|
|
1363
1496
|
this.lastDescRef = null;
|
|
1364
1497
|
this.lastParsedData = null;
|
|
@@ -1366,7 +1499,7 @@ class BlockDirective {
|
|
|
1366
1499
|
return;
|
|
1367
1500
|
}
|
|
1368
1501
|
const resolved = isBlockReference(desc)
|
|
1369
|
-
? resolveBlockReference(desc,
|
|
1502
|
+
? resolveBlockReference(desc, inputDefs ?? undefined)
|
|
1370
1503
|
: desc;
|
|
1371
1504
|
let data;
|
|
1372
1505
|
if (resolved === this.lastDescRef && this.lastParsedData != null) {
|
|
@@ -1393,10 +1526,10 @@ class BlockDirective {
|
|
|
1393
1526
|
const registry = this.blockRegistry() ?? undefined;
|
|
1394
1527
|
const generation = ++this.loadGeneration;
|
|
1395
1528
|
this.loader
|
|
1396
|
-
.load(resolved, this.viewContainerRef, {
|
|
1529
|
+
.load(resolved, this.viewContainerRef, this.model, {
|
|
1397
1530
|
outputHandlers: Object.keys(outputHandlers).length > 0 ? outputHandlers : undefined,
|
|
1398
1531
|
registry,
|
|
1399
|
-
blockDefinitions:
|
|
1532
|
+
blockDefinitions: inputDefs ?? undefined,
|
|
1400
1533
|
})
|
|
1401
1534
|
.then((result) => {
|
|
1402
1535
|
if (generation !== this.loadGeneration)
|
|
@@ -1425,7 +1558,7 @@ class BlockDirective {
|
|
|
1425
1558
|
}
|
|
1426
1559
|
}
|
|
1427
1560
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1428
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: BlockDirective, isStandalone: true, selector: "[block]", inputs: { description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, outputHandlers: { classPropertyName: "outputHandlers", publicName: "outputHandlers", isSignal: true, isRequired: false, transformFunction: null }, blockRegistry: { classPropertyName: "blockRegistry", publicName: "blockRegistry", isSignal: true, isRequired: false, transformFunction: null }, blockDefinitions: { classPropertyName: "blockDefinitions", publicName: "blockDefinitions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1561
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: BlockDirective, isStandalone: true, selector: "[block]", inputs: { description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, outputHandlers: { classPropertyName: "outputHandlers", publicName: "outputHandlers", isSignal: true, isRequired: false, transformFunction: null }, blockRegistry: { classPropertyName: "blockRegistry", publicName: "blockRegistry", isSignal: true, isRequired: false, transformFunction: null }, blockDefinitions: { classPropertyName: "blockDefinitions", publicName: "blockDefinitions", isSignal: true, isRequired: false, transformFunction: null }, model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
1429
1562
|
}
|
|
1430
1563
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockDirective, decorators: [{
|
|
1431
1564
|
type: Directive,
|
|
@@ -1433,7 +1566,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1433
1566
|
selector: '[block]',
|
|
1434
1567
|
standalone: true,
|
|
1435
1568
|
}]
|
|
1436
|
-
}], ctorParameters: () => [], propDecorators: { description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], outputHandlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputHandlers", required: false }] }], blockRegistry: [{ type: i0.Input, args: [{ isSignal: true, alias: "blockRegistry", required: false }] }], blockDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "blockDefinitions", required: false }] }] } });
|
|
1569
|
+
}], ctorParameters: () => [], propDecorators: { description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], outputHandlers: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputHandlers", required: false }] }], blockRegistry: [{ type: i0.Input, args: [{ isSignal: true, alias: "blockRegistry", required: false }] }], blockDefinitions: [{ type: i0.Input, args: [{ isSignal: true, alias: "blockDefinitions", required: false }] }], model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }] } });
|
|
1437
1570
|
|
|
1438
1571
|
/*
|
|
1439
1572
|
* Public API Surface of blocks-studio
|
|
@@ -1443,5 +1576,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
1443
1576
|
* Generated bundle index. Do not edit.
|
|
1444
1577
|
*/
|
|
1445
1578
|
|
|
1446
|
-
export { BlockDefinitionsRegistry, BlockDescriptionSchema, BlockDirective, BlockLoaderService, BlockRegistryImpl, ComponentRegistry, GuardRegistry, RegistryMetadataStore, RouteLoader, ServiceRegistry, buildComputedForTemplate, classifyTwoWayString, createOutputHandler, deepMergeBlockDefinition, extractReadonlyRefs, getRefPathFromReadonly, getRefValue,
|
|
1579
|
+
export { BlockDefinitionsRegistry, BlockDescriptionSchema, BlockDirective, BlockLoaderService, BlockRegistryImpl, ComponentRegistry, GuardRegistry, RegistryMetadataStore, RouteLoader, ServiceRegistry, buildComputedForTemplate, classifyTwoWayString, createOutputHandler, deepMergeBlockDefinition, extractReadonlyRefs, getRefPathFromReadonly, getRefSignal, getRefValue, getValueByPath, isBlockReference, isInvalidTwoWayMix, isOutputReference, isTwoWayRefString, normalizeServices, parseRefPath, parseTwoWayRef, refPathResolvesToSignal, resolveBlockReference, resolveOutputReference, resolveRefPath, safeParseBlockDescription, setRefValue };
|
|
1447
1580
|
//# sourceMappingURL=ngx-blocks-studio.mjs.map
|