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.array(z.object({
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
- })).optional(),
667
- onSuccess: z.object({
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
- }).optional(),
672
- onError: z.object({
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
- }).optional(),
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.union([
690
- ServiceEntrySchema,
691
- z.array(ServiceEntrySchema),
692
- ]).optional().default([]),
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 blockDefinitions.
751
- * If blockDefinition is present, it is deep-merged onto the base; otherwise returns the base.
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 id = ref.blockId ?? ref.id;
755
- if (!id)
756
- throw new Error('Block reference must have id or blockId.');
757
- const base = blockDefinitions[id];
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 "${id}" has no definition in blockDefinitions.`);
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
- typeof val['set'] === 'function'
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
- const definitions = {
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[id] = svc;
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 = { registry, currentBlockId: desc.id ?? undefined, currentInstance: blockInstance };
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
- let currentEffectRefs = this.setInputs(componentRef, desc, ctx);
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
- currentEffectRefs = this.setInputs(componentRef, p.data, ctx);
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.filter((t) => t != null).map((t) => ({ provide: t, useClass: t }));
1158
- return providers.length === 0 ? this.injector : Injector.create({ providers, parent: this.injector });
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 && typeof modelSig === 'function') {
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' && str.indexOf('{{') !== -1 && str.indexOf('}}', str.indexOf('{{')) !== -1) {
1204
- const initial = this.interpolateTemplate(str, ctx);
1205
- if (componentRef.setInput)
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 resolved = this.interpolateTemplate(str, ctx);
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 && typeof emitter.subscribe === 'function') {
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.map((s) => (typeof s === 'string' ? s : `${s.id}:${s.scope ?? ''}`)).join(',');
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, definitions)
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: definitions ?? undefined,
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, isBlockReference, isIdReference, isInvalidTwoWayMix, isOutputReference, isTwoWayRefString, normalizeServices, parseBlockDescription, parseRefPath, parseTwoWayRef, resolveBlockReference, resolveOutputReference, resolveRefPath, safeParseBlockDescription, setRefValue };
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