@masterteam/form-builder 0.0.50 → 0.0.52

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.
@@ -84,6 +84,8 @@
84
84
  "toolbar-fields": "الحقول",
85
85
  "toolbar-operators": "العوامل",
86
86
  "toolbar-no-fields": "لا توجد حقول متاحة",
87
+ "toolbar-values": "القيم",
88
+ "toolbar-no-values": "لا توجد قيم متاحة",
87
89
  "build-condition-formula": "أنشئ صيغة الشرط..."
88
90
  }
89
91
  }
@@ -84,6 +84,8 @@
84
84
  "toolbar-fields": "Fields",
85
85
  "toolbar-operators": "Operators",
86
86
  "toolbar-no-fields": "No fields available",
87
+ "toolbar-values": "Values",
88
+ "toolbar-no-values": "No values available",
87
89
  "build-condition-formula": "Build condition formula..."
88
90
  }
89
91
  }
@@ -29,10 +29,10 @@ import { DynamicForm } from '@masterteam/forms/dynamic-form';
29
29
  import { ToggleField } from '@masterteam/components/toggle-field';
30
30
  import { ModalRef } from '@masterteam/components/dialog';
31
31
  import { CONDITION_FUNCTION_CATEGORIES, CONDITION_OPERATORS, FormulaToolbar, FormulaEditor, serializeTokens, tokenizeFormulaTemplate, serializeTokensForDisplay } from '@masterteam/components/formula';
32
+ import { of, map, catchError, tap, startWith } from 'rxjs';
32
33
  import { Tooltip } from '@masterteam/components/tooltip';
33
34
  import { ClientForm } from '@masterteam/forms/client-form';
34
35
  import { Table } from '@masterteam/components/table';
35
- import { startWith, of, map, catchError } from 'rxjs';
36
36
  import { RadioCardsField } from '@masterteam/components/radio-cards-field';
37
37
  import { FormulaBuilder } from '@masterteam/formula-builder';
38
38
 
@@ -1227,6 +1227,146 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1227
1227
  args: [{ providedIn: 'root' }]
1228
1228
  }] });
1229
1229
 
1230
+ /** Lower-case a view type and strip the separators the backend may send. */
1231
+ function normalizeViewType(viewType) {
1232
+ return (viewType ?? '').toLowerCase().replace(/[ _-]/g, '');
1233
+ }
1234
+ /**
1235
+ * View types whose values the backend can serve. Matched after lower-casing
1236
+ * and stripping spaces/underscores/dashes, and kept in step with the backend
1237
+ * gate (`PropertyItemsLookupSourceResolver.IsLookupBacked` plus `Status` and
1238
+ * `PhaseGate`) — every other view type is rejected with a validation error.
1239
+ */
1240
+ const VALUE_BACKED_VIEW_TYPES = new Set([
1241
+ 'lookup',
1242
+ 'lookupmultiselect',
1243
+ 'lookupmatrix',
1244
+ 'status',
1245
+ 'phasegate',
1246
+ ]);
1247
+ /**
1248
+ * View types whose item keys are ids to compare numerically. Only phase values
1249
+ * qualify: lookup items legitimately carry numeric-looking string keys (the
1250
+ * backend falls back to the item id when a lookup item has no key) and compare
1251
+ * as strings, so this must stay keyed on the view type, never on whether a key
1252
+ * happens to parse as a number.
1253
+ */
1254
+ const NUMERIC_VALUE_VIEW_TYPES = new Set(['phasegate']);
1255
+ /** Whether a property's values can be picked from a source instead of typed. */
1256
+ function isValueBackedViewType(viewType) {
1257
+ if (!viewType)
1258
+ return false;
1259
+ return VALUE_BACKED_VIEW_TYPES.has(normalizeViewType(viewType));
1260
+ }
1261
+ class FormBuilderContextService {
1262
+ http = inject(HttpClient);
1263
+ baseUrl = 'context';
1264
+ propertyItemsCache = new Map();
1265
+ /**
1266
+ * Resolves context data including navigation paths and properties.
1267
+ * No caching - always fetches fresh data.
1268
+ */
1269
+ resolveContext(contextKey, options = {}) {
1270
+ if (!contextKey)
1271
+ return of(null);
1272
+ return this.http
1273
+ .post(`${this.baseUrl}/resolve`, {
1274
+ contextKey,
1275
+ displayTags: options.displayTags,
1276
+ propertyFlags: options.propertyFlags,
1277
+ matchAllTags: options.matchAllTags,
1278
+ includeVirtual: options.includeVirtual,
1279
+ includeNavigationPaths: options.includeNavigationPaths,
1280
+ includeConnectionMetadata: options.includeConnectionMetadata,
1281
+ includeRelatedContextValues: options.includeRelatedContextValues,
1282
+ relatedContextsToInclude: options.relatedContextsToInclude,
1283
+ // Consumer-supplied fields win on collision with any field above.
1284
+ ...options.extraParams,
1285
+ })
1286
+ .pipe(map((response) => this.normalizeResponse(response)), catchError((error) => {
1287
+ console.warn('Failed to resolve context:', error);
1288
+ return of(null);
1289
+ }));
1290
+ }
1291
+ /**
1292
+ * Gets properties for a context.
1293
+ * No caching - always fetches fresh data.
1294
+ */
1295
+ getProperties(contextKey, options = {}) {
1296
+ if (!contextKey)
1297
+ return of([]);
1298
+ return this.resolveContext(contextKey, options).pipe(map((resolved) => resolved?.properties ?? []));
1299
+ }
1300
+ /**
1301
+ * Selectable values of a lookup / lookup-matrix / status / phase property,
1302
+ * used by the condition builder so a compared value is picked rather than
1303
+ * typed.
1304
+ *
1305
+ * The backend answers 400 for any other view type, which we swallow into an
1306
+ * empty list — callers already gate on `isValueBackedViewType`, so empty
1307
+ * simply means "nothing to pick".
1308
+ *
1309
+ * Deliberately sent **without** the backend's optional `contextKey`. A form's
1310
+ * fields can come from several scopes at once (the builder's Current / Host /
1311
+ * Parent tabs), so a module form legitimately carries level-scoped fields —
1312
+ * and scoping the request to the form's own context 404s every one of them,
1313
+ * because the property is not in that catalog. Resolution by full property
1314
+ * key is unambiguous for persisted properties, which is what fields carry.
1315
+ */
1316
+ getPropertyItems(propertyKey) {
1317
+ if (!propertyKey)
1318
+ return of([]);
1319
+ const cached = this.propertyItemsCache.get(propertyKey);
1320
+ if (cached)
1321
+ return of(cached);
1322
+ return this.http
1323
+ .get(`formulas/builder/property-items/${encodeURIComponent(propertyKey)}`)
1324
+ .pipe(map((response) => {
1325
+ const payload = response && 'data' in response ? response.data : response;
1326
+ // The response's own view type decides how a picked value has to
1327
+ // serialize — the item keys look alike, so this is the only signal.
1328
+ const valueType = NUMERIC_VALUE_VIEW_TYPES.has(normalizeViewType(payload?.viewType))
1329
+ ? 'number'
1330
+ : 'string';
1331
+ return (payload?.items ?? []).map((item) => ({ ...item, valueType }));
1332
+ }), tap((items) => this.propertyItemsCache.set(propertyKey, items)), catchError((error) => {
1333
+ console.warn('Failed to load property items:', error);
1334
+ return of([]);
1335
+ }));
1336
+ }
1337
+ normalizeResponse(response) {
1338
+ const payload = response?.data ?? response;
1339
+ if (!payload)
1340
+ return null;
1341
+ const properties = this.normalizeProperties(payload.properties ?? []);
1342
+ return {
1343
+ ...payload,
1344
+ properties,
1345
+ };
1346
+ }
1347
+ normalizeProperties(raw) {
1348
+ return (raw ?? [])
1349
+ .map((prop) => {
1350
+ const key = prop?.key ?? prop?.propertyKey;
1351
+ if (!key)
1352
+ return null;
1353
+ const name = prop?.name ?? prop?.displayName ?? key;
1354
+ return {
1355
+ ...prop,
1356
+ key,
1357
+ name,
1358
+ };
1359
+ })
1360
+ .filter((prop) => Boolean(prop));
1361
+ }
1362
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormBuilderContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1363
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormBuilderContextService, providedIn: 'root' });
1364
+ }
1365
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormBuilderContextService, decorators: [{
1366
+ type: Injectable,
1367
+ args: [{ providedIn: 'root' }]
1368
+ }] });
1369
+
1230
1370
  /**
1231
1371
  * Field Conditions Dialog
1232
1372
  *
@@ -1235,6 +1375,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1235
1375
  */
1236
1376
  class FBFieldConditions {
1237
1377
  modalService = inject(ModalService);
1378
+ contextService = inject(FormBuilderContextService);
1238
1379
  ref = inject(ModalRef);
1239
1380
  transloco = inject(TranslocoService);
1240
1381
  destroyRef = inject(DestroyRef);
@@ -1253,11 +1394,16 @@ class FBFieldConditions {
1253
1394
  // Condition-specific functions and operators
1254
1395
  functionCategories = CONDITION_FUNCTION_CATEGORIES;
1255
1396
  operators = CONDITION_OPERATORS;
1397
+ /** Values already loaded, keyed by property key. */
1398
+ propertyValues = signal({}, ...(ngDevMode ? [{ debugName: "propertyValues" }] : /* istanbul ignore next */ []));
1399
+ /** Property key whose values are being fetched right now. */
1400
+ loadingValuesFor = signal(null, ...(ngDevMode ? [{ debugName: "loadingValuesFor" }] : /* istanbul ignore next */ []));
1256
1401
  /** Property display items for toolbar; keys remain the persisted formula value. */
1257
1402
  propertyItems = computed(() => this.availableFields().map((field) => ({
1258
1403
  key: field.key,
1259
1404
  name: field.name,
1260
1405
  description: field.type ? `${field.name} (${field.type})` : field.name,
1406
+ hasValues: isValueBackedViewType(field.type),
1261
1407
  })), ...(ngDevMode ? [{ debugName: "propertyItems" }] : /* istanbul ignore next */ []));
1262
1408
  /** Toolbar labels */
1263
1409
  toolbarLabels = computed(() => {
@@ -1267,6 +1413,8 @@ class FBFieldConditions {
1267
1413
  properties: this.transloco.translate('formBuilder.toolbar-fields'),
1268
1414
  operators: this.transloco.translate('formBuilder.toolbar-operators'),
1269
1415
  noPropertiesAvailable: this.transloco.translate('formBuilder.toolbar-no-fields'),
1416
+ values: this.transloco.translate('formBuilder.toolbar-values'),
1417
+ noValuesAvailable: this.transloco.translate('formBuilder.toolbar-no-values'),
1270
1418
  };
1271
1419
  }, ...(ngDevMode ? [{ debugName: "toolbarLabels" }] : /* istanbul ignore next */ []));
1272
1420
  constructor() {
@@ -1289,6 +1437,28 @@ class FBFieldConditions {
1289
1437
  }
1290
1438
  }
1291
1439
  }
1440
+ /**
1441
+ * Load the selectable values of a lookup / status field the first time its
1442
+ * value list is opened, so the compared value is picked from the real source
1443
+ * instead of typed as free text.
1444
+ */
1445
+ onPropertyValuesRequest(propertyKey) {
1446
+ if (this.propertyValues()[propertyKey] !== undefined)
1447
+ return;
1448
+ this.loadingValuesFor.set(propertyKey);
1449
+ this.contextService
1450
+ .getPropertyItems(propertyKey)
1451
+ .pipe(takeUntilDestroyed(this.destroyRef))
1452
+ .subscribe((items) => {
1453
+ this.propertyValues.update((current) => ({
1454
+ ...current,
1455
+ [propertyKey]: items,
1456
+ }));
1457
+ if (this.loadingValuesFor() === propertyKey) {
1458
+ this.loadingValuesFor.set(null);
1459
+ }
1460
+ });
1461
+ }
1292
1462
  /** Handle block insert from toolbar */
1293
1463
  onBlockInsert(block) {
1294
1464
  const editor = this.editorRef();
@@ -1326,7 +1496,7 @@ class FBFieldConditions {
1326
1496
  return raw.trim();
1327
1497
  }
1328
1498
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FBFieldConditions, deps: [], target: i0.ɵɵFactoryTarget.Component });
1329
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.8", type: FBFieldConditions, isStandalone: true, selector: "mt-fb-field-conditions", inputs: { initialFormula: { classPropertyName: "initialFormula", publicName: "initialFormula", isSignal: true, isRequired: false, transformFunction: null }, availableFields: { classPropertyName: "availableFields", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div\r\n [class]=\"[\r\n modalService.contentClass,\r\n 'min-w-0',\r\n 'p-4',\r\n 'overflow-y-hidden!',\r\n 'max-[640px]:p-3',\r\n ]\"\r\n >\r\n <div class=\"mt-4 flex h-full min-h-0 flex-col gap-3 overflow-y-auto pb-10\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyItems()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels()\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n [class]=\"[\r\n modalService.footerClass,\r\n '!h-auto',\r\n 'min-h-0',\r\n 'flex-col',\r\n 'gap-2',\r\n 'sm:flex-row',\r\n 'sm:items-center',\r\n 'sm:justify-end',\r\n ]\"\r\n >\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n [disabled]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "propertiesTemplate", "functionCategories", "operators", "initialTab", "visibleTabs", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled", "borderless"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }] });
1499
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.8", type: FBFieldConditions, isStandalone: true, selector: "mt-fb-field-conditions", inputs: { initialFormula: { classPropertyName: "initialFormula", publicName: "initialFormula", isSignal: true, isRequired: false, transformFunction: null }, availableFields: { classPropertyName: "availableFields", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div\r\n [class]=\"[\r\n modalService.contentClass,\r\n 'min-w-0',\r\n 'p-4',\r\n 'overflow-y-hidden!',\r\n 'max-[640px]:p-3',\r\n ]\"\r\n >\r\n <div class=\"mt-4 flex h-full min-h-0 flex-col gap-3 overflow-y-auto pb-10\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyItems()\"\r\n [propertyValues]=\"propertyValues()\"\r\n [loadingValuesFor]=\"loadingValuesFor()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels()\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n (onPropertyValuesRequest)=\"onPropertyValuesRequest($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n [class]=\"[\r\n modalService.footerClass,\r\n '!h-auto',\r\n 'min-h-0',\r\n 'flex-col',\r\n 'gap-2',\r\n 'sm:flex-row',\r\n 'sm:items-center',\r\n 'sm:justify-end',\r\n ]\"\r\n >\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n [disabled]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "propertiesTemplate", "propertyValues", "loadingValuesFor", "functionCategories", "operators", "initialTab", "visibleTabs", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange", "onPropertyValuesRequest"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled", "borderless"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }] });
1330
1500
  }
1331
1501
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FBFieldConditions, decorators: [{
1332
1502
  type: Component,
@@ -1336,7 +1506,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1336
1506
  Button,
1337
1507
  FormulaToolbar,
1338
1508
  FormulaEditor,
1339
- ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div\r\n [class]=\"[\r\n modalService.contentClass,\r\n 'min-w-0',\r\n 'p-4',\r\n 'overflow-y-hidden!',\r\n 'max-[640px]:p-3',\r\n ]\"\r\n >\r\n <div class=\"mt-4 flex h-full min-h-0 flex-col gap-3 overflow-y-auto pb-10\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyItems()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels()\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n [class]=\"[\r\n modalService.footerClass,\r\n '!h-auto',\r\n 'min-h-0',\r\n 'flex-col',\r\n 'gap-2',\r\n 'sm:flex-row',\r\n 'sm:items-center',\r\n 'sm:justify-end',\r\n ]\"\r\n >\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n [disabled]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
1509
+ ], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div\r\n [class]=\"[\r\n modalService.contentClass,\r\n 'min-w-0',\r\n 'p-4',\r\n 'overflow-y-hidden!',\r\n 'max-[640px]:p-3',\r\n ]\"\r\n >\r\n <div class=\"mt-4 flex h-full min-h-0 flex-col gap-3 overflow-y-auto pb-10\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyItems()\"\r\n [propertyValues]=\"propertyValues()\"\r\n [loadingValuesFor]=\"loadingValuesFor()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels()\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n (onPropertyValuesRequest)=\"onPropertyValuesRequest($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div\r\n [class]=\"[\r\n modalService.footerClass,\r\n '!h-auto',\r\n 'min-h-0',\r\n 'flex-col',\r\n 'gap-2',\r\n 'sm:flex-row',\r\n 'sm:items-center',\r\n 'sm:justify-end',\r\n ]\"\r\n >\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n [disabled]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
1340
1510
  }], ctorParameters: () => [], propDecorators: { initialFormula: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFormula", required: false }] }], availableFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], editorRef: [{ type: i0.ViewChild, args: ['editor', { isSignal: true }] }] } });
1341
1511
 
1342
1512
  class FBFieldForm {
@@ -2453,77 +2623,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2453
2623
  args: [{ selector: 'mt-fb-validation-rules', standalone: true, imports: [TranslocoDirective, Table, Button], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div\r\n [class]=\"[\r\n modalService.contentClass,\r\n 'min-w-0',\r\n 'p-4',\r\n 'overflow-y-auto',\r\n 'max-[640px]:p-3',\r\n ]\"\r\n >\r\n <div class=\"flex justify-end pb-4 max-[640px]:justify-stretch\">\r\n <mt-button\r\n [label]=\"t('add-validation-rule')\"\r\n severity=\"primary\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"openCreate()\"\r\n ></mt-button>\r\n </div>\r\n <mt-table\r\n noCard\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [rowActions]=\"rowActions()\"\r\n storageKey=\"fb-validation-rules-table\"\r\n (cellChange)=\"onCellChange($event)\"\r\n ></mt-table>\r\n </div>\r\n</ng-container>\r\n" }]
2454
2624
  }], ctorParameters: () => [] });
2455
2625
 
2456
- class FormBuilderContextService {
2457
- http = inject(HttpClient);
2458
- baseUrl = 'context';
2459
- /**
2460
- * Resolves context data including navigation paths and properties.
2461
- * No caching - always fetches fresh data.
2462
- */
2463
- resolveContext(contextKey, options = {}) {
2464
- if (!contextKey)
2465
- return of(null);
2466
- return this.http
2467
- .post(`${this.baseUrl}/resolve`, {
2468
- contextKey,
2469
- displayTags: options.displayTags,
2470
- propertyFlags: options.propertyFlags,
2471
- matchAllTags: options.matchAllTags,
2472
- includeVirtual: options.includeVirtual,
2473
- includeNavigationPaths: options.includeNavigationPaths,
2474
- includeConnectionMetadata: options.includeConnectionMetadata,
2475
- includeRelatedContextValues: options.includeRelatedContextValues,
2476
- relatedContextsToInclude: options.relatedContextsToInclude,
2477
- // Consumer-supplied fields win on collision with any field above.
2478
- ...options.extraParams,
2479
- })
2480
- .pipe(map((response) => this.normalizeResponse(response)), catchError((error) => {
2481
- console.warn('Failed to resolve context:', error);
2482
- return of(null);
2483
- }));
2484
- }
2485
- /**
2486
- * Gets properties for a context.
2487
- * No caching - always fetches fresh data.
2488
- */
2489
- getProperties(contextKey, options = {}) {
2490
- if (!contextKey)
2491
- return of([]);
2492
- return this.resolveContext(contextKey, options).pipe(map((resolved) => resolved?.properties ?? []));
2493
- }
2494
- normalizeResponse(response) {
2495
- const payload = response?.data ?? response;
2496
- if (!payload)
2497
- return null;
2498
- const properties = this.normalizeProperties(payload.properties ?? []);
2499
- return {
2500
- ...payload,
2501
- properties,
2502
- };
2503
- }
2504
- normalizeProperties(raw) {
2505
- return (raw ?? [])
2506
- .map((prop) => {
2507
- const key = prop?.key ?? prop?.propertyKey;
2508
- if (!key)
2509
- return null;
2510
- const name = prop?.name ?? prop?.displayName ?? key;
2511
- return {
2512
- ...prop,
2513
- key,
2514
- name,
2515
- };
2516
- })
2517
- .filter((prop) => Boolean(prop));
2518
- }
2519
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormBuilderContextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2520
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormBuilderContextService, providedIn: 'root' });
2521
- }
2522
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: FormBuilderContextService, decorators: [{
2523
- type: Injectable,
2524
- args: [{ providedIn: 'root' }]
2525
- }] });
2526
-
2527
2626
  const SECTIONS_DROP_LIST_ID = 'sections-list';
2528
2627
  const SCOPE_ORDER = [
2529
2628
  'Current',